Gemini Fork: OpenXR Toolkit Plugin Msnager

Creating a fork of the OpenXR Toolkit to support plugin extensions requires architectural modifications to the core detour/hooking layer, a standardized plugin API/ABI, and a dynamic loading mechanism. Because the OpenXR Toolkit intercepts native OpenXR function calls (like xrCreateInstance, xrBeginSession, xrCreateSwapchain) via an OpenXR active runtime interceptor or OpenXR layer (API layer), adding a plugin system lets external modules hook into these calls, add custom UI elements, or manipulate graphics frames.

Architecture Overview for Plugin Support

To transform the OpenXR Toolkit into a modular framework, you need to implement three primary layers:

* Plugin Manager (PluginManager): Scans a designated directory (e.g., ./plugins/) at startup, loads dynamic libraries (.dll), and resolves exported entry points.

* Event & Hook Interceptor Routing: Allows plugins to register hooks for specific OpenXR functions or rendering lifecycle events (e.g., pre/post xrAcquireSwapchainImage, frame rendering hooks).

* C-Compatible Plugin Interface (OpenXRToolkitPlugin.h): Provides a stable ABI using standard C types so third-party developers can write plugins without compiling against the full toolkit source code.

Step 1: Define the Plugin C-API (OpenXRToolkitPlugin.h)

Create a header file that defines the interface contract between your forked toolkit and external plugins.

#pragma once

#ifdef _WIN32

#define EXPORT_API extern “C” __declspec(dllexport)

#else

#define EXPORT_API extern “C” _attribute_((visibility(“default”)))

#endif

#include <openxr/openxr.h>

// Version checking for ABI compatibility

#define OXRT_PLUGIN_API_VERSION 1

struct OXRT_PluginContext {

int apiVersion;

XrInstance instance;

XrSession session;

// Add logging or utility function pointers here if needed

};

struct OXRT_PluginInterface {

int apiVersion;

const char\* pluginName;



// Lifecycle hooks

bool (\*OnLoad)(const OXRT_PluginContext\* context);

void (\*OnUnload)();



// OpenXR Intercept hooks (Optional examples)

XrResult (\*OnCreateInstance)(const XrInstanceCreateInfo\* info, XrInstance\* instance);

void (\*OnPreRenderFrame)(XrSession session);

void (\*OnPostRenderFrame)(XrSession session);

};

// Every plugin must export this function to be recognized

typedef bool(*OXRT_GetPluginInterfaceFunc)(OXRT_PluginInterface* interfaceStruct);

Step 2: Implement the Plugin Manager in the Toolkit

Next, add a module to scan and load these plugins dynamically during the OpenXR instance creation routine (xrCreateInstance).

#include <windows.h>

#include

#include

#include

#include “OpenXRToolkitPlugin.h”

class PluginManager {

private:

std::vector<HMODULE> loadedModules;

std::vector<OXRT_PluginInterface> plugins;

public:

void LoadPlugins(const std::string& directory, const OXRT_PluginContext& context) {

    if (!std::filesystem::exists(directory)) return;

    for (const auto& entry : std::filesystem::directory_iterator(directory)) {

        if (entry.path().extension() == ".dll") {

            HMODULE hMod = LoadLibraryW(entry.path().wstring().c_str());

            if (hMod) {

                auto getInterface = (OXRT_GetPluginInterfaceFunc)GetProcAddress(hMod, "OXRT_GetPluginInterface");

                if (getInterface) {

                    OXRT_PluginInterface pluginIface{};

                    if (getInterface(&pluginIface) && pluginIface.apiVersion == OXRT_PLUGIN_API_VERSION) {

                        if (pluginIface.OnLoad && pluginIface.OnLoad(&context)) {

                            loadedModules.push_back(hMod);

                            plugins.push_back(pluginIface);

                            // Log success: pluginIface.pluginName loaded

                        }

                    }

                }

                if (loadedModules.empty() || loadedModules.back() != hMod) {

                    FreeLibrary(hMod); // Unload if failed initialization

                }

            }

        }

    }

}

void InvokePreRender(XrSession session) {

    for (auto& plugin : plugins) {

        if (plugin.OnPreRenderFrame) {

            plugin.OnPreRenderFrame(session);

        }

    }

}

void UnloadPlugins() {

    for (auto& plugin : plugins) {

        if (plugin.OnUnload) plugin.OnUnload();

    }

    for (HMODULE hMod : loadedModules) {

        FreeLibrary(hMod);

    }

    loadedModules.clear();

    plugins.clear();

}

};

