OpenXR interface, cleanup mess.

This commit is contained in:
mik
2026-06-23 20:25:40 -04:00
parent eb64700d87
commit 10a3d9e04f
4 changed files with 896 additions and 831 deletions

12
README.md Normal file
View File

@@ -0,0 +1,12 @@
# C++ & Graphics Prototypes
A collection of standalone modules demonstrating my approach to memory management, testing, and Vulkan pipeline engineering.
### 📁 Samples
* **`pipelineTracker.hpp` / `test_pipelineTracker.cpp`**: The VulkanPipelineTracker class is a core component designed for coordinating multi-stage workload pipelining and data flow across independent Vulkan devices using single-threaded host synchronization. It decouples multi-GPU coordination from hardware-specific extensions, ensuring reliable execution by isolating stages with custom configurations and metrics tracking. This design enhances compatibility across different GPU architectures while maintaining efficient data management between devices.
Unit tests are included to validate the basic functionality of the class.
* **`openXRComponent.hpp`**: The OpenXR Interface serves as an application-to-hardware bridge through OpenXR currently optimized for integration with VR runtime library like SteamVR.
* **`strandComponent.hpp`**: High-performance volumetric rendering engine-component optimized for massive strand geometries (30M+ vertices).

View File

@@ -1,119 +0,0 @@
/*
* Copyright (c) 2026 Mykhailo Mamedov. All rights reserved.
*
* RESEARCH PREVIEW / REFERENCE ONLY:
* This source code is provided solely for the purpose of reviewing
* the author's research methods and implementation.
*
* NO LICENSE GRANTED:
* This code is NOT for distribution, modification, or use in any
* project (commercial or otherwise). Unauthorized copying or
* use of this code is strictly prohibited.
*
* For inquiries regarding use or licensing, contact: ua.modin@gmail.com
*
* Simple UE Shader dispatch for testing.
*
*/
#include "testPlugMi.h"
#include "Interfaces/IPluginManager.h"
#include "ShaderParameterStruct.h"
#include "GlobalShader.h"
#include "HAL/IConsoleManager.h"
#include "RenderGraphBuilder.h"
#include "RenderGraphUtils.h"
#include "SceneViewExtension.h"
#define LOCTEXT_NAMESPACE "FtestPlugMiModule"
TRefCountPtr<IPooledRenderTarget> MyInternalTexturePtr;
class FMyTestCS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FMyTestCS);
SHADER_USE_PARAMETER_STRUCT(FMyTestCS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, OutputTexture)
END_SHADER_PARAMETER_STRUCT()
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) {
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
}
/// <summary>
/// Shader dispatch proc.
/// </summary>
static void Dispatch(FRDGBuilder& GraphBuilder, FRDGTextureRef OutputTexture) {
TShaderMapRef<FMyTestCS> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
if (ComputeShader.IsValid()) {
FMyTestCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FMyTestCS::FParameters>();
PassParameters->OutputTexture = GraphBuilder.CreateUAV(OutputTexture);
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("testPass"),
ComputeShader,
PassParameters,
FIntVector(32, 32, 1)
);
}
}
};
IMPLEMENT_GLOBAL_SHADER(FMyTestCS, "/Plugin/testPlugMi/Private/textureWrite.usf", "MainCS", SF_Compute);
class FMyTestViewExtension : public FSceneViewExtensionBase
{
public:
FMyTestViewExtension(const FAutoRegister& AutoRegister) : FSceneViewExtensionBase(AutoRegister) {}
/// <summary>
/// Create temporary texture and dispatch shader, boilerplates.
/// </summary>
virtual void PreRenderView_RenderThread(FRDGBuilder& GraphBuilder, FSceneView& InView) override {
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
FIntPoint(1024, 1024),
PF_FloatRGBA,
FClearValueBinding::Black,
TexCreate_ShaderResource | TexCreate_UAV
);
FRDGTextureRef textureTest = GraphBuilder.CreateTexture(Desc, TEXT("textureTestShaderOutput"));
FMyTestCS::Dispatch(GraphBuilder, textureTest);
GraphBuilder.QueueTextureExtraction(textureTest, &MyInternalTexturePtr);
}
virtual void SetupViewFamily(FSceneViewFamily& InViewFamily) override {}
virtual void SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView) override {}
virtual void BeginRenderViewFamily(FSceneViewFamily& InViewFamily) override {}
};
TSharedPtr<FMyTestViewExtension> TestViewExtension;
void FtestPlugMiModule::StartupModule() {
// Register shader earlier (PostConfigInit).
FString ShaderDir = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("testPlugMi"))->GetBaseDir(), TEXT("Shaders"));
AddShaderSourceDirectoryMapping(TEXT("/Plugin/testPlugMi"), ShaderDir);
// Adds a new view extension on post engine init.
FCoreDelegates::OnPostEngineInit.AddLambda([]() {
TestViewExtension = TSharedPtr<FMyTestViewExtension>(FSceneViewExtensions::NewExtension<FMyTestViewExtension>());
UE_LOG(LogTemp, Warning, TEXT("Extension Registered Safely via Delegate!"));
});
}
void FtestPlugMiModule::ShutdownModule() {
if (TestViewExtension.IsValid()) {
TestViewExtension.Reset();
}
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FtestPlugMiModule, testPlugMi)

View File

