// // Copyright 2018 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // feature_support_util.cpp: Helps client APIs make decisions based on rules // data files. For example, the Android EGL loader uses this library to // determine whether to use ANGLE or a native GLES driver. #include "feature_support_util.h" #include #include #include "common/platform.h" #if defined(ANGLE_PLATFORM_ANDROID) # include # include #endif #include #include #include #include #include #include #include #include "../gpu_info_util/SystemInfo.h" namespace angle { #if defined(ANGLE_PLATFORM_ANDROID) // Define ANGLE_FEATURE_UTIL_LOG_VERBOSE if you want VERBOSE to output // ANGLE_FEATURE_UTIL_LOG_VERBOSE is automatically defined when is_debug = true # define ERR(...) __android_log_print(ANDROID_LOG_ERROR, "ANGLE", __VA_ARGS__) # define WARN(...) __android_log_print(ANDROID_LOG_WARN, "ANGLE", __VA_ARGS__) # define INFO(...) __android_log_print(ANDROID_LOG_INFO, "ANGLE", __VA_ARGS__) # define DEBUG(...) __android_log_print(ANDROID_LOG_DEBUG, "ANGLE", __VA_ARGS__) # ifdef ANGLE_FEATURE_UTIL_LOG_VERBOSE # define VERBOSE(...) __android_log_print(ANDROID_LOG_VERBOSE, "ANGLE", __VA_ARGS__) # else # define VERBOSE(...) ((void)0) # endif #else // defined(ANDROID) # define ERR(...) printf(__VA_ARGS__) # define WARN(...) printf(__VA_ARGS__) # define INFO(...) printf(__VA_ARGS__) # define DEBUG(...) printf(__VA_ARGS__) // Uncomment for debugging. //# define VERBOSE(...) printf(__VA_ARGS__) # define VERBOSE(...) #endif // defined(ANDROID) // JSON values are generally composed of either: // - Objects, which are a set of comma-separated string:value pairs (note the recursive nature) // - Arrays, which are a set of comma-separated values. // We'll call the string in a string:value pair the "identifier". These identifiers are defined // below, as follows: // The JSON identifier for the top-level set of rules. This is an object, the value of which is an // array of rules. The rules will be processed in order. If a rule matches, the rule's version of // the answer (true or false) becomes the new answer. After all rules are processed, the // most-recent answer is the final answer. constexpr char kJsonRules[] = "Rules"; // The JSON identifier for a given rule. A rule is an object, the first string:value pair is this // identifier (i.e. "Rule") as the string and the value is a user-firendly description of the rule: constexpr char kJsonRule[] = "Rule"; // Within a rule, the JSON identifier for the answer--whether or not to use ANGLE. The value is a // boolean (i.e. true or false). constexpr char kJsonUseANGLE[] = "UseANGLE"; // Within a rule, the JSON identifier for describing one or more applications. The value is an // array of objects, each object of which can specify attributes of an application. constexpr char kJsonApplications[] = "Applications"; // Within an object that describes the attributes of an application, the JSON identifier for the // name of the application (e.g. "com.google.maps"). The value is a string. If any other // attributes will be specified, this must be the first attribute specified in the object. constexpr char kJsonAppName[] = "AppName"; // Within a rule, the JSON identifier for describing one or more devices. The value is an // array of objects, each object of which can specify attributes of a device. constexpr char kJsonDevices[] = "Devices"; // Within an object that describes the attributes of a device, the JSON identifier for the // manufacturer of the device. The value is a string. If any other non-GPU attributes will be // specified, this must be the first attribute specified in the object. constexpr char kJsonManufacturer[] = "Manufacturer"; // Within an object that describes the attributes of a device, the JSON identifier for the // model of the device. The value is a string. constexpr char kJsonModel[] = "Model"; // Within an object that describes the attributes of a device, the JSON identifier for describing // one or more GPUs/drivers used in the device. The value is an // array of objects, each object of which can specify attributes of a GPU and its driver. constexpr char kJsonGPUs[] = "GPUs"; // Within an object that describes the attributes of a GPU and driver, the JSON identifier for the // vendor of the device/driver. The value is a string. If any other attributes will be specified, // this must be the first attribute specified in the object. constexpr char kJsonVendor[] = "Vendor"; // Within an object that describes the attributes of a GPU and driver, the JSON identifier for the // deviceId of the device. The value is an unsigned integer. If the driver version will be // specified, this must preceded the version attributes specified in the object. constexpr char kJsonDeviceId[] = "DeviceId"; // Within an object that describes the attributes of either an application or a GPU, the JSON // identifier for the major version of that application or GPU driver. The value is a positive // integer number. Not specifying a major version implies a wildcard for all values of a version. constexpr char kJsonVerMajor[] = "VerMajor"; // Within an object that describes the attributes of either an application or a GPU, the JSON // identifier for the minor version of that application or GPU driver. The value is a positive // integer number. In order to specify a minor version, it must be specified immediately after the // major number associated with it. Not specifying a minor version implies a wildcard for the // minor, subminor, and patch values of a version. constexpr char kJsonVerMinor[] = "VerMinor"; // Within an object that describes the attributes of either an application or a GPU, the JSON // identifier for the subminor version of that application or GPU driver. The value is a positive // integer number. In order to specify a subminor version, it must be specified immediately after // the minor number associated with it. Not specifying a subminor version implies a wildcard for // the subminor and patch values of a version. constexpr char kJsonVerSubMinor[] = "VerSubMinor"; // Within an object that describes the attributes of either an application or a GPU, the JSON // identifier for the patch version of that application or GPU driver. The value is a positive // integer number. In order to specify a patch version, it must be specified immediately after the // subminor number associated with it. Not specifying a patch version implies a wildcard for the // patch value of a version. constexpr char kJsonVerPatch[] = "VerPatch"; // This encapsulates a std::string. The default constructor (not given a string) assumes that this // is a wildcard (i.e. will match all other StringPart objects). class StringPart { public: StringPart() = default; explicit StringPart(const std::string part) : mPart(part), mWildcard(false) {} ~StringPart() = default; static StringPart FromJson(const Json::Value &parent, const char *key) { if (parent.isMember(key) && parent[key].isString()) { return StringPart(parent[key].asString()); } return {}; } bool match(const StringPart &toCheck) const { return (mWildcard || toCheck.mWildcard || (toCheck.mPart == mPart)); } public: std::string mPart; bool mWildcard = true; }; // This encapsulates a 32-bit unsigned integer. The default constructor (not given a number) // assumes that this is a wildcard (i.e. will match all other IntegerPart objects). class IntegerPart { public: IntegerPart() = default; explicit IntegerPart(uint32_t part) : mPart(part), mWildcard(false) {} ~IntegerPart() = default; static IntegerPart FromJson(const Json::Value &parent, const char *key) { if (parent.isMember(key) && parent[key].isInt()) { return IntegerPart(parent[key].asInt()); } return {}; } bool match(const IntegerPart &toCheck) const { return (mWildcard || toCheck.mWildcard || (toCheck.mPart == mPart)); } public: uint32_t mPart = 0; bool mWildcard = true; }; // This encapsulates a list of other classes, each of which will have a match() and logItem() // method. The common constructor (given a type, but not any list items) assumes that this is // a wildcard (i.e. will match all other ListOf objects). template class ListOf { public: explicit ListOf(const std::string listType) : mWildcard(true), mListType(listType) {} ~ListOf() { mList.clear(); } void addItem(T &&toAdd) { mList.push_back(std::move(toAdd)); mWildcard = false; } bool match(const T &toCheck) const { VERBOSE("\t\t Matching ListOf<%s> against item:\n", mListType.c_str()); if (mWildcard || toCheck.mWildcard) { VERBOSE("\t\t\t Successful match due to wildcard.\n"); return true; } for (const T &it : mList) { if (it.match(toCheck)) { VERBOSE("\t\t\t Successful match due to list item match.\n"); return true; } } VERBOSE("\t\t\t Failed to match.\n"); return false; } bool match(const ListOf &toCheck) const { VERBOSE("\t\t Matching ListOf<%s>:\n", mListType.c_str()); if (mWildcard || toCheck.mWildcard) { VERBOSE("\t\t\t Successful match due to wildcard.\n"); return true; } // If we make it to here, both this and toCheck have at least one item in their mList. for (const T &it : toCheck.mList) { if (match(it)) { VERBOSE("\t\t\t Successful match due to list item match.\n"); return true; } } VERBOSE("\t\t\t Failed to match list.\n"); return false; } void logListOf(const std::string prefix, const std::string name) const { if (mWildcard) { VERBOSE("%sListOf%s is wildcarded to always match\n", prefix.c_str(), name.c_str()); } else { VERBOSE("%sListOf%s has %d item(s):\n", prefix.c_str(), name.c_str(), static_cast(mList.size())); for (auto &it : mList) { it.logItem(); } } } bool mWildcard; private: std::string mListType; std::vector mList; }; // This encapsulates up-to four 32-bit unsigned integers, that represent a potentially-complex // version number. The default constructor (not given any numbers) assumes that this is a wildcard // (i.e. will match all other Version objects). Each part of a Version is stored in an IntegerPart // class, and so may be wildcarded as well. class Version { public: Version(uint32_t major, uint32_t minor, uint32_t subminor, uint32_t patch) : mMajor(major), mMinor(minor), mSubminor(subminor), mPatch(patch) {} Version() = default; Version(const Version &) = default; Version(Version &&) = default; Version &operator=(const Version &) = default; Version &operator=(Version &&) = default; ~Version() = default; static Version FromJson(const Json::Value &jObject) { Version version; version.mMajor = IntegerPart::FromJson(jObject, kJsonVerMajor); if (version.mMajor.mWildcard) { return version; } // Revision fields are only checked if their parent version field // is set. version.mMinor = IntegerPart::FromJson(jObject, kJsonVerMinor); if (version.mMinor.mWildcard) { return version; } version.mSubminor = IntegerPart::FromJson(jObject, kJsonVerSubMinor); if (version.mSubminor.mWildcard) { return version; } version.mPatch = IntegerPart::FromJson(jObject, kJsonVerPatch); return version; } bool match(const Version &toCheck) const { VERBOSE("\t\t\t Matching Version %s against %s\n", getString().c_str(), toCheck.getString().c_str()); return (isWildcard() || toCheck.isWildcard() || (mMajor.match(toCheck.mMajor) && mMinor.match(toCheck.mMinor) && mSubminor.match(toCheck.mSubminor) && mPatch.match(toCheck.mPatch))); } std::string getString() const { if (mMajor.mWildcard) { return "*"; } std::ostringstream ss; ss << mMajor.mPart; // Must at least have a major version: if (!mMinor.mWildcard) { ss << "." << mMinor.mPart; if (!mSubminor.mWildcard) { ss << "." << mSubminor.mPart; if (!mPatch.mWildcard) { ss << "." << mPatch.mPart; } } } if (mPatch.mWildcard) { ss << ".*"; } return ss.str(); } bool isWildcard() const { return mMajor.mWildcard; } public: IntegerPart mMajor; IntegerPart mMinor; IntegerPart mSubminor; IntegerPart mPatch; }; // This encapsulates an application, and potentially the application's Version. The default // constructor (not given any values) assumes that this is a wildcard (i.e. will match all // other Application objects). Each part of an Application is stored in a class that may // also be wildcarded. class Application { public: Application(StringPart name, Version version = {}) : mName(name), mVersion(version), mWildcard(false) {} Application() = default; ~Application() = default; static bool FromJson(const Json::Value &jObject, Application *out) { // If an application is listed, the application's name is required: auto name = StringPart::FromJson(jObject, kJsonAppName); if (name.mWildcard) { return false; } auto version = Version::FromJson(jObject); *out = Application{std::move(name), std::move(version)}; return true; } bool match(const Application &toCheck) const { return (mWildcard || toCheck.mWildcard || (toCheck.mName.match(mName) && toCheck.mVersion.match(mVersion))); } void logItem() const { if (mWildcard) { VERBOSE(" Wildcard (i.e. will match all applications)\n"); } else if (!mVersion.isWildcard()) { VERBOSE(" Application \"%s\" (version: %s)\n", mName.mPart.c_str(), mVersion.getString().c_str()); } else { VERBOSE(" Application \"%s\"\n", mName.mPart.c_str()); } } public: StringPart mName; Version mVersion; bool mWildcard = true; }; // This encapsulates a GPU and its driver. The default constructor (not given any values) assumes // that this is a wildcard (i.e. will match all other GPU objects). Each part of a GPU is stored // in a class that may also be wildcarded. class GPU { public: GPU(StringPart vendor, IntegerPart deviceId, Version version) : mVendor(std::move(vendor)), mDeviceId(std::move(deviceId)), mVersion(version), mWildcard(false) {} GPU(std::string vendor, uint32_t deviceId, Version version) : GPU(StringPart(std::move(vendor)), IntegerPart(deviceId), std::move(version)) {} GPU() = default; ~GPU() = default; bool match(const GPU &toCheck) const { VERBOSE("\t\t Matching %s \n\t\t against %s\n", toString().c_str(), toCheck.toString().c_str()); return (mWildcard || toCheck.mWildcard || (toCheck.mVendor.match(mVendor) && toCheck.mDeviceId.match(mDeviceId) && toCheck.mVersion.match(mVersion))); } // Returns true if out is set to a valid GPU instance. static bool CreateGpuFromJson(const Json::Value &jObject, GPU *out) { // If a GPU is listed, the vendor name is required: auto vendor = StringPart::FromJson(jObject, kJsonVendor); if (vendor.mWildcard) { WARN("Asked to parse a GPU, but no vendor found.\n"); return false; } auto deviceId = IntegerPart::FromJson(jObject, kJsonDeviceId); auto version = Version::FromJson(jObject); *out = GPU{std::move(vendor), std::move(deviceId), std::move(version)}; return true; } std::string toString() const { if (mWildcard) { return std::string("Wildcard (i.e. will match all GPUs)"); } std::ostringstream ss; ss << "GPU vendor: " << mVendor.mPart; if (!mDeviceId.mWildcard) { ss << ", deviceId: " << std::hex << mDeviceId.mPart; } ss << ", version: " << mVersion.getString(); return ss.str(); } void logItem() const { VERBOSE("\t %s\n", toString().c_str()); } public: StringPart mVendor; IntegerPart mDeviceId; Version mVersion; bool mWildcard = true; }; // This encapsulates a device, and potentially the device's model and/or a list of GPUs/drivers // associated with the Device. The default constructor (not given any values) assumes that this is // a wildcard (i.e. will match all other Device objects). Each part of a Device is stored in a // class that may also be wildcarded. class Device { public: Device(StringPart manufacturer, StringPart model) : mManufacturer(std::move(manufacturer)), mModel(std::move(model)), mGpuList("GPU"), mWildcard(false) {} Device() : mGpuList("GPU") {} ~Device() = default; static Device FromJson(const Json::Value &jObject) { auto manufacturer = StringPart::FromJson(jObject, kJsonManufacturer); if (!manufacturer.mWildcard) { // We don't let a model be specified without also specifying a manufacturer: auto model = StringPart::FromJson(jObject, kJsonModel); return Device(std::move(manufacturer), std::move(model)); } // This case is not treated as an error because a rule may wish to only call out one or // more GPUs, but not any specific manufacturer (e.g. for any manufacturer's device // that uses a GPU from Vendor-A, with DeviceID-Foo, and with driver version 1.2.3.4): return Device(); } void addGPU(GPU &&gpu) { mGpuList.addItem(std::move(gpu)); } bool match(const Device &toCheck) const { // GPU lists must always match, even when wildcards are used. VERBOSE("\t Checking ListOf:\n"); if (!mGpuList.match(toCheck.mGpuList)) { VERBOSE("\t Failed to match due to mismatched GPU list.\n"); return false; } if (mWildcard || toCheck.mWildcard) { VERBOSE("\t Matching due to wildcard.\n"); return true; } if (toCheck.mManufacturer.match(mManufacturer) && toCheck.mModel.match(mModel)) { VERBOSE("\t Matching due to manufacturer and model match.\n"); return true; } return false; } void logItem() const { if (mWildcard) { if (mGpuList.mWildcard) { VERBOSE(" Wildcard (i.e. will match all devices)\n"); return; } else { VERBOSE( " Device with any manufacturer and model" ", and with the following GPUs:\n"); } } else { if (!mModel.mWildcard) { VERBOSE( " Device manufacturer: \"%s\" and model \"%s\"" ", and with the following GPUs:\n", mManufacturer.mPart.c_str(), mModel.mPart.c_str()); } else { VERBOSE( " Device manufacturer: \"%s\"" ", and with the following GPUs:\n", mManufacturer.mPart.c_str()); } } mGpuList.logListOf(" ", "GPUs"); } public: StringPart mManufacturer; StringPart mModel; ListOf mGpuList; bool mWildcard = true; }; // This encapsulates a particular scenario to check against the rules. A Scenario is similar to a // Rule, except that a Rule has an answer and potentially many wildcards, and a Scenario is the // fully-specified combination of an Application and a Device that is proposed to be run with // ANGLE. It is compared with the list of Rules. class Scenario { public: Scenario(const char *appName, const char *deviceMfr, const char *deviceModel) : mApplication(Application(StringPart(appName))), mDevice(Device(StringPart(deviceMfr), StringPart(deviceModel))) {} ~Scenario() = default; void logScenario() { VERBOSE(" Scenario to compare against the rules:\n"); VERBOSE(" Application:\n"); mApplication.logItem(); VERBOSE(" Device:\n"); mDevice.logItem(); } public: Application mApplication; Device mDevice; }; // This encapsulates a Rule that provides an answer based on whether a particular Scenario matches // the Rule. A Rule always has an answer, but can potentially wildcard every item in it (i.e. // match every scenario). class Rule { public: Rule(const std::string description, bool useANGLE) : mDescription(description), mAppList("Application"), mDevList("Device"), mUseANGLE(useANGLE) {} ~Rule() = default; void addApp(Application &&app) { mAppList.addItem(std::move(app)); } void addDevice(Device &&dev) { mDevList.addItem(std::move(dev)); } bool match(const Scenario &toCheck) const { VERBOSE(" Matching rule \"%s\" against scenario:\n", mDescription.c_str()); if (!mAppList.match(toCheck.mApplication)) { VERBOSE("\tFailed to match rule due to mismatched application.\n"); return false; } if (!mDevList.match(toCheck.mDevice)) { VERBOSE("\tFailed to match rule due to mismatched device.\n"); return false; } VERBOSE("\tSuccessfully matched rule."); return true; } bool getUseANGLE() const { return mUseANGLE; } void logRule() const { VERBOSE(" Rule: \"%s\" %s ANGLE\n", mDescription.c_str(), mUseANGLE ? "enables" : "disables"); mAppList.logListOf(" ", "Applications"); mDevList.logListOf(" ", "Devices"); } std::string mDescription; ListOf mAppList; ListOf mDevList; bool mUseANGLE; }; // This encapsulates a list of Rules that Scenarios are matched against. A Scenario is compared // with each Rule, in order. Any time a Scenario matches a Rule, the current answer is overridden // with the answer of the matched Rule. class RuleList { public: RuleList() {} ~RuleList() { mRuleList.clear(); } static RuleList *ReadRulesFromJsonString(const std::string jsonFileContents) { RuleList *rules = new RuleList; // Open the file and start parsing it: Json::CharReaderBuilder builder; // Json::CharReaderBuilder::strictMode(&builder.settings_); std::unique_ptr reader(builder.newCharReader()); Json::Value jTopLevelObject; std::string errorMessage; const bool succeeded = reader->parse(&*jsonFileContents.begin(), &*jsonFileContents.end(), &jTopLevelObject, &errorMessage); if (!succeeded) { VERBOSE("Failed to parse rules from json file. Error: %s\n", errorMessage.c_str()); return nullptr; } for (const auto &jRule : jTopLevelObject[kJsonRules]) { std::string ruleDescription = jRule[kJsonRule].asString(); bool useANGLE = jRule[kJsonUseANGLE].asBool(); Rule newRule(std::move(ruleDescription), useANGLE); for (const auto &jApp : jRule[kJsonApplications]) { Application app; if (Application::FromJson(jApp, &app)) { newRule.addApp(std::move(app)); } } for (const auto &jDev : jRule[kJsonDevices]) { Device newDev = Device::FromJson(jDev); for (const auto &jGPU : jDev[kJsonGPUs]) { GPU newGPU; if (GPU::CreateGpuFromJson(jGPU, &newGPU)) { newDev.addGPU(std::move(newGPU)); } } newRule.addDevice(std::move(newDev)); } rules->addRule(std::move(newRule)); } // Make sure there is at least one, default rule. If not, add it here: if (rules->mRuleList.empty()) { Rule defaultRule("Default Rule", false); rules->addRule(std::move(defaultRule)); } return rules; } void addRule(Rule &&rule) { mRuleList.push_back(std::move(rule)); } bool getUseANGLE(const Scenario &toCheck) { // Initialize useANGLE to the system-wide default (that should be set in the default // rule, but just in case, set it here too): bool useANGLE = false; VERBOSE("Checking scenario against %d ANGLE-for-Android rules:\n", static_cast(mRuleList.size())); for (const Rule &rule : mRuleList) { VERBOSE(" Checking Rule: \"%s\" (to see whether there's a match)\n", rule.mDescription.c_str()); if (rule.match(toCheck)) { VERBOSE(" -> Rule matches. Updating useANGLE to %s.\n", rule.getUseANGLE() ? "true" : "false"); // The ANGLE rules are ordered from least to greatest specificity, meaning that // the last rule with a match should dictate whether or not ANGLE should be // recommended for use. useANGLE = rule.getUseANGLE(); } else { VERBOSE(" -> Rule doesn't match.\n"); } } return useANGLE; } void logRules() { VERBOSE("Showing %d ANGLE-for-Android rules:\n", static_cast(mRuleList.size())); for (const Rule &rule : mRuleList) { rule.logRule(); } } public: std::vector mRuleList; }; } // namespace angle extern "C" { using namespace angle; // This function is part of the version-2 API: ANGLE_EXPORT bool ANGLEGetFeatureSupportUtilAPIVersion(unsigned int *versionToUse) { if (!versionToUse || (*versionToUse < kFeatureVersion_LowestSupported)) { // The versionToUse is either nullptr or is less than the lowest version supported, which // is an error. return false; } if (*versionToUse > kFeatureVersion_HighestSupported) { // The versionToUse is greater than the highest version supported; change it to the // highest version supported (caller will decide if it can use that version). *versionToUse = kFeatureVersion_HighestSupported; } return true; } // This function is part of the version-2 API: ANGLE_EXPORT bool ANGLEAndroidParseRulesString(const char *rulesString, RulesHandle *rulesHandle, int *rulesVersion) { if (!rulesString || !rulesHandle || !rulesVersion) { return false; } std::string rulesFileContents = rulesString; RuleList *rules = RuleList::ReadRulesFromJsonString(rulesFileContents); if (!rules) { return false; } rules->logRules(); *rulesHandle = rules; *rulesVersion = 0; return true; } // This function is part of the version-2 API: ANGLE_EXPORT bool ANGLEGetSystemInfo(SystemInfoHandle *systemInfoHandle) { if (!systemInfoHandle) { return false; } // TODO (http://anglebug.com/3036): Restore the real code angle::SystemInfo *systemInfo = new angle::SystemInfo; systemInfo->gpus.resize(1); GPUDeviceInfo &gpu = systemInfo->gpus[0]; gpu.vendorId = 0xFEFEFEFE; gpu.deviceId = 0xFEEEFEEE; gpu.driverVendor = "Foo"; gpu.driverVersion = "1.2.3.4"; *systemInfoHandle = systemInfo; return true; } // This function is part of the version-2 API: ANGLE_EXPORT bool ANGLEAddDeviceInfoToSystemInfo(const char *deviceMfr, const char *deviceModel, SystemInfoHandle systemInfoHandle) { angle::SystemInfo *systemInfo = static_cast(systemInfoHandle); if (!deviceMfr || !deviceModel || !systemInfo) { return false; } systemInfo->machineManufacturer = deviceMfr; systemInfo->machineModelName = deviceModel; return true; } // This function is part of the version-2 API: ANGLE_EXPORT bool ANGLEShouldBeUsedForApplication(const RulesHandle rulesHandle, int rulesVersion, const SystemInfoHandle systemInfoHandle, const char *appName) { RuleList *rules = static_cast(rulesHandle); angle::SystemInfo *systemInfo = static_cast(systemInfoHandle); if (!rules || !systemInfo || !appName || (systemInfo->gpus.size() != 1)) { return false; } Scenario scenario(appName, systemInfo->machineManufacturer.c_str(), systemInfo->machineModelName.c_str()); Version gpuDriverVersion(systemInfo->gpus[0].detailedDriverVersion.major, systemInfo->gpus[0].detailedDriverVersion.minor, systemInfo->gpus[0].detailedDriverVersion.subMinor, systemInfo->gpus[0].detailedDriverVersion.patch); GPU gpuDriver(systemInfo->gpus[0].driverVendor, systemInfo->gpus[0].deviceId, std::move(gpuDriverVersion)); scenario.mDevice.addGPU(std::move(gpuDriver)); scenario.logScenario(); bool rtn = rules->getUseANGLE(std::move(scenario)); VERBOSE("Application \"%s\" should %s ANGLE.\n", appName, rtn ? "use" : "NOT use"); return rtn; } // This function is part of the version-2 API: ANGLE_EXPORT void ANGLEFreeRulesHandle(const RulesHandle rulesHandle) { RuleList *rules = static_cast(rulesHandle); if (rules) { delete rules; } } // This function is part of the version-2 API: ANGLE_EXPORT void ANGLEFreeSystemInfoHandle(const SystemInfoHandle systemInfoHandle) { angle::SystemInfo *systemInfo = static_cast(systemInfoHandle); if (systemInfo) { delete systemInfo; } } } // extern "C"