OpenXR interface, cleanup mess.
This commit is contained in:
12
README.md
Normal file
12
README.md
Normal 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).
|
||||
119
UEtestPlugMi.cpp
119
UEtestPlugMi.cpp
@@ -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)
|
||||
712
injector.cpp
712
injector.cpp
@@ -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
884
openXRComponent.hpp
Normal 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
|
||||
Reference in New Issue
Block a user