@@ -1,712 +0,0 @@
/*
* Copyright (c) 2021 Mykhailo Mamedov. All rights reserved.
*
* RESEARCH PREVIEW / REFERENCE ONLY:
* This source code is provided solely for the purpose of reviewing
* the author's research methods and implementation.
*
* NO LICENSE GRANTED:
* This code is NOT for distribution, modification, or use in any
* project (commercial or otherwise). Unauthorized copying or
* use of this code is strictly prohibited.
*
* For inquiries regarding use or licensing, contact: ua.modin@gmail.com
*
*
* Description:
*
* This is an old injector designed to help legacy processes
* play nice with modern filesystem features. Some older services
* get tripped up by reparse points when running out of the system root,
* so this steps in to patch the PEB.
*
*/
#include "main_defs.h"
#pragma comment(lib,"ntdll.lib")
EXTERN_C NTSTATUS NTAPI NtSuspendProcess(IN HANDLE ProcessHandle);
EXTERN_C NTSTATUS NTAPI NtResumeProcess(IN HANDLE ProcessHandle);
LPCTSTR PARENT_PIPE = TEXT("\\\\.\\pipe\\Aura");
#if _WIN64
_PPEB pebPtr = NULL;
#else
PPEB32 pebPtr = NULL;
#endif
_CreateProcessInternalW TrueCreateProcessInternalW =
(_CreateProcessInternalW)GetProcAddress(GetModuleHandle(L"KernelBase"), "CreateProcessInternalW");
inline static BOOL InArray(const std::string& value, const std::vector<std::string>& array) {
return std::find(array.begin(), array.end(), value) != array.end();
}
static void CheckProcImage(DEBUG_EVENT debugEvent) {
// Getting executable image name from handle provided.
const size_t size = _MAX_PATH + 4;
char* resolvedPath = (char*)malloc(size);
DWORD ret = GetFinalPathNameByHandleA(debugEvent.u.CreateProcessInfo.hFile, resolvedPath, size, 0);
simpleLogger(std::format("Module path: {}", resolvedPath));
fs::path image_path(resolvedPath);
free(resolvedPath);
}
static void CheckProcImageDLL(DEBUG_EVENT debugEvent) {
// Getting dll image name from handle provided. Can be merged with above.
const size_t size = _MAX_PATH + 4;
char* resolvedPath = (char*)malloc(size);
DWORD ret = GetFinalPathNameByHandleA(debugEvent.u.LoadDll.hFile, resolvedPath, size, 0);
simpleLogger(std::format("Module DLL path: {}", resolvedPath));
free(resolvedPath);
}
void CheckProcThreads(DWORD processID) {
// Print threads of the process specified (expensive).
// Currently not in use.
HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
THREADENTRY32 te32;
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hThreadSnap == INVALID_HANDLE_VALUE) {
simpleLogger(std::format("Error in getting thread snapshot"));
return;
}
te32.dwSize = sizeof(THREADENTRY32);
if (!Thread32First(hThreadSnap, &te32)) {
simpleLogger(std::format("Error in getting first thread"));
CloseHandle(hThreadSnap);
return;
}
int threadCount = 0;
do {
if (te32.th32OwnerProcessID == processID) {
++threadCount;
simpleLogger(std::format("THREAD ID: {}", te32.th32ThreadID));
}
} while (Thread32Next(hThreadSnap, &te32));
simpleLogger(std::format("THREAD COUNT: {}", threadCount));
CloseHandle(hThreadSnap);
}
HANDLE ConnectNamedPipe(LPCTSTR pipe_name) {
HANDLE hPipe = 0;
DWORD dwError;
while (TRUE) {
hPipe = ::CreateFile(pipe_name,
GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
if (hPipe != INVALID_HANDLE_VALUE)
break;
// If any error except the ERROR_PIPE_BUSY has occurred,
// we should return FALSE.
dwError = GetLastError();
if (dwError != ERROR_PIPE_BUSY) {
simpleLogger(std::format("Pipe connect failed {}", dwError));
return FALSE;
}
simpleLogger(std::format("Pipe is busy, waiting.."));
if (!WaitNamedPipe(pipe_name, 2000)) {
simpleLogger(std::format("Pipe wait failed {}", GetLastError()));
return FALSE;
}
}
return hPipe;
}
DWORD RequestPatchProc(DWORD pid, fs::path file, DWORD thread_id, DWORD parent_pid, BOOL keepSuspended) {
std::string msg;
HANDLE hPipe = 0;
DWORD dwWritten = 0;
DWORD dwRead;
BOOL pipe_res = FALSE;
char _buffer[1024]{};
hPipe = ConnectNamedPipe(PARENT_PIPE);
if (hPipe != INVALID_HANDLE_VALUE) {
msg = std::format("patch_process_peb=[{0}][{1}][{2}]",
pid, file.filename().generic_string().c_str(), parent_pid);
pipe_res = WriteFile(hPipe,
msg.c_str(),
msg.length(),
&dwWritten,
NULL);
if (!pipe_res || dwWritten != msg.length()) {
simpleLogger(std::format("Pipe failed to write(PEB): {}", pipe_res));
}
if (ReadFile(hPipe, _buffer, sizeof(_buffer) - 1, &dwRead, NULL) != FALSE) {
_buffer[dwRead] = '\0';
simpleLogger(std::format("Got back from pipe(PEB): {}", _buffer));
}
msg = std::format("patch_inject=[{0}][{1}][{2}][{3}][{4}]",
pid, file.filename().generic_string().c_str(), thread_id, parent_pid, keepSuspended);
pipe_res = WriteFile(hPipe,
msg.c_str(),
msg.length(),
&dwWritten,
NULL);
if (!pipe_res || dwWritten != msg.length()) {
simpleLogger(std::format("Pipe failed to write(PATCH): {}", pipe_res));
}
// Either response or broken connection. We shell wait.
if (ReadFile(hPipe, _buffer, sizeof(_buffer) - 1, &dwRead, NULL) != FALSE) {
_buffer[dwRead] = '\0';
simpleLogger(std::format("Got back from pipe(PATCH): {}", _buffer));
}
CloseHandle(hPipe);
simpleLogger(std::format("Pipe done"));
}
else {
simpleLogger(std::format("Named pipe is not available, service might be down."));
}
return pipe_res;
}
fs::path GetFileNameProc(HANDLE proc) {
TCHAR filename[MAX_PATH];
fs::path out;
if (GetModuleFileNameEx(proc, NULL, filename, MAX_PATH) != 0) {
out = fs::path(filename);
}
return out;
}
void CheckDbgString(DEBUG_EVENT event, DWORD pid) {
BOOL dropSub = FALSE;
DWORD dropPid = event.dwProcessId;
BOOL dropForced = FALSE;
if (!event.u.DebugString.nDebugStringLength)
return;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (event.u.DebugString.fUnicode)
{
std::wstring debugStringExtracted;
debugStringExtracted.resize(event.u.DebugString.nDebugStringLength + 1);
ReadProcessMemory(hProcess, event.u.DebugString.lpDebugStringData, &debugStringExtracted[0],
event.u.DebugString.nDebugStringLength * 2 + 2, nullptr);
std::string dbg_str(w_wo_a((LPWSTR)debugStringExtracted.c_str()));
simpleLogger(std::format("DBG_STR(W) {}", dbg_str));
if (dbg_str.starts_with(hookSet)) {
dropSub = TRUE;
}
}
else
{
std::string debugStringExtracted;
debugStringExtracted.resize(event.u.DebugString.nDebugStringLength + 1);
ReadProcessMemory(hProcess, event.u.DebugString.lpDebugStringData, &debugStringExtracted[0],
event.u.DebugString.nDebugStringLength + 1, nullptr);
std::string dbg_str(debugStringExtracted.c_str());
simpleLogger(std::format("DBG_STR(A) {}", dbg_str));
if (dbg_str.starts_with(hookSet)) {
dropSub = TRUE;
}
else if (dbg_str.starts_with(libDrop)) {
std::string pid_s(dbg_str.substr(libDrop.length(), std::string::npos));
if (pid_s.length()) {
dropPid = atoi(pid_s.c_str());
simpleLogger(std::format("DBG_STR(AU_lib_drop A) {}", dropPid));
dropSub = TRUE;
dropForced = TRUE;
}
}
}
CloseHandle(hProcess);
if (dropSub) {
if (releaseAttachedDebuggee || dropForced) {
simpleLogger(std::format("Dropping {}", dropPid));
if (!DebugActiveProcessStop(dropPid)) {
simpleLogger(std::format("Failed to detach"));
}
else {
simpleLogger(std::format("Detached"));
}
}
}
}
std::string flattenEnvBlock(LPVOID env) {
std::string out;
UINT offset = 0;
char* env_c = static_cast<char*>(env);
while (1) {
if (std::memcmp(env_c + offset, "\0\0", 2) == 0) {
break;
}
else {
offset++;
}
}
if (offset) {
char* env_copy = (char*)malloc(offset);
std::memcpy(env_copy, env_c, offset);
for (UINT i = 0; i < offset; i++) {
if (std::memcmp(env_copy + i, "\0", 1) == 0)
std::memcpy(env_copy + i, " ", 1);
}
simpleLogger(std::format("Env len, {}.", offset));
out = std::string(env_copy);
free(env_copy);
}
return out;
}
int ProcessWrapperT(HANDLE hToken, // __in
LPCWSTR AppName, // __in_opt
LPWSTR CmdLine, // __inout_opt
LPSECURITY_ATTRIBUTES ProcessAttr,
LPSECURITY_ATTRIBUTES ThreadAttr,
BOOL bIH,
DWORD flags,
LPVOID env,
LPCWSTR CurrDir,
LPSTARTUPINFOW si,
LPPROCESS_INFORMATION pi,
PHANDLE NewToken,
PBOOL ret,
PDWORD lastErr,
BOOL* procLaunched
) {
simpleLogger(std::format("{} Start", __func__));
*ret = TrueCreateProcessInternalW(
hToken, AppName, CmdLine, ProcessAttr, ThreadAttr, bIH,
flags, env, CurrDir, si, pi, NewToken);
// Preserve original error returned from API call.
*lastErr = GetLastError();
if (!*ret && *lastErr == ERROR_NOT_SUPPORTED) { // 32bit with 64bit child proc case.
// Try one more time.
flags = flags & ~DEBUG_PROCESS;
*ret = TrueCreateProcessInternalW(
hToken, AppName, CmdLine, ProcessAttr, ThreadAttr, bIH,
flags, env, CurrDir, si, pi, NewToken);
if (ret) {
simpleLogger(std::format("Launched detached (32>64bit case)"));
*procLaunched = 1;
return 0;
}
else {
*lastErr = GetLastError();
simpleLogger(std::format("Abort launch E:{:x}.", (DWORD)&lastErr));
*procLaunched = 1;
return 1;
}
}
simpleLogger(std::format("Launched?, E:{:x}.", (DWORD)&lastErr));
// Notify parent thread that process is started
// and returned shared pointers can be checked.
*procLaunched = 1;
// Children process continue running if this process terminated (very unlikely).
DebugSetProcessKillOnExit(FALSE);
BOOL toContinue = TRUE;
DWORD waitMax = INFINITE;
if (flags & CREATE_SUSPENDED) {
// Some processes could be created suspended and wait for parent to resume
// But parent can break and left children hanging, therefore putting a timeout
// after which process is released.
waitMax = 30000;
}
HANDLE Handle;
fs::path pModule;
simpleLogger(std::format("Waiting for events."));
while (toContinue) {
DWORD continueStatus = DBG_CONTINUE;
DWORD th_id = 0;
DEBUG_EVENT debugEvent = { 0 };
BOOL keep = FALSE;
BOOL keepSuspended = FALSE;
fs::path tempPath;
if (!::WaitForDebugEvent(&debugEvent, waitMax)) {
simpleLogger(std::format("DBG stop."));
break;
}
else {
simpleLogger(std::format("DBG event: {} {}", debugEvent.dwDebugEventCode, debugEvent.dwProcessId));
switch (debugEvent.dwDebugEventCode) {
case EXCEPTION_DEBUG_EVENT:
// Non of exceptions expected.
simpleLogger(std::format("DBG Exception: {}",
debugEvent.u.Exception.ExceptionRecord.ExceptionCode));
continueStatus = DBG_EXCEPTION_NOT_HANDLED;
switch (debugEvent.u.Exception.ExceptionRecord.ExceptionCode)
{
case EXCEPTION_ACCESS_VIOLATION:
simpleLogger(std::format("EXCEPTION _ACCESS_VIOLATION"));
break;
case EXCEPTION_DATATYPE_MISALIGNMENT:
simpleLogger(std::format("EXCEPTION _DATATYPE_MISALIGNMENT"));
break;
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
simpleLogger(std::format("EXCEPTION _ARRAY_BOUNDS_EXCEEDED"));
break;
case EXCEPTION_PRIV_INSTRUCTION:
simpleLogger(std::format("EXCEPTION _PRIV_INSTRUCTION"));
break;
case EXCEPTION_IN_PAGE_ERROR:
simpleLogger(std::format("EXCEPTION _IN_PAGE_ERROR"));
break;
case EXCEPTION_ILLEGAL_INSTRUCTION:
simpleLogger(std::format("EXCEPTION _ILLEGAL_INSTRUCTION"));
break;
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
simpleLogger(std::format("EXCEPTION _NONCONTINUABLE_EXCEPTION"));
break;
case EXCEPTION_STACK_OVERFLOW:
simpleLogger(std::format("EXCEPTION _STACK_OVERFLOW"));
break;
case EXCEPTION_INVALID_DISPOSITION:
break;
case EXCEPTION_INVALID_HANDLE:
simpleLogger(std::format("EXCEPTION_INVALID_HANDLE"));
break;
default:
;
}
break;
case EXIT_PROCESS_DEBUG_EVENT: // last
simpleLogger(std::format("EXIT_PROCESS_DEBUG_EVENT"));
break;
case CREATE_THREAD_DEBUG_EVENT: //
simpleLogger(std::format("CREATE_THREAD_DEBUG_EVENT"));
break;
case CREATE_PROCESS_DEBUG_EVENT: // first
simpleLogger(std::format("CREATE_PROCESS_DEBUG_EVENT"));
tempPath = GetFileNameProc(debugEvent.u.CreateProcessInfo.hProcess);
simpleLogger(std::format("Started proc: {} {}", debugEvent.dwProcessId, tempPath.generic_string()));
th_id = 0;
if (debugEvent.u.CreateProcessInfo.hThread) {
th_id = GetThreadId(debugEvent.u.CreateProcessInfo.hThread);
keepSuspended = TRUE;
}
RequestPatchProc(
debugEvent.dwProcessId,
tempPath,
th_id,
pi->dwProcessId,
keepSuspended
);
CheckProcImage(debugEvent);
break;
case EXIT_THREAD_DEBUG_EVENT:
simpleLogger(std::format("EXIT_THREAD_DEBUG_EVENT"));
break;
case LOAD_DLL_DEBUG_EVENT: //
simpleLogger(std::format("LOAD_DLL_DEBUG_EVENT"));
if (debugMode)
CheckProcImageDLL(debugEvent);
break;
case UNLOAD_DLL_DEBUG_EVENT:
simpleLogger(std::format("UNLOAD_DLL_DEBUG_EVENT"));
break;
case OUTPUT_DEBUG_STRING_EVENT:
simpleLogger(std::format("OUTPUT_DEBUG_STRING_EVENT"));
CheckDbgString(debugEvent, debugEvent.dwProcessId);
break;
default:
;
}
::ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, continueStatus);
}
}
if (!DebugActiveProcessStop(pi->dwProcessId)) {
simpleLogger(std::format("Failed to detach (top)"));
}
else {
simpleLogger(std::format("DBG Detached (top)"));
}
return 0;
}
void send_debug_string(std::string dbg_str) {
UCHAR orig_dbg_value = pebPtr->BeingDebugged;
BOOL changed = FALSE;
if (!orig_dbg_value) {
changed = TRUE;
// Required to be flipped back-forth to submit the notification.
// Original must be preserved afterwards.
pebPtr->BeingDebugged = 1;
}
OutputDebugStringA(dbg_str.c_str());
if (changed)
pebPtr->BeingDebugged = orig_dbg_value;
}
BOOL WINAPI HookCreateProcessInternalW(
HANDLE hToken, // __in
LPCWSTR AppName, // __in_opt
LPWSTR CmdLine, // __inout_opt
LPSECURITY_ATTRIBUTES ProcessAttr, // __in_opt
LPSECURITY_ATTRIBUTES ThreadAttr, // __in_opt
BOOL bIH, // __in
DWORD flags, // __in
LPVOID env, // __in_opt
LPCWSTR CurrDir, // __in_opt
LPSTARTUPINFOW si, // __in
LPPROCESS_INFORMATION pi, // __out
PHANDLE NewToken // __in
) {
std::wstring w_AppName(L"NOPE");
std::wstring w_CmdLine(L"NOPE");
if (AppName)
w_AppName = std::wstring(AppName);
if (CmdLine)
w_CmdLine = std::wstring(CmdLine);
simpleLogger(std::format("{} Create proc: AppName: `{}` CmdLine: `{}` flags: `{:x}`",
__func__, w_wo_a(w_AppName), w_wo_a(w_CmdLine), flags));
// Patch the flags.
DWORD newFlags = flags;
DWORD current_pid = GetCurrentProcessId();
simpleLogger(std::format("{} Original flags: `{:x}`", __func__, flags));
BOOL cacheOverrideSet = FALSE;
if (env) {
std::string flattenedEnv(flattenEnvBlock(env));
if (flattenedEnv.find(" __AU_ROOT") != std::string::npos) {
cacheOverrideSet = TRUE;
simpleLogger(std::format("Override is set."));
}
}
BOOL wrapIt = TRUE;
if (legacyRoot || cacheOverrideSet)
wrapIt = FALSE;
if (wrapIt) {
if (!(DEBUG_PROCESS & flags)) {
simpleLogger(std::format("Adding DEBUG_PROCESS flag"));
newFlags |= DEBUG_PROCESS;
}
}
BOOL keepSuspended = FALSE;
if (flags & CREATE_SUSPENDED) {
simpleLogger(std::format("{} sub-proc requested suspended flag: `{:x}`", __func__, flags));
keepSuspended = TRUE;
}
else {
if (legacyRoot && !cacheOverrideSet) {
newFlags = flags | CREATE_SUSPENDED;
}
}
simpleLogger(std::format("{} final flags: `{:x}`", __func__, newFlags));
BOOL res = 0;
DWORD lastError = 0;
if (!wrapIt) {
// Same thread launch (when we don't need to track hierarchy.
res = TrueCreateProcessInternalW(
hToken, AppName, CmdLine, ProcessAttr, ThreadAttr, bIH,
newFlags, env, CurrDir, si, pi, NewToken);
lastError = GetLastError();
}
else {
BOOL trigger = 0;
std::thread(ProcessWrapperT, hToken, AppName, CmdLine, ProcessAttr, ThreadAttr, bIH,
newFlags, env, CurrDir, si, pi, NewToken, &res, &lastError, &trigger).detach();
UINT counter = 0;
while (1) {
// Not an elegant replacement for mutex, since 32bit threads having issues with it.
if (trigger)
break;
if (counter > 200) {
simpleLogger(std::format("Launch timeout reached. (unexpected)"));
break;
}
counter++;
Sleep(5);
}
simpleLogger(std::format("Wrapper launched."));
}
DWORD ec = 0;
DWORD thread_res = 0;
DWORD event_pid = 0;
BOOL arm_lib = FALSE;
DWORD sz_r = 0;
if (res && !wrapIt) {
// Legacy handling
fs::path _temp_path = GetFileNameProc(pi->hProcess);
simpleLogger(std::format("{} sub-proc(below) started: {} {}",
__func__, pi->dwProcessId, _temp_path.generic_string()));
if (!cacheOverrideSet) {
// Patch PEB and create remote thread.
DWORD th_id = 0;
if (legacyRoot && pi->hThread)
th_id = GetThreadId(pi->hThread);
if (isInSRoot(_temp_path.native())) {
RequestPatchProc(
pi->dwProcessId,
_temp_path,
th_id,
current_pid,
keepSuspended
);
}
else {
if (!keepSuspended) {
if (ResumeThread(pi->hThread) == -1) {
simpleLogger(std::format("{} Failed to resume: {}", __func__, pi->dwProcessId));
}
}
}
}
}
else if (res) {
// Process created.
simpleLogger(std::format("{} sub-proc started: {}", __func__, pi->dwProcessId));
}
else {
simpleLogger(std::format("{} didn't start Err: {}", __func__, lastError));
}
if (cacheOverrideSet) {
std::string msg("AU_lib_drop");
msg.append(std::to_string(pi->dwProcessId));
send_debug_string(msg);
}
simpleLogger(std::format("Releasing process.."));
SetLastError(lastError);
return res;
}
LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) {
simpleLogger(std::format("Exception: 0x{:x}", pExceptionInfo->ExceptionRecord->ExceptionCode));
return EXCEPTION_CONTINUE_SEARCH;
}
BOOLEAN WINAPI DllMain(IN HINSTANCE hDllHandle,
IN DWORD nReason,
IN LPVOID Reserved)
{
BOOLEAN bSuccess = TRUE;
BOOL unhook_res = FALSE;
CHAR szFileName[MAX_PATH * 3];
const wchar_t* env_p = NULL;
PTEB tebPtr = NULL;
UCHAR orig_dbg_value;
BOOL changed;
std::string msg_d("AU_lib_drop");
switch (nReason) {
case DLL_PROCESS_ATTACH:
#if _WIN64
tebPtr = reinterpret_cast<PTEB>(
__readgsqword(reinterpret_cast<DWORD_PTR>(&static_cast<NT_TIB*>(nullptr)->Self)));
pebPtr = (_PPEB)tebPtr->ProcessEnvironmentBlock;
#else
tebPtr = reinterpret_cast<PTEB>(
__readfsdword(reinterpret_cast<DWORD_PTR>(&static_cast<NT_TIB*>(nullptr)->Self)));
pebPtr = (PPEB32)tebPtr->ProcessEnvironmentBlock;
#endif
GetModuleFileNameA(NULL, szFileName, MAX_PATH * 3);
pathCurrentModule = fs::path(szFileName);
exeName = std::string(pathCurrentModule.filename().generic_string());
env_p = _wgetenv(L"AU_API_HOOK_DEBUG");
if (env_p) {
debugMode = TRUE;
}
env_p = _wgetenv(L"AU_API_HOOK_WRITE_STD_ERR");
if (env_p) {
writeStdErr = _wtoi(env_p);
}
env_p = _wgetenv(L"AU_API_HOOK_LOG_PATH");
if (env_p) {
logPathPrefix = std::wstring(env_p);
}
env_p = _wgetenv(L"AU_API_HOOK_EX_HANDLER");
if (env_p) {
AddVectoredExceptionHandler(1, VectoredExceptionHandler);
}
env_p = _wgetenv(L"AU_API_HOOK_RELEASE_CHILD");
if (env_p) {
// Keep child process attached for debugging purposes.
releaseAttachedDebuggee = TRUE;
}
env_p = _wgetenv(L"__AU_PACKAGE_REP_ROOT");
if (env_p) {
msg_d.append(std::to_string(GetCurrentProcessId()));
send_debug_string(msg_d);
simpleLogger(std::format("Abort hook."));
break;
}
resolveSystemRoot();
if (isSRootLegacy() != G_LOCATION::is_not_in_systemroot) {
// Consider no response as legacy as well to keep going.
legacyRoot = TRUE;
}
simpleLogger(std::format("AU platform api lib({}-{}) attached: {} PID: {} legacy: {} dbg: {}\nfull path: {}",
__DATE__, __TIME__, pathCurrentModule.filename().generic_string(),
GetCurrentProcessId(), legacyRoot,
IsDebuggerPresent(), pathCurrentModule.generic_string()));
if (legacyRoot) {
pebPtr->PlaceholderCompatibilityMode = PHCM_DISGUISE_PLACEHOLDER;
}
// Beware PEB patch removed from within library,
// Set hook to tweak sub-procs.
if (!hookCreateProcW)
hookCreateProcW = Mhook_SetHook((PVOID*)&TrueCreateProcessInternalW, HookCreateProcessInternalW);
simpleLogger(std::format("Hook state: {}", hookCreateProcW));
send_debug_string(hookSet); // Notify parent that DLL initialized and hook is set.
break;
case DLL_PROCESS_DETACH:
simpleLogger("AU platform api lib DLL_PROCESS_DETACH");
if (hookCreateProcW)
unhook_res = Mhook_Unhook((PVOID*)&TrueCreateProcessInternalW);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return bSuccess;
}

884
openXRComponent.hpp Normal file
View File

@@ -0,0 +1,884 @@
/*
* Copyright (c) 2026 Mykhailo Mamedov. All rights reserved.
*
* RESEARCH PREVIEW / REFERENCE ONLY:
* This source code is provided solely for the purpose of reviewing
* the author's research methods and implementation.
*
* NO LICENSE GRANTED:
* This code is NOT for distribution, modification, or use in any
* project (commercial or otherwise). Unauthorized copying or
* use of this code is strictly prohibited.
*
* For inquiries regarding use or licensing, contact: ua.modin@gmail.com
*
* Description:
*
* The OpenXR Interface serves as an application-to-hardware bridge through OpenXR,
* currently optimized for integration with VR systems like SteamVR.
*
* Current Iteration:
* - Connection via OpenXR, basic session, instance.
* - Provides HMD transforms and hand tracking data.
*
* Future Improvements:
* - Structure changes.
* - Device/API compatibility checking improvements.
*
*/
#ifndef OPENXR_HEADER
#define OPENXR_HEADER
#define XR_USE_PLATFORM_WIN32
#define XR_USE_GRAPHICS_API_VULKAN
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
#include <iostream>
#include <vector>
#include <functional>
namespace cfgXr {
;
}
namespace M_CORE {
struct sem;
struct mDevice;
struct PhyDevice;
}
struct SwapchainResources {
XrSwapchain handle = nullptr;
std::vector<VkImage> images;
std::vector<VkImageView> imageViews;
std::vector<M_CORE::m_texture> m_depthImages;
VkFormat format = VkFormat::VK_FORMAT_UNDEFINED;
};
enum HandIndex {
HAND_LEFT = 0,
HAND_RIGHT = 1,
HAND_COUNT = 2
};
struct HandTrackingState {
XrHandJointLocationEXT jointLocations[2][XR_HAND_JOINT_COUNT_EXT];
XrHandJointLocationsEXT handLocations[2];
};
/**
* CB signature : It passes the views(for camera transforms)
* and the index the swapchain wants app to render to.
*/
using RenderCallback = std::function<void(
const std::vector<XrCompositionLayerProjectionView>& projectionViews,
uint32_t imageIndex
)>;
class OpenXrInterface {
public:
void SetRenderCallback(RenderCallback cb) { m_renderCallback = cb; }
void CreateXrSession(VkInstance vulkanInstance, M_CORE::mDevice& m_device, u32 queueFamilyIndex, RENDERDOC_API_1_6_0* rdoc_api);
bool checkDeviceCompatible(M_CORE::mDevice& m_device);
void PrepareSwapchainImageViews(M_CORE::mCore& core, M_CORE::mDevice& device, SwapchainResources& resources);
void CreateXrSwapchain(M_CORE::mCore& core, M_CORE::mDevice& device);
void RenderXrScene();
OpenXrInterface() {
PrintInstanceExtensions();
m_currentInstance = createXrInstance();
XrSystemGetInfo info{ XR_TYPE_SYSTEM_GET_INFO };
info.formFactor = XrFormFactor::XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
XrResult res = xrGetSystem(m_currentInstance, &info, &m_xrSystemId);
assert(!XR_FAILED(res) && "xrGetSystem failed!");
SetRequiredInstanceExtensions();
SetVulkanXrDeviceExtensions();
getVulkanGfxRequirement();
GetViewConfigViews(m_currentInstance, m_xrSystemId);
SetEnvironmentBlendModes();
XrResult result = xrGetInstanceProcAddr(
m_currentInstance, "xrLocateHandJointsEXT", (PFN_xrVoidFunction*)&pfnxrLocateHandJointsEXT);
assert(!XR_FAILED(result));
}
// Example: Get configuration for the primary stereo view
void GetViewConfigViews(XrInstance instance, XrSystemId systemId) {
uint32_t viewCount = 0;
// Get the required count
XrResult result = xrEnumerateViewConfigurationViews(
instance, systemId, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
0, &viewCount, nullptr);
assert(!XR_FAILED(result));
// Allocate and get data
m_views.resize(viewCount, { XR_TYPE_VIEW_CONFIGURATION_VIEW });
result = xrEnumerateViewConfigurationViews(
instance, systemId, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
viewCount, &viewCount, m_views.data());
assert(!XR_FAILED(result));
// Set defaults:
assert(m_views.size());
m_samples = m_views[0].recommendedSwapchainSampleCount;
m_width = m_views[0].recommendedImageRectWidth;
m_height = m_views[0].recommendedImageRectHeight;
}
XrInstance GetXrInstance() {
assert(m_currentInstance);
return m_currentInstance;
}
void BeginXrSession() {
XrSessionBeginInfo beginInfo{ XR_TYPE_SESSION_BEGIN_INFO };
beginInfo.primaryViewConfigurationType = XrViewConfigurationType::XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
beginInfo.next = nullptr;
XrResult result = xrBeginSession(m_xrSession, &beginInfo);
assert(XR_SUCCEEDED(result));
m_sessionLaunched = true;
}
XrInstance createXrInstance() {
XrInstanceCreateInfo instanceInfo{ XR_TYPE_INSTANCE_CREATE_INFO };
instanceInfo.applicationInfo = {
.applicationName = "TheApp",
.applicationVersion = 1,
.engineName = "TheEngine",
.engineVersion = 1,
.apiVersion = XR_API_VERSION_1_0 // XR_CURRENT_API_VERSION // Forward compat.
};
std::vector<const char*> enabledExtensions = {
XR_KHR_VULKAN_ENABLE_EXTENSION_NAME,
XR_KHR_VULKAN_ENABLE2_EXTENSION_NAME,
XR_EXT_HAND_TRACKING_EXTENSION_NAME
};
// XR_ERROR_EXTENSION_NOT_PRESENT TODO.
instanceInfo.enabledExtensionCount = enabledExtensions.size();
instanceInfo.enabledExtensionNames = enabledExtensions.data();
XrInstance instance;
XrResult result = xrCreateInstance(&instanceInfo, &instance);
assert(!XR_FAILED(result) && "OpenXR Runtime not found or failed!");
result = xrGetInstanceProperties(instance, &m_instanceProps);
assert(!XR_FAILED(result));
std::cout << "OpenXR Instance created successfully!" << std::endl;
std::cout << "Runtime Name: " << m_instanceProps.runtimeName << std::endl;
std::cout << "Runtime Version: " << XR_VERSION_MAJOR(m_instanceProps.runtimeVersion) << "."
<< XR_VERSION_MINOR(m_instanceProps.runtimeVersion) << "."
<< XR_VERSION_PATCH(m_instanceProps.runtimeVersion) << std::endl;
return instance;
}
void PrintInstanceExtensions() {
uint32_t extensionCount = 0;
xrEnumerateInstanceExtensionProperties(nullptr, 0, &extensionCount, nullptr);
std::vector<XrExtensionProperties> extensionProperties(extensionCount, { XR_TYPE_EXTENSION_PROPERTIES });
xrEnumerateInstanceExtensionProperties(nullptr, extensionCount, &extensionCount, extensionProperties.data());
std::cout << "Xr Extensions:" << std::endl;
for (const auto& ext : extensionProperties) {
std::cout << std::string(ext.extensionName) << std::endl;
}
}
/**
* @brief Retrieves hand tracking information from the specified context.
*
* This function retrieves details about hand tracking, such as joint locations,
*/
void getHandTrackingInfo(XrHandTrackerEXT hand_tracker, const std::vector<XrView>& views, XrTime predictedDisplayTime, HandIndex hand_idx) {
m_handTrack.handLocations[hand_idx] = { XR_TYPE_HAND_JOINT_LOCATIONS_EXT };
m_handTrack.handLocations[hand_idx].jointCount = XR_HAND_JOINT_COUNT_EXT;
m_handTrack.handLocations[hand_idx].jointLocations = m_handTrack.jointLocations[hand_idx];
XrHandJointsLocateInfoEXT locateInfo{ XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT };
locateInfo.baseSpace = m_appSpace;
locateInfo.time = predictedDisplayTime;
XrResult result = pfnxrLocateHandJointsEXT(hand_tracker, &locateInfo, &m_handTrack.handLocations[hand_idx]);
assert(XR_SUCCEEDED(result));
/*
if (m_jointLocationsInfo.isActive) {
std::cout << "\n=== Hand Tracking Frame Data ===" << std::endl;
std::cout << std::fixed << std::setprecision(4);
for (uint32_t i = 0; i < XR_HAND_JOINT_COUNT_EXT; ++i) {
constexpr XrSpaceLocationFlags validFlags =
XR_SPACE_LOCATION_POSITION_VALID_BIT |
XR_SPACE_LOCATION_ORIENTATION_VALID_BIT;
if ((m_jointLocations[i].locationFlags & validFlags) == validFlags) {
const XrVector3f& pos = m_jointLocations[i].pose.position;
const XrQuaternionf& rot = m_jointLocations[i].pose.orientation;
float radius = m_jointLocations[i].radius;
std::cout << "Joint [" << std::setw(2) << i << "] "
<< "Pos: (" << std::setw(7) << pos.x << ", "
<< std::setw(7) << pos.y << ", "
<< std::setw(7) << pos.z << ") | "
<< "Rot: (" << std::setw(7) << rot.x << ", "
<< std::setw(7) << rot.y << ", "
<< std::setw(7) << rot.z << ", "
<< std::setw(7) << rot.w << ") | "
<< "Rad: " << radius << "\n";
}
else {
std::cout << "Joint [" << std::setw(2) << i << "] Not tracked (invalid flags)\n";
}
}
std::cout << "================================\n" << std::endl;
}
else {
std::cout << "Hand tracker is currently INACTIVE." << std::endl;
}
*/
}
/**
* @brief Creates and initializes hand trackers.
*
* This function creates instances of XrHandTrackerEXT,
* It sets up the necessary configurations to start tracking both left and right hands in the current session.
* The created trackers are stored internally within this class instance.
*
*/
void CreateHandTrackers() {
XrHandTrackerCreateInfoEXT createInfo = { XrStructureType::XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT };
createInfo.hand = XrHandEXT::XR_HAND_LEFT_EXT;
createInfo.handJointSet = XrHandJointSetEXT::XR_HAND_JOINT_SET_DEFAULT_EXT;
PFN_xrCreateHandTrackerEXT pfnxrCreateHandTrackerEXT = nullptr;
XrResult result = xrGetInstanceProcAddr(m_currentInstance, "xrCreateHandTrackerEXT", (PFN_xrVoidFunction*)&pfnxrCreateHandTrackerEXT);
assert(!XR_FAILED(result));
result = pfnxrCreateHandTrackerEXT(m_xrSession, &createInfo, &m_leftHandTracker);
assert(!XR_FAILED(result));
createInfo.hand = XR_HAND_RIGHT_EXT;
result = pfnxrCreateHandTrackerEXT(m_xrSession, &createInfo, &m_rightHandTracker);
assert(!XR_FAILED(result));
}
void SetEnvironmentBlendModes() {
uint32_t count = 0;
XrViewConfigurationType viewType = XrViewConfigurationType::XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
// First call to get the required count
XrResult result = xrEnumerateEnvironmentBlendModes(m_currentInstance, m_xrSystemId, viewType, 0, &count, nullptr);
assert(XR_SUCCEEDED(result));
m_blendModes.resize(count);
// Second call to get the actual data
result = xrEnumerateEnvironmentBlendModes(m_currentInstance, m_xrSystemId, viewType, count, &count, m_blendModes.data());
assert(XR_SUCCEEDED(result));
}
void SetRequiredInstanceExtensions() {
PFN_xrGetVulkanInstanceExtensionsKHR pfnGetVulkanInstanceExtensionsKHR = nullptr;
XrResult result = xrGetInstanceProcAddr(m_currentInstance, "xrGetVulkanInstanceExtensionsKHR", (PFN_xrVoidFunction*)&pfnGetVulkanInstanceExtensionsKHR);
assert(!XR_FAILED(result));
uint32_t length = 0;
result = pfnGetVulkanInstanceExtensionsKHR(m_currentInstance, m_xrSystemId, 0, &length, nullptr);
assert(!XR_FAILED(result));
if (length == 0) return;
std::string extensions(length, ' ');
result = pfnGetVulkanInstanceExtensionsKHR(m_currentInstance, m_xrSystemId, length, &length, extensions.data());
assert(!XR_FAILED(result));
// Parse the space-separated string
std::stringstream ss(extensions);
std::string ext;
while (ss >> ext) {
m_requiredVulkanInstanceExtensions.push_back(ext);
}
}
void SetVulkanXrDeviceExtensions() {
PFN_xrGetVulkanDeviceExtensionsKHR pfnGetVulkanDeviceExtensionsKHR = nullptr;
XrResult result = xrGetInstanceProcAddr(m_currentInstance, "xrGetVulkanDeviceExtensionsKHR", (PFN_xrVoidFunction*)&pfnGetVulkanDeviceExtensionsKHR);
assert(!XR_FAILED(result));
uint32_t count = 0;
// Get the required buffer size
result = pfnGetVulkanDeviceExtensionsKHR(m_currentInstance, m_xrSystemId, 0, &count, nullptr);
assert(!XR_FAILED(result));
if (count == 0) return;
// Allocate buffer
std::string buffer(count, ' ');
// Populate buffer
result = pfnGetVulkanDeviceExtensionsKHR(m_currentInstance, m_xrSystemId, count, &count, &buffer[0]);
assert(!XR_FAILED(result));
// Parse just like the instance extensions
std::stringstream ss(buffer);
std::string ext;
while (ss >> ext) {
m_requiredVulkanDeviceExtensions.push_back(ext);
}
}
VkPhysicalDevice GetVulkanPhysicalDevice(VkInstance vkInstance) {
assert(m_currentInstance && m_xrSystemId && "Wrong order of execution.");
PFN_xrGetVulkanGraphicsDevice2KHR pfnGetVulkanGraphicsDevice2KHR = nullptr;
XrResult res = xrGetInstanceProcAddr(m_currentInstance, "xrGetVulkanGraphicsDevice2KHR", (PFN_xrVoidFunction*)&pfnGetVulkanGraphicsDevice2KHR);
assert(!XR_FAILED(res) && "xrGetInstanceProcAddr failed!");
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
XrVulkanGraphicsDeviceGetInfoKHR getInfo{ XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR };
getInfo.vulkanInstance = vkInstance;
getInfo.systemId = m_xrSystemId;
res = pfnGetVulkanGraphicsDevice2KHR(m_currentInstance, &getInfo, &physicalDevice);
assert(!XR_FAILED(res) && "pfnGetVulkanGraphicsDevice2 failed!");
return physicalDevice;
}
void getVulkanGfxRequirement(){
PFN_xrGetVulkanGraphicsRequirementsKHR pfnGetVulkanGraphicsRequirementsKHR = nullptr;
XrResult result = xrGetInstanceProcAddr(m_currentInstance, "xrGetVulkanGraphicsRequirementsKHR",
(PFN_xrVoidFunction*)&pfnGetVulkanGraphicsRequirementsKHR);
assert(!XR_FAILED(result) && "xrGetInstanceProcAddr failed!");
assert(pfnGetVulkanGraphicsRequirementsKHR &&
"Extension (xrGetVulkanGraphicsRequirementsKHR) not supported by runtime!");
// (must be done before xrCreateSession)
result = pfnGetVulkanGraphicsRequirementsKHR(m_currentInstance, m_xrSystemId, &m_graphicsRequirements);
assert(!XR_FAILED(result) && "pfnGetVulkanGraphicsRequirementsKHR failed!");
}
void ResetState(XrEventDataBuffer& eventData) {
std::memset(&eventData, 0, sizeof(XrEventDataBuffer));
eventData.type = XR_TYPE_EVENT_DATA_BUFFER;
eventData.next = nullptr;
}
bool IsInstanceStateInvalid() {
XrInstanceProperties instanceProperties{ XR_TYPE_INSTANCE_PROPERTIES };
if (!m_currentInstance)
return true;
XrResult result = xrGetInstanceProperties(m_currentInstance, &instanceProperties);
assert(XR_SUCCEEDED(result));
if (result == XR_ERROR_HANDLE_INVALID) {
return true;
}
return false;
}
void HandshakeSimulatorSwapchains() { // TODO: Check if necessary.
XrSwapchain sw_chain = swapChainResources->handle;
assert(sw_chain);
uint32_t imageIndex = 0;
XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
XrSwapchainImageReleaseInfo releaseInfo{ XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
// Check out the texture array from the runtime
XrResult res = xrAcquireSwapchainImage(sw_chain, &acquireInfo, &imageIndex);
if (res == XR_SUCCESS) {
XrSwapchainImageWaitInfo waitInfo{ XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO };
waitInfo.timeout = XR_INFINITE_DURATION; // Let it block until ready
res = xrWaitSwapchainImage(sw_chain, &waitInfo);
assert(XR_SUCCEEDED(res));
// Immediately hand it right back to unblock the compositor queue
res = xrReleaseSwapchainImage(sw_chain, &releaseInfo);
assert(XR_SUCCEEDED(res));
}
}
void SetupXrSpace() {
XrReferenceSpaceCreateInfo spaceInfo = { XR_TYPE_REFERENCE_SPACE_CREATE_INFO };
spaceInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE; // XR_REFERENCE_SPACE_TYPE_LOCAL
spaceInfo.poseInReferenceSpace = { {0,0,0,1}, {0,0,0} };
assert(xrCreateReferenceSpace(m_xrSession, &spaceInfo, &m_appSpace) == XR_SUCCESS);
}
~OpenXrInterface() {
if(m_currentInstance)
xrDestroyInstance(m_currentInstance);
if (m_xrSession)
xrDestroySession(m_xrSession);
}
u64 m_scheduledWorkloadCounter = 0;
u32 m_previousIndex = 20;
u32 m_width = 0;
u32 m_height = 0;
u32 m_samples = 0;
u32 m_arraySizeInitial = 2;
u32 m_frame = 0;
bool m_sessionLaunched = false;
bool r_running = true;
bool r_ready = false;
bool r_idle = false;
bool r_isSessionRunning = false;
bool capture_xr = false;
u32 m_captured_frames = 0;
M_CORE::mDevice* m_deviceGfx = nullptr;
RENDERDOC_API_1_6_0* m_rdoc_api;
XrSessionState r_latestState = XR_SESSION_STATE_UNKNOWN;
XrSession m_xrSession = nullptr;
XrSystemId m_xrSystemId = 0;
XrSpace m_appSpace;
XrInstance m_currentInstance = nullptr;
XrInstanceProperties m_instanceProps{ XR_TYPE_INSTANCE_PROPERTIES };
XrHandTrackerEXT m_leftHandTracker;
XrHandTrackerEXT m_rightHandTracker;
XrGraphicsRequirementsVulkanKHR m_graphicsRequirements = { XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR };
XrActionSetCreateInfo m_actionSetInfo{ XR_TYPE_ACTION_SET_CREATE_INFO };
XrSessionActionSetsAttachInfo m_attachInfo{ XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO };
PFN_xrLocateHandJointsEXT pfnxrLocateHandJointsEXT = nullptr;
std::vector<XrEnvironmentBlendMode> m_blendModes;
std::vector<XrViewConfigurationView> m_views;
std::vector <std::string> m_requiredVulkanInstanceExtensions;
std::vector <std::string> m_requiredVulkanDeviceExtensions;
SwapchainResources* swapChainResources = nullptr;
VkCommandPool xrCommandPool = nullptr;
RenderCallback m_renderCallback;
HandTrackingState m_handTrack{};
};
#endif // OPENXR_HEADER
#ifdef OPENXR_COMPONENT
void OpenXrInterface::CreateXrSession(VkInstance vulkanInstance, M_CORE::mDevice& m_device, u32 queueFamilyIndex, RENDERDOC_API_1_6_0* rdoc_api) {
XrGraphicsBindingVulkanKHR graphicsBinding = { XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR };
graphicsBinding.instance = vulkanInstance;
graphicsBinding.physicalDevice = m_device.phyDeviceData.m_physDevice;
graphicsBinding.device = m_device.virtualDevice;
graphicsBinding.queueFamilyIndex = queueFamilyIndex;
graphicsBinding.queueIndex = 0; // ?
XrSessionCreateInfo sessionCreateInfo = { XR_TYPE_SESSION_CREATE_INFO };
sessionCreateInfo.next = &graphicsBinding;
sessionCreateInfo.systemId = m_xrSystemId;
XrResult res = xrCreateSession(m_currentInstance, &sessionCreateInfo, &m_xrSession);
assert(res == XR_SUCCESS && m_xrSession);
SetupXrSpace();
// Create action set (TEST)
strcpy(m_actionSetInfo.actionSetName, "gameplay");
strcpy(m_actionSetInfo.localizedActionSetName, "Gameplay");
XrActionSet actionSet;
res = xrCreateActionSet(m_currentInstance, &m_actionSetInfo, &actionSet);
assert(XR_SUCCEEDED(res));
// Attach to the session
m_attachInfo.countActionSets = 1;
m_attachInfo.actionSets = &actionSet;
res = xrAttachSessionActionSets(m_xrSession, &m_attachInfo);
assert(XR_SUCCEEDED(res));
m_deviceGfx = &m_device;
m_rdoc_api = rdoc_api;
}
bool OpenXrInterface::checkDeviceCompatible(M_CORE::mDevice& m_device) {
// Ensure the device supports at least the minimum version
uint32_t xrMinMajor = (m_graphicsRequirements.minApiVersionSupported >> 48) & 0xFFFF;
uint32_t xrMinMinor = (m_graphicsRequirements.minApiVersionSupported >> 32) & 0xFFFF;
uint32_t vkMajor = (m_device.phyDeviceData.m_devProps.apiVersion >> 22) & 0x3FF;
uint32_t vkMinor = (m_device.phyDeviceData.m_devProps.apiVersion >> 12) & 0x3FF;
bool versionOk = vkMajor >= xrMinMajor && vkMinor >= xrMinMinor;
if (!versionOk) {
auto err_string = std::format("Api version {} not supported on {}, required {}",
m_device.phyDeviceData.m_devProps.apiVersion,
m_device.phyDeviceData.m_devProps.deviceName,
m_graphicsRequirements.minApiVersionSupported
);
assert(versionOk && err_string.c_str());
}
return true;
}
/**
* Create XR swap-chain.
*/
void OpenXrInterface::CreateXrSwapchain(M_CORE::mCore& core, M_CORE::mDevice& device) {
assert(m_currentInstance && m_xrSession);
// Create Swapchain
swapChainResources = new SwapchainResources();
swapChainResources->format = VK_FORMAT_R8G8B8A8_SRGB;
XrSwapchainCreateInfo swapchainInfo = { XR_TYPE_SWAPCHAIN_CREATE_INFO };
swapchainInfo.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_SAMPLED_BIT; // Adding XR_SWAPCHAIN_USAGE_SAMPLED_BIT
swapchainInfo.format = swapChainResources->format;
swapchainInfo.sampleCount = 1; // m_samples
swapchainInfo.width = m_width;
swapchainInfo.height = m_height;
swapchainInfo.faceCount = 1;
swapchainInfo.arraySize = m_arraySizeInitial; // Stereo
swapchainInfo.mipCount = 1;
swapchainInfo.createFlags = 0;
XrResult res = xrCreateSwapchain(m_xrSession, &swapchainInfo, &swapChainResources->handle);
assert(XR_SUCCEEDED(res));
// Prepare views
PrepareSwapchainImageViews(core, device, *swapChainResources);
}
/**
* Function to prepare Vulkan Image and Depth Views.
*/
void OpenXrInterface::PrepareSwapchainImageViews(M_CORE::mCore& core, M_CORE::mDevice& device, SwapchainResources& resources) {
u32 imageCount = 0;
XrResult res = xrEnumerateSwapchainImages(resources.handle, 0, &imageCount, nullptr);
assert(XR_SUCCEEDED(res));
std::vector<XrSwapchainImageVulkanKHR> swapchainImages(imageCount, { XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR });
res = xrEnumerateSwapchainImages(
resources.handle, imageCount, &imageCount, reinterpret_cast<XrSwapchainImageBaseHeader*>(swapchainImages.data()));
assert(XR_SUCCEEDED(res));
resources.images.clear();
resources.imageViews.clear();
resources.m_depthImages.resize(imageCount);
u32 counter = 0;
for (int i = 0; i < imageCount; i++) {
auto& image = swapchainImages[i];
resources.images.push_back(image.image);
VkImageViewCreateInfo viewInfo = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
viewInfo.image = image.image;
viewInfo.viewType = VkImageViewType::VK_IMAGE_VIEW_TYPE_2D_ARRAY;
viewInfo.format = resources.format;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = m_arraySizeInitial;
VkImageView imageView;
if (vkCreateImageView(device.virtualDevice, &viewInfo, nullptr, &imageView) != VK_SUCCESS) {
throw std::runtime_error("failed to create image view!");
}
core.setObjectName(device.virtualDevice, imageView, std::format("xrImage_{}", counter).c_str());
resources.imageViews.push_back(imageView);
VkImageLayout OldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImageLayout NewLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
core.TransitionImageLayout(viewInfo.image, resources.format, OldLayout, NewLayout, 1);
vkDeviceWaitIdle(device.virtualDevice);
counter++;
}
// Depth resources maintained by us, not XR api.
VkFormat DepthFormat = device.phyDeviceData.m_depthFormat;
for (int i = 0; i < imageCount; i++) {
M_CORE::ImageParams parms{
.device = device,
.Tex = resources.m_depthImages[i],
.ImageWidth = m_width,
.ImageHeight = m_height,
.TexFormat = DepthFormat,
.UsageFlags = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
.PropertyFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
.ImageDepth = 1,
.name = std::format("xrDepthImage_{}", i).c_str(),
.shareMode = VK_SHARING_MODE_EXCLUSIVE,
.sharedQueueFamilies = {0, 0},
.IsCubemap = false,
.Is3dImage = false,
.IsStereo = true
};
core.CreateImage(parms);
VkImageLayout OldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImageLayout NewLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
M_CORE::ImageViewParams params{};
params.Device = device.virtualDevice;
params.Image = resources.m_depthImages[i].m_image;
params.Format = DepthFormat;
params.AspectFlags = VK_IMAGE_ASPECT_DEPTH_BIT;
params.IsStereo = true;
resources.m_depthImages[i].m_view = core.CreateImageView(params);
core.setObjectName(device.virtualDevice,
resources.m_depthImages[i].m_view, std::format("xrDepthImage_{}", i).c_str());
core.TransitionImageLayout(resources.m_depthImages[i].m_image, DepthFormat, OldLayout, NewLayout, 1);
}
}
/**
* @brief RenderXrScene is the function responsible for rendering scenes within an OpenXR application context,
* facilitating interaction between VR and graphics pipelines.
*
* @details It processes:
* - Camera transforms to ensure accurate alignment of virtual content with user's field of view.
* - Hand tracking data for realistic interactions in VR scenarios.
* - Swapchain images captured during each frame cycle to provide real-time visual feedback.
* This function integrates hardware inputs like camera movements, hand positions,
* orientations, and joint locations into the rendering process.
*/
void OpenXrInterface::RenderXrScene() {
XrResult result = XrResult::XR_SUCCESS;
assert(swapChainResources);
if (!r_running)
return;
while (true) {
if (IsInstanceStateInvalid()) {
r_running = false;
break;
}
// --- Event Handling ---
XrEventDataBuffer eventData = { XR_TYPE_EVENT_DATA_BUFFER, nullptr };
while (true) {
result = xrPollEvent(m_currentInstance, &eventData);
if (result != XR_SUCCESS) { // XR_ERROR_VALIDATION_FAILURE
if (result == XR_EVENT_UNAVAILABLE) {
ResetState(eventData);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
break;
}
char errorString[XR_MAX_RESULT_STRING_SIZE];
XrResult result2 = xrResultToString(m_currentInstance, result, errorString);
assert(XR_SUCCEEDED(result2));
printf(std::format("xrPollEvent throws: {}\n", errorString).c_str());
ResetState(eventData);
break;
}
if (eventData.type == XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED) {
auto* stateChanged = reinterpret_cast<XrEventDataSessionStateChanged*>(&eventData);
r_latestState = stateChanged->state;
switch (stateChanged->state) {
case XR_SESSION_STATE_READY: {
BeginXrSession();
HandshakeSimulatorSwapchains();
ResetState(eventData);
break;
}
case XR_SESSION_STATE_SYNCHRONIZED: {
printf(std::format("XR_SESSION_STATE_SYNCHRONIZED\n").c_str());
r_isSessionRunning = true;
ResetState(eventData);
break;
}
case XR_SESSION_STATE_VISIBLE: {
printf(std::format("XR_SESSION_STATE_VISIBLE\n").c_str());
r_isSessionRunning = true;
ResetState(eventData);
break;
}
case XR_SESSION_STATE_FOCUSED: {
r_isSessionRunning = true;
ResetState(eventData);
break;
}
case XR_SESSION_STATE_IDLE: {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
ResetState(eventData);
break;
}
case XR_SESSION_STATE_STOPPING: {
r_isSessionRunning = false;
m_sessionLaunched = false;
if (stateChanged->state == XR_SESSION_STATE_STOPPING) {
xrEndSession(m_xrSession); // Clean up if required by spec
}
break;
}
}
}
else {
printf(std::format("Unhandled other event: {}\n", (int)eventData.type).c_str());
}
}
if (!m_sessionLaunched) {
break;
}
if (m_rdoc_api && capture_xr) m_rdoc_api->StartFrameCapture(m_deviceGfx->virtualDevice, nullptr);
// --- Frame Synchronization ---
XrFrameWaitInfo waitInfo = { XR_TYPE_FRAME_WAIT_INFO };
XrFrameState frameState = { XR_TYPE_FRAME_STATE };
result = xrWaitFrame(m_xrSession, &waitInfo, &frameState);
assert(!XR_FAILED(result));
XrFrameBeginInfo beginInfo = { XR_TYPE_FRAME_BEGIN_INFO };
result = xrBeginFrame(m_xrSession, &beginInfo);
assert(!XR_FAILED(result));
// --- Camera Pose Location ---
XrViewLocateInfo viewLocateInfo = { XR_TYPE_VIEW_LOCATE_INFO };
viewLocateInfo.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; // Assuming Stereo
viewLocateInfo.displayTime = frameState.predictedDisplayTime;
viewLocateInfo.space = m_appSpace;
XrViewState viewState = { XR_TYPE_VIEW_STATE };
u32 viewCount{ 0 };
std::vector<XrView> views;
result = xrLocateViews(m_xrSession, &viewLocateInfo, &viewState, viewCount, &viewCount, nullptr);
assert(!XR_FAILED(result));
assert(viewCount==2);
views.resize(viewCount, { XR_TYPE_VIEW });
result = xrLocateViews(m_xrSession, &viewLocateInfo, &viewState, viewCount, &viewCount, views.data());
assert(!XR_FAILED(result));
bool invalid_view = false;
if ((viewState.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT) == 0) {
invalid_view = true; // Pose is invalid, do not submit this layer
}
if ((viewState.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT) == 0) {
invalid_view = true;
}
// --- Pull hand tracking.
getHandTrackingInfo(m_leftHandTracker, views, frameState.predictedDisplayTime, HAND_LEFT);
getHandTrackingInfo(m_rightHandTracker, views, frameState.predictedDisplayTime, HAND_RIGHT);
// --- Rendering ---
XrCompositionLayerProjection projectionLayer = { XR_TYPE_COMPOSITION_LAYER_PROJECTION };
std::vector<XrCompositionLayerProjectionView> projectionViews(viewCount, { XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW });
if (frameState.shouldRender) {
XrRect2Di singleEyeRect;
singleEyeRect.offset.x = 0;
singleEyeRect.offset.y = 0;
singleEyeRect.extent.width = m_width;
singleEyeRect.extent.height = m_height;
if(true) {
u32 imageIndex;
XrSwapchainImageAcquireInfo acquireInfo = { XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
result = xrAcquireSwapchainImage(swapChainResources->handle, &acquireInfo, &imageIndex);
assert(!XR_FAILED(result));
XrSwapchainImageWaitInfo waitInfo = { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO };
waitInfo.timeout = XR_INFINITE_DURATION;
if (xrWaitSwapchainImage(swapChainResources->handle, &waitInfo) == XR_SUCCESS) {
// Prepare projection views.
for (uint32_t i = 0; i < viewCount; ++i) {
projectionViews[i].pose = views[i].pose;
projectionViews[i].fov = views[i].fov;
projectionViews[i].subImage.swapchain = swapChainResources->handle;
projectionViews[i].subImage.imageArrayIndex = i;
projectionViews[i].subImage.imageRect = singleEyeRect;
}
// Use registered CB, inherited from the parent instance.
// See who called SetRenderCallback() declared above.
assert(m_renderCallback);
m_renderCallback(projectionViews, 0);
m_frame++;
}
else {
throw std::runtime_error("xrWaitSwapchainImage failed!"); // TODO Check handling.
}
XrSwapchainImageReleaseInfo releaseInfo = { XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
result = xrReleaseSwapchainImage(swapChainResources->handle, &releaseInfo);
assert(!XR_FAILED(result));
}
// End Frame
projectionLayer.space = m_appSpace;
projectionLayer.viewCount = viewCount;
projectionLayer.views = projectionViews.data();
const XrCompositionLayerBaseHeader* layers[] = { (XrCompositionLayerBaseHeader*)&projectionLayer };
XrFrameEndInfo endInfo = { XR_TYPE_FRAME_END_INFO };
endInfo.displayTime = frameState.predictedDisplayTime;
endInfo.layerCount = 1;
endInfo.layers = layers;
endInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; // XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND;
assert(!not_contains(endInfo.environmentBlendMode, m_blendModes));
if (invalid_view || !r_isSessionRunning || !frameState.shouldRender) {
endInfo.layers = nullptr;
endInfo.layerCount = 0;
}
result = xrEndFrame(m_xrSession, &endInfo);
if (m_rdoc_api && capture_xr) m_rdoc_api->EndFrameCapture(m_deviceGfx->virtualDevice, nullptr);
m_captured_frames++;
if (m_captured_frames > 3) {
m_captured_frames = 0;
capture_xr = false;
}
if (result == XR_SESSION_LOSS_PENDING) {
printf(std::format("Session loss pending\n").c_str());
r_running = false;
}
if (result == XR_ERROR_SESSION_NOT_RUNNING) {
printf(std::format("Session is not running. Pausing frame loop.\n").c_str());
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
if (XR_FAILED(result)) {
char errorString[XR_MAX_RESULT_STRING_SIZE];
result = xrResultToString(m_currentInstance, result, errorString);
assert(XR_SUCCEEDED(result));
printf(std::format("xrEndFrame throws: {}\n", errorString).c_str());
}
}
else {
printf(std::format("Skipping render.\n").c_str());
}
break; // Not a loop.
}
// --- Cleanup ---
// Destroy resources here as this is the end of the thread - TODO
}
#endif