Skip to content

Commit

Permalink
OpenXR loader: add API layer discovery support.
Browse files Browse the repository at this point in the history
  • Loading branch information
dengkail committed Sep 4, 2023
1 parent f66902f commit fb198f9
Show file tree
Hide file tree
Showing 6 changed files with 347 additions and 9 deletions.
191 changes: 184 additions & 7 deletions src/loader/android_utilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,12 @@ constexpr auto SYSTEM_AUTHORITY = "org.khronos.openxr.system_runtime_broker";
constexpr auto BASE_PATH = "openxr";
constexpr auto ABI_PATH = "abi";
constexpr auto RUNTIMES_PATH = "runtimes";
constexpr auto API_LAYERS_PATH = "api_layer";
constexpr auto IMP_LAYER = "implicit";
constexpr auto EXP_LAYER = "explicit";

constexpr const char *getBrokerAuthority(bool systemBroker) { return systemBroker ? SYSTEM_AUTHORITY : AUTHORITY; }
constexpr const char *getLayerTypePath(bool isImplicitLayer) { return isImplicitLayer ? IMP_LAYER : EXP_LAYER; }

struct BaseColumns {
/**
Expand Down Expand Up @@ -164,6 +168,51 @@ struct Columns : BaseColumns {
};
} // namespace functions

namespace api_layer {
/**
* Final path component to this URI.
*/

/**
* Create a content URI for querying all rows of the implicit/explicit API layer data for a given
* runtime package and major version of OpenXR.
*
* @param systemBroker If the system runtime broker (instead of the installable one) should be queried.
* @param majorVer The major version of OpenXR.
* @param layerType The layer type of the API layer.
* @param abi The Android ABI name in use.
* @return A content URI for the entire table: the function remapping for that runtime.
*/
static Uri makeContentUri(bool systemBroker, int majorVersion, std::string const &layerType, const char *abi) {
auto builder = Uri_Builder::construct();
builder.scheme("content")
.authority(getBrokerAuthority(systemBroker))
.appendPath(BASE_PATH)
.appendPath(std::to_string(majorVersion))
.appendPath(ABI_PATH)
.appendPath(abi)
.appendPath(API_LAYERS_PATH)
.appendPath(getLayerTypePath(layerType == IMP_LAYER));
return builder.build();
}
struct Columns : BaseColumns {
// implicit or explicit
static constexpr auto FILE_FORMAT_VERSION = "file_format_version";
static constexpr auto NAME = "name";
static constexpr auto NATIVE_LIB_DIR = "native_lib_dir";
static constexpr auto SO_FILENAME = "so_filename";
static constexpr auto API_VERSION = "api_version";
static constexpr auto IMPLEMENTATION_VERSION = "implementation_version";
static constexpr auto DESCRIPTION = "description";
static constexpr auto ENABLE_ENVIRONMENT = "enable_environment";
static constexpr auto DISABLE_ENVIRONMENT = "disable_environment";
static constexpr auto FUNCTIONS = "functions";
// extensions names will be combined like "extension1&extension2"
static constexpr auto INSTANCE_EXTENSION_NAMES = "instance_extension_names";
static constexpr auto INSTANCE_EXTENSION_VERSIONS = "instance_extension_versions";
};
} // namespace api_layer

} // namespace

