blob: 5ecac0ad9f5ccfede7a60a3526c4c285a6a9a131 [file] [log] [blame]
// Copyright 2016 The Android Open Source Project
//
// This software is licensed under the terms of the GNU General Public
// License version 2, as published by the Free Software Foundation, and
// may be copied, distributed, and modified under those terms.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#include "android/base/async/Looper.h"
#include "android/base/async/ThreadLooper.h"
#include "android/base/memory/LazyInstance.h"
#include "android/base/system/System.h"
#include "android/base/system/Win32UnicodeString.h"
#include "android/base/threads/ParallelTask.h"
#include "android/base/threads/Thread.h"
#include "android/opengl/gpuinfo.h"
#include "android/utils/file_io.h"
#include <assert.h>
#include <inttypes.h>
#include <fstream>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using android::base::Looper;
using android::base::ParallelTask;
using android::base::RunOptions;
using android::base::System;
#ifdef _WIN32
using android::base::Win32UnicodeString;
#endif
static const System::Duration kGPUInfoQueryTimeoutMs = 5000;
static const System::Duration kQueryCheckIntervalMs = 66;
static const size_t kFieldLen = 2048;
static const size_t NOTFOUND = std::string::npos;
static ::android::base::LazyInstance<GpuInfoList> sGpuInfoList =
LAZY_INSTANCE_INIT;
GpuInfoList* GpuInfoList::get() {
return sGpuInfoList.ptr();
}
void GpuInfo::addDll(std::string dll_str) {
dlls.push_back(dll_str);
}
void GpuInfoList::addGpu() {
infos.push_back(GpuInfo());
}
GpuInfo& GpuInfoList::currGpu() {
if (infos.empty()) { addGpu(); }
return infos.back();
}
std::string GpuInfoList::dump() const {
std::stringstream ss;
for (unsigned int i = 0; i < infos.size(); i++) {
ss << "GPU #" << i + 1 << std::endl;
if (!infos[i].make.empty()) {
ss << " Make: " << infos[i].make << std::endl;
}
if (!infos[i].model.empty()) {
ss << " Model: " << infos[i].model << std::endl;
}
if (!infos[i].device_id.empty()) {
ss << " Device ID: " << infos[i].device_id << std::endl;
}
if (!infos[i].revision_id.empty()) {
ss << " Revision ID: " << infos[i].revision_id << std::endl;
}
if (!infos[i].version.empty()) {
ss << " Driver version: " << infos[i].version << std::endl;
}
if (!infos[i].renderer.empty()) {
ss << " Renderer: " << infos[i].renderer << std::endl;
}
}
return ss.str();
}
void GpuInfoList::clear() {
blacklist_status = false;
infos.clear();
}
// Actual blacklist starts here.
// Most entries imported from Chrome blacklist.
static const BlacklistEntry sGpuBlacklist[] = {
// Make | Model | DeviceID | RevisionID | DriverVersion | Renderer
{nullptr, nullptr, "0x7249", nullptr, nullptr,
nullptr}, // ATI Radeon X1900 on Mac
{"8086", nullptr, nullptr, nullptr, nullptr,
"Mesa"}, // Linux, Intel, Mesa
{"8086", nullptr, nullptr, nullptr, nullptr,
"mesa"}, // Linux, Intel, Mesa
{"8086", nullptr, "27ae", nullptr, nullptr,
nullptr}, // Intel 945 Chipset
{"1002", nullptr, nullptr, nullptr, nullptr, nullptr}, // Linux, ATI
{nullptr, nullptr, "0x9583", nullptr, nullptr,
nullptr}, // ATI Radeon HD2600 on Mac
{nullptr, nullptr, "0x94c8", nullptr, nullptr,
nullptr}, // ATI Radeon HD2400 on Mac
{"NVIDIA (0x10de)", nullptr, "0x0324", nullptr, nullptr,
nullptr}, // NVIDIA GeForce FX Go5200 (Mac)
{"NVIDIA", "NVIDIA GeForce FX Go5200", nullptr, nullptr, nullptr,
nullptr}, // NVIDIA GeForce FX Go5200 (Win)
{"10de", nullptr, "0324", nullptr, nullptr,
nullptr}, // NVIDIA GeForce FX Go5200 (Linux)
{"10de", nullptr, "029e", nullptr, nullptr,
nullptr}, // NVIDIA Quadro FX 1500 (Linux)
// Various Quadro FX cards on Linux
{"10de", nullptr, "00cd", nullptr, nullptr,
"195.36.24"},
{"10de", nullptr, "00ce", nullptr, nullptr,
"195.36.24"},
// Driver version 260.19.6 on Linux
{"10de", nullptr, nullptr, nullptr, nullptr,
"260.19.6"},
{"NVIDIA (0x10de)", nullptr, "0x0393", nullptr, nullptr,
nullptr}, // NVIDIA GeForce 7300 GT (Mac)
};
static const int sBlacklistSize =
sizeof(sGpuBlacklist) / sizeof(BlacklistEntry);
// If any blacklist entry matches any gpu, return true.
bool gpuinfo_query_blacklist(GpuInfoList* gpulist,
const BlacklistEntry* list,
int size) {
for (auto gpuinfo : gpulist->infos) {
for (int i = 0; i < size; i++) {
auto bl_entry = list[i];
const char* bl_make = bl_entry.make;
const char* bl_model = bl_entry.model;
const char* bl_device_id = bl_entry.device_id;
const char* bl_revision_id = bl_entry.revision_id;
const char* bl_version = bl_entry.version;
const char* bl_renderer = bl_entry.renderer;
if (bl_make && (gpuinfo.make != bl_make))
continue;
if (bl_model && (gpuinfo.model != bl_model))
continue;
if (bl_device_id && (gpuinfo.device_id != bl_device_id))
continue;
if (bl_revision_id && (gpuinfo.revision_id != bl_revision_id))
continue;
if (bl_version && (gpuinfo.revision_id != bl_version))
continue;
if (bl_renderer && (gpuinfo.renderer.find(bl_renderer) ==
NOTFOUND))
continue;
return true;
}
}
return false;
}
std::string load_gpu_info() {
auto& sys = *System::get();
std::string tmp_dir(sys.getTempDir().c_str());
// Get temporary file path
#ifndef _WIN32
if (tmp_dir.back() != '/') {
tmp_dir += "/";
}
std::string temp_filename_pattern = "gpuinfo_XXXXXX";
std::string temp_file_path_template = (tmp_dir + temp_filename_pattern);
int tmpfd = mkstemp((char*)temp_file_path_template.data());
if (tmpfd == -1) {
return std::string();
}
const char* temp_file_path = temp_file_path_template.c_str();
#else
char tmp_filename_buffer[kFieldLen] = {};
DWORD temp_file_ret =
GetTempFileName(tmp_dir.c_str(), "gpu", 0, tmp_filename_buffer);
if (!temp_file_ret) {
return std::string();
}
const char* temp_file_path = tmp_filename_buffer;
#endif
// Execute the command to get GPU info.
#ifdef __APPLE__
char command[kFieldLen] = {};
snprintf(command, sizeof(command),
"system_profiler SPDisplaysDataType > %s", temp_file_path);
system(command);
#elif !defined(_WIN32)
char command[kFieldLen] = {};
snprintf(command, sizeof(command), "lspci -mvnn > %s", temp_file_path);
system(command);
snprintf(command, sizeof(command), "glxinfo >> %s", temp_file_path);
system(command);
#else
char temp_path_arg[kFieldLen] = {};
snprintf(temp_path_arg, sizeof(temp_path_arg), "/OUTPUT:%s",
temp_file_path);
int wmic_run_res = sys.runCommand({"wmic", temp_path_arg, "path", "Win32_VideoController", "get", "/value"},
RunOptions::WaitForCompletion |
RunOptions::TerminateOnTimeout,
kGPUInfoQueryTimeoutMs);
if (!wmic_run_res) {
return std::string();
}
#endif
std::ifstream fh(temp_file_path, std::ios::binary);
if (!fh) {
#ifdef _WIN32
Sleep(100);
#endif
fh.open(temp_file_path, std::ios::binary);
if (!fh) {
return std::string();
}
}
std::stringstream ss;
ss << fh.rdbuf();
std::string contents = ss.str();
fh.close();
#ifndef _WIN32
remove(temp_file_path);
#else
DWORD del_ret = DeleteFile(temp_file_path);
if (!del_ret) {
Sleep(100);
del_ret = DeleteFile(temp_file_path);
}
#endif
#ifdef _WIN32
int num_chars = contents.size() / sizeof(wchar_t);
std::string utf8String = Win32UnicodeString::convertToUtf8(
reinterpret_cast<const wchar_t*>(contents.c_str()), num_chars);
return utf8String;
#else
return contents;
#endif
}
std::string parse_last_hexbrackets(const std::string& str) {
size_t closebrace_p = str.rfind("]");
size_t openbrace_p = str.rfind("[", closebrace_p - 1);
return str.substr(openbrace_p + 1, closebrace_p - openbrace_p - 1);
}
std::string parse_renderer_part(const std::string& str) {
size_t lastspace_p = str.rfind(" ");
size_t prevspace_p = str.rfind(" ", lastspace_p - 1);
return str.substr(prevspace_p + 1, lastspace_p - prevspace_p - 1);
}
void parse_gpu_info_list_osx(const std::string& contents,
GpuInfoList* gpulist) {
size_t line_loc = contents.find("\n");
if (line_loc == NOTFOUND) {
line_loc = contents.size();
}
size_t p = 0;
size_t kvsep_loc;
std::string key;
std::string val;
// OS X: We expect a sequence of lines from system_profiler
// that describe all GPU's connected to the system.
// When a line containing
// Chipset Model: <gpu model name>
// that's the earliest reliable indication of information
// about an additional GPU. After that,
// it's a simple matter to find the vendor/device ID lines.
while (line_loc != NOTFOUND) {
kvsep_loc = contents.find(": ", p);
if ((kvsep_loc != NOTFOUND) && (kvsep_loc < line_loc)) {
key = contents.substr(p, kvsep_loc - p);
size_t valbegin = (kvsep_loc + 2);
val = contents.substr(valbegin, line_loc - valbegin);
if (key.find("Chipset Model") != NOTFOUND) {
gpulist->addGpu();
gpulist->currGpu().model = val;
} else if (key.find("Vendor") != NOTFOUND) {
gpulist->currGpu().make = val;
} else if (key.find("Device ID") != NOTFOUND) {
gpulist->currGpu().device_id = val;
} else if (key.find("Revision ID") != NOTFOUND) {
gpulist->currGpu().revision_id = val;
} else if (key.find("Display Type") != NOTFOUND) {
gpulist->currGpu().current_gpu = true;
} else {
}
}
if (line_loc == contents.size()) {
break;
}
p = line_loc + 1;
line_loc = contents.find("\n", p);
if (line_loc == NOTFOUND) {
line_loc = contents.size();
}
}
}
void parse_gpu_info_list_linux(const std::string& contents,
GpuInfoList* gpulist) {
size_t line_loc = contents.find("\n");
if (line_loc == NOTFOUND) {
line_loc = contents.size();
}
size_t p = 0;
std::string key;
std::string val;
bool lookfor = false;
// Linux - Only support one GPU for now.
// On Linux, the only command that seems not to take
// forever is lspci.
// We just look for "VGA" in lspci, then
// attempt to grab vendor and device information.
// Second, we issue glxinfo and look for the version string,
// in case there is a renderer such as Mesa
// to look out for.
while (line_loc != NOTFOUND) {
key = contents.substr(p, line_loc - p);
if (!lookfor && (key.find("VGA") != NOTFOUND)) {
lookfor = true;
gpulist->addGpu();
} else if (lookfor && (key.find("Vendor") != NOTFOUND)) {
gpulist->currGpu().make = parse_last_hexbrackets(key);
} else if (lookfor && (key.find("Device") != NOTFOUND)) {
gpulist->currGpu().device_id = parse_last_hexbrackets(key);
lookfor = false;
} else if (key.find("OpenGL version string") != NOTFOUND) {
gpulist->currGpu().renderer = key;
} else {
}
if (line_loc == contents.size()) {
break;
}
p = line_loc + 1;
line_loc = contents.find("\n", p);
if (line_loc == NOTFOUND) {
line_loc = contents.size();
}
}
}
static void parse_windows_gpu_dlls(int line_loc, int val_pos,
const std::string& contents,
GpuInfoList* gpulist) {
if (line_loc - val_pos == 0) {
return;
}
const std::string& dll_str =
contents.substr(val_pos, line_loc - val_pos);
size_t vp = 0;
size_t dll_sep_loc = dll_str.find(",", vp);
size_t dll_end =
(dll_sep_loc != NOTFOUND) ? dll_sep_loc : dll_str.size() - vp;
gpulist->currGpu().addDll(dll_str.substr(vp, dll_end - vp));
while (dll_sep_loc != NOTFOUND) {
vp = dll_sep_loc + 1;
dll_sep_loc = dll_str.find(",", vp);
dll_end =
(dll_sep_loc != NOTFOUND) ? dll_sep_loc : dll_str.size() - vp;
gpulist->currGpu().addDll(
dll_str.substr(vp, dll_end - vp));
}
const std::string& curr_make = gpulist->currGpu().make;
if (curr_make == "NVIDIA") {
gpulist->currGpu().addDll("nvoglv32.dll");
gpulist->currGpu().addDll("nvoglv64.dll");
} else if (curr_make == "Advanced Micro Devices, Inc.") {
gpulist->currGpu().addDll("atioglxx.dll");
gpulist->currGpu().addDll("atig6txx.dll");
}
}
void parse_gpu_info_list_windows(const std::string& contents,
GpuInfoList* gpulist) {
size_t line_loc = contents.find("\r\n");
if (line_loc == NOTFOUND) {
line_loc = contents.size();
}
size_t p = 0;
size_t equals_pos = 0;
size_t val_pos = 0;
std::string key;
std::string val;
// Windows: We use `wmic path Win32_VideoController get /value`
// to get a reasonably detailed list of '<key>=<val>'
// pairs. From these, we can get the make/model
// of the GPU, the driver version, and all DLLs involved.
while (line_loc != NOTFOUND) {
equals_pos = contents.find("=", p);
if ((equals_pos != NOTFOUND) && (equals_pos < line_loc)) {
key = contents.substr(p, equals_pos - p);
val_pos = equals_pos + 1;
val = contents.substr(val_pos, line_loc - val_pos);
if (key.find("AdapterCompatibility") != NOTFOUND) {
gpulist->addGpu();
gpulist->currGpu().make = val;
} else if (key.find("Caption") != NOTFOUND) {
gpulist->currGpu().model = val;
} else if (key.find("DriverVersion") != NOTFOUND) {
gpulist->currGpu().version = val;
} else if (key.find("InstalledDisplayDrivers") != NOTFOUND) {
parse_windows_gpu_dlls(line_loc, val_pos, contents, gpulist);
}
}
if (line_loc == contents.size()) {
break;
}
p = line_loc + 2;
line_loc = contents.find("\r\n", p);
if (line_loc == NOTFOUND) {
line_loc = contents.size();
}
}
}
void parse_gpu_info_list(const std::string& contents, GpuInfoList* gpulist) {
#ifdef __APPLE__
parse_gpu_info_list_osx(contents, gpulist);
#elif !defined(_WIN32)
parse_gpu_info_list_linux(contents, gpulist);
#else
parse_gpu_info_list_windows(contents, gpulist);
#endif
}
bool parse_and_query_blacklist(const std::string& contents) {
GpuInfoList* gpulist = GpuInfoList::get();
parse_gpu_info_list(contents, gpulist);
return gpuinfo_query_blacklist(gpulist, sGpuBlacklist, sBlacklistSize);
}
void query_blacklist_fn(bool* res) {
std::string gpu_info = load_gpu_info();
*res = parse_and_query_blacklist(gpu_info);
}
void query_blacklist_done_fn(const bool& res) { }
// Separate thread for GPU info querying:
//
// Our goal is to account for circumstances where obtaining GPU info either
// takes too long or ties up the host system in a special way where the system
// ends up hanging. This is bad, since no progress will happen for emulator
// startup, which is more critical.
//
// We therefore use a ParallelTask and a looper with timeout to take care of
// this case.
//
// Note that we use a separate thread (rather than the main thread) because
// later on when skin_winsys_enter_main_loop is called, it will set the looper
// of the main thread to a custom Looper that works with Qt
// (android::qt::createLooper()). Otherwise, creating a looper on the main
// thread at this point will prevent the custom looper from being used, which
// aborts the program.
class GPUInfoQueryThread : public android::base::Thread {
public:
GPUInfoQueryThread() {
this->start();
}
virtual intptr_t main() override {
Looper* looper = android::base::ThreadLooper::get();
android::base::runParallelTask<bool>
(looper, &query_blacklist_fn, &query_blacklist_done_fn,
kQueryCheckIntervalMs);
looper->runWithTimeoutMs(kGPUInfoQueryTimeoutMs);
looper->forceQuit();
return 0;
}
};
static android::base::LazyInstance<GPUInfoQueryThread> sGPUInfoQueryThread =
LAZY_INSTANCE_INIT;
bool async_query_host_gpu_blacklisted() {
sGPUInfoQueryThread.ptr();
sGPUInfoQueryThread->wait();
return GpuInfoList::get()->blacklist_status;
}