diff --git a/README.md b/README.md new file mode 100644 index 0000000..010fd72 --- /dev/null +++ b/README.md @@ -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). \ No newline at end of file diff --git a/UEtestPlugMi.cpp b/UEtestPlugMi.cpp deleted file mode 100644 index 5974d20..0000000 --- a/UEtestPlugMi.cpp +++ /dev/null @@ -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 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, OutputTexture) - END_SHADER_PARAMETER_STRUCT() - - static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { - return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5); - } - - /// - /// Shader dispatch proc. - /// - static void Dispatch(FRDGBuilder& GraphBuilder, FRDGTextureRef OutputTexture) { - TShaderMapRef ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); - if (ComputeShader.IsValid()) { - FMyTestCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); - 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) {} - - /// - /// Create temporary texture and dispatch shader, boilerplates. - /// - 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 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(FSceneViewExtensions::NewExtension()); - 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) diff --git a/injector.cpp b/injector.cpp deleted file mode 100644 index 4847ffa..0000000 --- a/injector.cpp +++ /dev/null @@ -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& 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(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( - __readgsqword(reinterpret_cast(&static_cast(nullptr)->Self))); - pebPtr = (_PPEB)tebPtr->ProcessEnvironmentBlock; - -#else - tebPtr = reinterpret_cast( - __readfsdword(reinterpret_cast(&static_cast(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; -} diff --git a/openXRComponent.hpp b/openXRComponent.hpp new file mode 100644 index 0000000..d235a58 --- /dev/null +++ b/openXRComponent.hpp @@ -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 +#include +#include +#include +#include + +namespace cfgXr { + ; +} + +namespace M_CORE { + struct sem; + struct mDevice; + struct PhyDevice; +} + +struct SwapchainResources { + XrSwapchain handle = nullptr; + std::vector images; + std::vector imageViews; + std::vector 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& 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 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 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& 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 m_blendModes; + std::vector m_views; + std::vector m_requiredVulkanInstanceExtensions; + std::vector 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 swapchainImages(imageCount, { XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR }); + res = xrEnumerateSwapchainImages( + resources.handle, imageCount, &imageCount, reinterpret_cast(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(&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 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 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 \ No newline at end of file