Step 3: Integrate Plugin Hooks into OpenXR Detours

Modify the existing interception hooks (typically implemented using MinHook or similar hooking frameworks inside the OpenXR Toolkit’s detour layer) to forward events to your loaded plugins.

For example, inside your intercepted xrBeginSession or frame loop handler:

// Global or singleton instance of your plugin manager

extern PluginManager g_PluginManager;

XrResult Hook_xrBeginSession(XrSession session, const XrSessionBeginInfo* beginInfo) {

// Original OpenXR call execution

XrResult result = Real_xrBeginSession(session, beginInfo);

if (XR_SUCCEEDED(result)) {

    // Trigger custom startup logic for plugins tied to session creation

}

return result;

}

// Inside your rendering loop thread/detour:

void Tracked_FrameRenderLoop(XrSession session) {

g_PluginManager.InvokePreRender(session);



// ... Toolkit's native upscaling, foveated rendering, or image sharpening logic ...



// Post-render actions can go here

}

Step 4: Structuring the Fork Repository

To properly organize your GitHub fork:

* /openxr-toolkit-core/: Contains the original upstream logic (upscaling, sharpening, hand-tracking wrappers, Menu UI overlay).

* /openxr-toolkit-sdk/: Contains OpenXRToolkitPlugin.h and helper libraries so third-party developers can easily build custom plugins.

* /plugins/: A sample directory containing reference plugins (e.g., an FPS counter plugin, custom shader injection plugin, or telemetry logger).

Step 5: Building a Sample Third-Party Plugin

An extension developer can build a plugin targeting your fork by creating a simple DLL project:

#include “OpenXRToolkitPlugin.h”

#include <windows.h>

bool MyPlugin_OnLoad(const OXRT_PluginContext* context) {

OutputDebugStringA("Custom OpenXR Toolkit Plugin Loaded Successfully!\\n");

return true;

}

void MyPlugin_OnPreRender(XrSession session) {

// Perform custom per-frame logic, telemetry, or adjustments

}

EXPORTS_API bool OXRT_GetPluginInterface(OXRT_PluginInterface* interfaceStruct) {

interfaceStruct->apiVersion = OXRT_PLUGIN_API_VERSION;

interfaceStruct->pluginName = "Sample Telemetry Plugin";

interfaceStruct->OnLoad = MyPlugin_OnLoad;

interfaceStruct->OnPreRenderFrame = MyPlugin_OnPreRender;

return true;

}

Key Considerations & Next Steps

* OpenXR Active Runtime / Layer JSON Manifest: Ensure your toolkit registers itself properly as an active OpenXR API Layer (XR_APILAYER_MOVIES_openxr_toolkit or similar mechanism) so application

s load it seamlessly.

* Thread Safety: Ensure plugin hooks are thread-safe, as OpenXR rendering loops and callbacks often occur on separate graphics threads.

To ensure your forked OpenXR Toolkit loads seamlessly as an active OpenXR API Layer, you need to create a JSON manifest file and configure the Windows Registry so the OpenXR loader discovers and initializes your layer before the application starts.

Step 1: Create the API Layer JSON Manifest (XR_APILAYER_METADATA_openxr_toolkit.json)

Create a text file named according to OpenXR standards. The loader looks for JSON files following specific naming patterns or registry pointers.

{

“file_format_version”: “1.0.0”,

“api_layer”: {

"name": "XR_APILAYER_METADATA_openxr_toolkit",

"library_path": ".\\\\openxr_toolkit_plugin_layer.dll",

"api_version": "XR_CURRENT_API_VERSION",

"implementation_version": "1.2.0",

"description": "OpenXR Toolkit Fork with Plugin Architecture Support",

"disable_environment": "XR_APILAYER_METADATA_OPENXR_TOOLKIT_DISABLE",

"functions": \[

  {

    "name": "xrCreateInstance"

  },

  {

    "name": "xrEnumerateInstanceExtensionProperties"

  },

  {

    "name": "xrGetInstanceProcAddr"

  }

\],

"instance_extensions": \[

  {

    "name": "XR_EXT_local_floor",

    "extension_version": 1

  }

\]

}

}