static inline jni::Array<std::string> makeArray(std::initializer_list<const char *> &&list) {
Expand Down Expand Up @@ -245,11 +294,13 @@ static int populateFunctions(wrap::android::content::Context const &context, boo
return 0;
}

/// Get cursor for active runtime, parameterized by whether or not we use the system broker
static bool getActiveRuntimeCursor(wrap::android::content::Context const &context, jni::Array<std::string> const &projection,
bool systemBroker, Cursor &cursor) {
auto uri = active_runtime::makeContentUri(systemBroker, XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), ABI);
ALOGI("getActiveRuntimeCursor: Querying URI: %s", uri.toString().c_str());
/// Get cursor for active runtime or API layer, parameterized by target type and whether or not we use the system broker
static bool getCursor(wrap::android::content::Context const &context, jni::Array<std::string> const &projection,
std::string const &targetType, bool systemBroker, Cursor &cursor) {
auto uri = (targetType == RUNTIMES_PATH)
? active_runtime::makeContentUri(systemBroker, XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), ABI)
: api_layer::makeContentUri(systemBroker, XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), targetType, ABI);
ALOGI("getCursor: Querying URI: %s", uri.toString().c_str());
try {
cursor = context.getContentResolver().query(uri, projection);
} catch (const std::exception &e) {
Expand Down Expand Up @@ -279,10 +330,10 @@ int getActiveRuntimeVirtualManifest(wrap::android::content::Context const &conte
// First, try getting the installable broker's provider
bool systemBroker = false;
Cursor cursor;
if (!getActiveRuntimeCursor(context, projection, systemBroker, cursor)) {
if (!getCursor(context, projection, RUNTIMES_PATH, systemBroker, cursor)) {
// OK, try the system broker as a fallback.
systemBroker = true;
getActiveRuntimeCursor(context, projection, systemBroker, cursor);
getCursor(context, projection, RUNTIMES_PATH, systemBroker, cursor);
}

if (cursor.isNull()) {
Expand Down Expand Up @@ -314,6 +365,132 @@ int getActiveRuntimeVirtualManifest(wrap::android::content::Context const &conte
virtualManifest = builder.build();
return 0;
}

static bool populateApiLayerManifest(std::string layerType, Cursor &cursor, std::vector<Json::Value> &layerRootNode) {
auto fileFormatVersion = cursor.getString(cursor.getColumnIndex(api_layer::Columns::FILE_FORMAT_VERSION));
auto name = cursor.getString(cursor.getColumnIndex(api_layer::Columns::NAME));
auto libDir = cursor.getString(cursor.getColumnIndex(active_runtime::Columns::NATIVE_LIB_DIR));
auto fileName = cursor.getString(cursor.getColumnIndex(api_layer::Columns::SO_FILENAME));
auto apiVersion = cursor.getString(cursor.getColumnIndex(api_layer::Columns::API_VERSION));
auto implementationVersion = cursor.getString(cursor.getColumnIndex(api_layer::Columns::IMPLEMENTATION_VERSION));
auto description = cursor.getString(cursor.getColumnIndex(api_layer::Columns::DESCRIPTION));
auto disableEnv = cursor.getString(cursor.getColumnIndex(api_layer::Columns::DISABLE_ENVIRONMENT));
auto enableEnv = cursor.getString(cursor.getColumnIndex(api_layer::Columns::ENABLE_ENVIRONMENT));
auto extensionNames = cursor.getString(cursor.getColumnIndex(api_layer::Columns::INSTANCE_EXTENSION_NAMES));
auto extensionVersions = cursor.getString(cursor.getColumnIndex(api_layer::Columns::INSTANCE_EXTENSION_VERSIONS));
auto functions = cursor.getString(cursor.getColumnIndex(api_layer::Columns::FUNCTIONS));

__android_log_print(ANDROID_LOG_INFO, TAG, "Got api layer: type: %s, name: %s, native lib dir: %s, fileName: %s, functions: %s",
layerType.c_str(), name.c_str(), libDir.c_str(), fileName.c_str(), functions.c_str());

Json::Value rootNode(Json::objectValue);
rootNode["file_format_version"] = fileFormatVersion;
rootNode["api_layer"] = Json::objectValue;
rootNode["api_layer"]["name"] = name;
rootNode["api_layer"]["library_path"] = libDir + "/" + fileName;
rootNode["api_layer"]["api_version"] = apiVersion;
rootNode["api_layer"]["implementation_version"] = implementationVersion;
rootNode["api_layer"]["description"] = description;
rootNode["api_layer"]["disable_environment"] = disableEnv;
rootNode["api_layer"]["enable_environment"] = enableEnv;

rootNode["api_layer"]["instance_extensions"] = Json::Value(Json::arrayValue);
std::vector<std::string> nameVec;
std::vector<std::string> versionVec;
// extract extension names
std::istringstream issNames(extensionNames);
std::string item;
while (std::getline(issNames, item, '&')) {
nameVec.push_back(item);
}
// extract extension versions
std::istringstream issVersions(extensionVersions);
while (std::getline(issVersions, item, '&')) {
versionVec.push_back(item);
}

Json::Value extension(Json::objectValue);
if (nameVec.size() == versionVec.size()) {
for (int i = 0; i < nameVec.size(); ++i) {
extension["name"] = nameVec[i];
extension["extension_version"] = versionVec[i];
rootNode["api_layer"]["instance_extensions"].append(extension);
__android_log_print(ANDROID_LOG_INFO, TAG, "extension name: %s, extension version: %s", nameVec[i].c_str(),
versionVec[i].c_str());
}
} else {
__android_log_print(ANDROID_LOG_INFO, TAG, "api layer extension name not match extension version!");
}

std::vector<std::string> functionNameVec;
std::vector<std::string> symbolVec;
std::istringstream issFunctions(functions);
while (std::getline(issFunctions, item, '&')) {
std::size_t pos = item.find(':');
if (pos != item.npos) {
functionNameVec.push_back(item.substr(0, pos));
symbolVec.push_back(item.substr(pos + 1, item.size()));
}
}

rootNode["api_layer"]["functions"] = Json::Value(Json::objectValue);
if (functions != "None") {
for (int i = 0; i < functionNameVec.size(); ++i) {
rootNode["api_layer"]["functions"][functionNameVec[i]] = symbolVec[i];
__android_log_print(ANDROID_LOG_INFO, TAG, "function name: %s, symbol: %s", functionNameVec[i].c_str(),
symbolVec[i].c_str());
}
} else {
__android_log_print(ANDROID_LOG_INFO, TAG, "functions field not existed!");
}

layerRootNode.push_back(rootNode);

return true;
}

static void enumerateApiLayerManifests(std::string layerType, wrap::android::content::Context const &context,
jni::Array<std::string> &projection, std::vector<Json::Value> &virtualManifests) {
Cursor cursor;

getCursor(context, projection, layerType, false, cursor);
if (cursor.isNull()) {
return;
}
cursor.moveToFirst();
for (int i = 0; i < cursor.getCount(); ++i) {
populateApiLayerManifest(layerType, cursor, virtualManifests);
cursor.moveToNext();
}

cursor.close();
}

int getApiLayerVirtualManifests(std::string layerType, wrap::android::content::Context const &context,
std::vector<Json::Value> &virtualManifests) {
static bool hasQueryBroker = false;
static std::vector<Json::Value> implicitLayerManifest;
static std::vector<Json::Value> explicitLayerManifest;

__android_log_print(ANDROID_LOG_INFO, TAG, "Try to get %s API layer from broker!", layerType.c_str());
if (!hasQueryBroker) {
jni::Array<std::string> projection =
makeArray({api_layer::Columns::FILE_FORMAT_VERSION, api_layer::Columns::NAME, api_layer::Columns::NATIVE_LIB_DIR,
api_layer::Columns::SO_FILENAME, api_layer::Columns::API_VERSION, api_layer::Columns::IMPLEMENTATION_VERSION,
api_layer::Columns::DESCRIPTION, api_layer::Columns::ENABLE_ENVIRONMENT,
api_layer::Columns::DISABLE_ENVIRONMENT, api_layer::Columns::FUNCTIONS,
api_layer::Columns::INSTANCE_EXTENSION_NAMES, api_layer::Columns::INSTANCE_EXTENSION_VERSIONS});

enumerateApiLayerManifests(IMP_LAYER, context, projection, implicitLayerManifest);
enumerateApiLayerManifests(EXP_LAYER, context, projection, explicitLayerManifest);

hasQueryBroker = true;
}

virtualManifests = (layerType == IMP_LAYER) ? implicitLayerManifest : explicitLayerManifest;
return 0;
}

} // namespace openxr_android

#endif // __ANDROID__
12 changes: 12 additions & 0 deletions src/loader/android_utilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ using wrap::android::content::Context;
* @return 0 on success, something else on failure.
*/
int getActiveRuntimeVirtualManifest(wrap::android::content::Context const &context, Json::Value &virtualManifest);

/*!
* Find the implicit/explicit API layers on the system, and return a constructed JSON object representing it.
*
* @param type An String to indicate layer type of API layers, implicit or explicit.
* @param context An Android context, preferably an Activity Context.
* @param[out] virtualManifest The Json::Value to fill with the virtual manifest.
*
* @return 0 on success, something else on failure.
*/
int getApiLayerVirtualManifests(std::string layerType, wrap::android::content::Context const &context,
std::vector<Json::Value> &virtualManifests);
} // namespace openxr_android

#endif // __ANDROID__
127 changes: 127 additions & 0 deletions src/loader/manifest_file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,118 @@ void ApiLayerManifestFile::AddManifestFilesAndroid(ManifestFileType type,
}
#endif // XR_USE_PLATFORM_ANDROID

void ApiLayerManifestFile::CreateIfValid(ManifestFileType type, const Json::Value &root_node, const std::string &filename,
std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) {
std::ostringstream error_ss("ApiLayerManifestFile::CreateIfValid ");
JsonVersion file_version = {};
if (!ManifestFile::IsValidJson(root_node, file_version)) {
error_ss << "isValidJson indicates " << filename << " is not a valid manifest file.";
LoaderLogger::LogErrorMessage("", error_ss.str());
return;
}

Json::Value layer_root_node = root_node["api_layer"];

// The API Layer manifest file needs the "api_layer" root as well as other sub-nodes.
// If any of those aren't there, fail.
if (layer_root_node.isNull() || layer_root_node["name"].isNull() || !layer_root_node["name"].isString() ||
layer_root_node["api_version"].isNull() || !layer_root_node["api_version"].isString() ||
layer_root_node["library_path"].isNull() || !layer_root_node["library_path"].isString() ||
layer_root_node["implementation_version"].isNull() || !layer_root_node["implementation_version"].isString()) {
error_ss << filename << " is missing required fields. Verify all proper fields exist.";
LoaderLogger::LogErrorMessage("", error_ss.str());
return;
}
if (MANIFEST_TYPE_IMPLICIT_API_LAYER == type) {
bool enabled = true;
// Implicit layers require the disable environment variable.
if (layer_root_node["disable_environment"].isNull() || !layer_root_node["disable_environment"].isString()) {
error_ss << "Implicit layer " << filename << " is missing \"disable_environment\"";
LoaderLogger::LogErrorMessage("", error_ss.str());
return;
}
// Check if there's an enable environment variable provided
if (!layer_root_node["enable_environment"].isNull() && layer_root_node["enable_environment"].isString()) {
std::string env_var = layer_root_node["enable_environment"].asString();
// If it's not set in the environment, disable the layer
if (!PlatformUtilsGetEnvSet(env_var.c_str())) {
enabled = false;
}
}
// Check for the disable environment variable, which must be provided in the JSON
std::string env_var = layer_root_node["disable_environment"].asString();
// If the env var is set, disable the layer. Disable env var overrides enable above
if (PlatformUtilsGetEnvSet(env_var.c_str())) {
enabled = false;
}

// Not enabled, so pretend like it isn't even there.
if (!enabled) {
error_ss << "Implicit layer " << filename << " is disabled";
LoaderLogger::LogInfoMessage("", error_ss.str());
return;
}
}
std::string layer_name = layer_root_node["name"].asString();
std::string api_version_string = layer_root_node["api_version"].asString();
JsonVersion api_version = {};
const int num_fields = sscanf(api_version_string.c_str(), "%u.%u", &api_version.major, &api_version.minor);
api_version.patch = 0;

if ((num_fields != 2) || (api_version.major == 0 && api_version.minor == 0) ||
api_version.major > XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)) {
error_ss << "layer " << filename << " has invalid API Version. Skipping layer.";
LoaderLogger::LogWarningMessage("", error_ss.str());
return;
}

char *end_ptr;
uint32_t implementation_version = strtol(layer_root_node["implementation_version"].asString().c_str(), &end_ptr, 10);
if (*end_ptr != '\0') {
error_ss << "layer " << filename << " has invalid implementation version.";
LoaderLogger::LogWarningMessage("", error_ss.str());
}

std::string library_path = layer_root_node["library_path"].asString();

// If the library_path variable has no directory symbol, it's just a file name and should be accessible on the
// global library path.
if (library_path.find('\\') != std::string::npos || library_path.find('/') != std::string::npos) {
// If the library_path is an absolute path, just use that if it exists
if (FileSysUtilsIsAbsolutePath(library_path)) {
if (!FileSysUtilsPathExists(library_path)) {
error_ss << filename << " library " << library_path << " does not appear to exist";
LoaderLogger::LogErrorMessage("", error_ss.str());
return;
}
} else {
// Otherwise, treat the library path as a relative path based on the JSON file.
std::string combined_path;
std::string file_parent;
if (!FileSysUtilsGetParentPath(filename, file_parent) ||
!FileSysUtilsCombinePaths(file_parent, library_path, combined_path) || !FileSysUtilsPathExists(combined_path)) {
error_ss << filename << " library " << combined_path << " does not appear to exist";
LoaderLogger::LogErrorMessage("", error_ss.str());
return;
}
library_path = combined_path;
}
}

std::string description;
if (!layer_root_node["description"].isNull() && layer_root_node["description"].isString()) {
description = layer_root_node["description"].asString();
}

// Add this layer manifest file
manifest_files.emplace_back(
new ApiLayerManifestFile(type, filename, layer_name, description, api_version, implementation_version, library_path));

// Add any extensions to it after the fact.
// Handle any renamed functions
manifest_files.back()->ParseCommon(layer_root_node);
}

void ApiLayerManifestFile::CreateIfValid(ManifestFileType type, const std::string &filename, std::istream &json_stream,
LibraryLocator locate_library,
std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) {
Expand Down Expand Up @@ -868,6 +980,7 @@ void ApiLayerManifestFile::CreateIfValid(ManifestFileType type, const std::strin
new ApiLayerManifestFile(type, filename, layer_name, description, api_version, implementation_version, library_path));

// Add any extensions to it after the fact.
// Handle any renamed functions
manifest_files.back()->ParseCommon(layer_root_node);
}

Expand Down Expand Up @@ -975,5 +1088,19 @@ XrResult ApiLayerManifestFile::FindManifestFiles(ManifestFileType type,
ApiLayerManifestFile::AddManifestFilesAndroid(type, manifest_files);
#endif // XR_USE_PLATFORM_ANDROID

#if defined(XR_KHR_LOADER_INIT_SUPPORT)
std::vector<Json::Value> virtualManifests;
std::string layerType = (type == ManifestFileType::MANIFEST_TYPE_IMPLICIT_API_LAYER) ? "implicit" : "explicit";
XrResult result = GetPlatformApiLayerVirtualManifests(layerType, virtualManifests);
if (XR_SUCCESS == result) {
for (int i = 0; i < virtualManifests.size(); ++i) {
ApiLayerManifestFile::CreateIfValid(type, virtualManifests[i], "virtual manifest", manifest_files);
}
} else {
LoaderLogger::LogErrorMessage("", "ApiLayerManifestFile::FindManifestFiles - faile to get virtual manifest files.");
assert(0);
}
#endif // XR_KHR_LOADER_INIT_SUPPORT

return XR_SUCCESS;
}
Loading

0 comments on commit fb198f9

Please sign in to comment.