Key Fields Explained:

* library_path: The relative or absolute path to your toolkit’s compiled DLL containing the intercepted OpenXR entry points. If it resides in the same directory as the manifest, a relative path like .\\openxr_toolkit_plugin_layer.dll works.

* disable_environment: An environment variable string that can bypass or disable the layer if troubleshooting is required.

* functions: Explicitly lists the core OpenXR functions your layer intercepts. At minimum, xrGetInstanceProcAddr is required to hook subsequent function pointers.

Step 2: Register the Layer via the Windows Registry

OpenXR loaders on Windows check specific registry keys to find available API layers. You can configure an installer script or add these entries manually.

Registry Location for Machine-Wide or User-Specific Layers:

* HKEY_LOCAL_MACHINE (All users): SOFTWARE\OpenXR\APILayers\Explicit

* HKEY_CURRENT_USER (Current user only): SOFTWARE\OpenXR\APILayers\Explicit (Recommended for user-space toolkits)

Registry Value Setup:

Create a DWORD value inside the Explicit key:

* Value Name: Full absolute path to your JSON manifest file (e.g., C:\Program Files\OpenXR-Toolkit-Fork\XR_APILAYER_METADATA_openxr_toolkit.json)

* Value Data: 0 (Setting the DWORD to 0 enables the layer; 1 disables it).

Step 3: Implement Implicit vs. Explicit Loading

OpenXR layers can be categorized as Explicit (user must opt-in via registry/manifest, recommended for overlays and toolkits) or Implicit (loaded automatically by runtimes like Meta Quest Link or SteamVR).

To ensure the OpenXR loader routes function calls correctly through your toolkit and into your loaded plugins, your DLL must export xrGetInstanceProcAddr:

#include <openxr/openxr.h>

// Forward declarations of your hooked functions

XrResult Hook_xrCreateInstance(const XrInstanceCreateInfo* info, XrInstance* instance);

extern “C” {

// Required entry point for OpenXR API Layers

\__declspec(dllexport) XrResult XRAPI_CALL xrGetInstanceProcAddr(

    XrInstance instance,

    const char\* name,

    PFN_xrVoidFunction\* function) 

{

    if (strcmp(name, "xrCreateInstance") == 0) {

        \*function = reinterpret_cast<PFN_xrVoidFunction>(Hook_xrCreateInstance);

        return XR_SUCCESS;

    }

    

    // Delegate other functions to the underlying runtime or next layer

    // (Managed via your layer's chained dispatch mechanism)

    return 

GetNextLayerGetInstanceProcAddr(instance, name, function);

}

}

To compile your forked OpenXR Toolkit into a functional API Layer DLL (openxr_toolkit_plugin_layer.dll), you need a complete source file that implements the core OpenXR dispatch mechanics, initializes the Plugin Manager, and hooks xrCreateInstance.

Below is the complete C++ implementation for the DLL entry point and core interception layer.

Complete DLL Source Code (dllmain.cpp)

Create a new C++ source file in your project and use the following code:

#define WIN32_LEAN_AND_MEAN

#include <windows.h>

#include <openxr/openxr.h>

#include <openxr/openxr_platform.h>

#include

#include

#include

#include “OpenXRToolkitPlugin.h” // From the previous step

// Global Plugin Manager instance

PluginManager g_PluginManager;

// Function pointers for chained OpenXR calls

typedef XrResult(XRAPI_PTR* PFN_xrGetInstanceProcAddr)(XrInstance instance, const char* name, PFN_xrVoidFunction* function);

typedef XrResult(XRAPI_PTR* PFN_xrCreateInstance)(const XrInstanceCreateInfo* info, XrInstance* instance);

PFN_xrCreateInstance g_Real_xrCreateInstance = nullptr;

PFN_xrGetInstanceProcAddr g_Real_xrGetInstanceProcAddr = nullptr;

XrInstance g_CurrentInstance = XR_NULL_HANDLE;

// Hooked xrCreateInstance to initialize plugins when the OpenXR session starts

XrResult XRAPI_CALL Hook_xrCreateInstance(const XrInstanceCreateInfo* info, XrInstance* instance) {

if (!g_Real_xrCreateInstance) {

    return XR_ERROR_INITIALIZATION_FAILURE;

}

XrResult result = g_Real_xrCreateInstance(info, instance);

if (XR_SUCCEEDED(result)) {

    g_CurrentInstance = \*instance;

    // Setup Plugin Context

    OXRT_PluginContext context{};

    context.apiVersion = OXRT_PLUGIN_API_VERSION;

    context.instance = g_CurrentInstance;

    // Determine path to load plugins from a 'plugins' folder next to the DLL

    wchar_t dllPath\[MAX_PATH\];

    GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,

        (LPCWSTR)&Hook_xrCreateInstance, dllPath);

    

    std::filesystem::path dir(dllPath);

    std::string pluginDir = dir.parent_path().string() + "\\\\plugins";

    // Load all discovered plugins

    g_PluginManager.LoadPlugins(pluginDir, context);

}

return result;

}

// Required OpenXR API Layer Entry Point

extern “C” {

\__declspec(dllexport) XrResult XRAPI_CALL xrGetInstanceProcAddr(

    XrInstance instance,

    const char\* name,

    PFN_xrVoidFunction\* function) 

{

    if (strcmp(name, "xrCreateInstance") == 0) {

        \*function = reinterpret_cast<PFN_xrVoidFunction>(Hook_xrCreateInstance);

        return XR_SUCCESS;

    }

    // If we have a real dispatcher function, forward other requests down the chain

    if (g_Real_xrGetInstanceProcAddr) {

        return g_Real_xrGetInstanceProcAddr(instance, name, function);

    }

    return XR_ERROR_FUNCTION_UNSUPPORTED;

}

// OpenXR Loader calls this to initialize the layer and pass down the next layer's function pointer

\__declspec(dllexport) XrResult XRAPI_CALL xrNegotiateLoaderApiLayerInterface(

    const XrNegotiateLoaderInfo\* loaderInfo,

    const char\* apiLayerName,

    XrNegotiateApiLayerRequest\* apiLayerRequest)

{

    if (loaderInfo->structType != XR_LOADER_INTERFACE_STRUCT_LOADER_INFO ||

        apiLayerRequest->structType != XR_LOADER_INTERFACE_STRUCT_API_LAYER_REQUEST) {

        return XR_ERROR_INITIALIZATION_FAILURE;

    }

    g_Real_xrGetInstanceProcAddr = loaderInfo->nextGetInstanceProcAddr;

    

    // Intercept function requests from the loader

    apiLayerRequest->layerInterfaceVersion = XR_CURRENT_API_LAYER_INTERFACE_VERSION;

    apiLayerRequest->minApiVersion = XR_MAKE_VERSION(1, 0, 0);

    apiLayerRequest->maxApiVersion = XR_CURRENT_API_VERSION;

    apiLayerRequest->getInstanceProcAddr = xrGetInstanceProcAddr;

    // Resolve original xrCreateInstance from the next layer/runtime chain

    PFN_xrVoidFunction createInstanceFunc = nullptr;

    g_Real_xrGetInstanceProcAddr(XR_NULL_HANDLE, "xrCreateInstance", &createInstanceFunc);

    g_Real_xrCreateInstance = reinterpret_cast<PFN_xrCreateInstance>(createInstanceFunc);

    return XR_SUCCESS;

}

}

// Standard DLL Entry Point for cleanup

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {

switch (ul_reason_for_call) {

case DLL_PROCESS_ATTACH:

    break;

case DLL_PROCESS_DETACH:

    g_PluginManager.UnloadPlugins();

    break;

}

return TRUE;

}

Step 2: Build Instructions

To compile this code into openxr_toolkit_plugin_layer.dll:

* Prerequisites: Install Visual Studio with the Desktop development with C++ workload. Download the official OpenXR SDK headers from Khronos.

* Project Setup: Create a new Dynamic-Link Library (DLL) project in Visual Studio.

* Include Directories: Add your OpenXR SDK include directory to your project’s C/C++ → General → Additional Include Directories.

* Compile: Build the solution in Release / x64 mode. The re

sulting openxr_toolkit_plugin_layer.dll file should be placed in the folder matching your JSON manifest’s library_path.