From a98298b6683c035fb2dbac5d6a1d4f478414c651 Mon Sep 17 00:00:00 2001 From: ecker Date: Mon, 22 Jun 2026 22:04:18 -0500 Subject: [PATCH] repaired VR support (to-do: make the VR rendermode work) --- bin/data/config.json | 6 +- bin/data/shaders/display/vr/flat.frag.glsl | 27 + bin/data/shaders/display/vr/flat.vert.glsl | 23 + bin/data/shaders/display/vr/stereo.frag.glsl | 27 + bin/data/shaders/display/vr/stereo.vert.glsl | 16 + client/client/ext.cpp | 5 + engine/inc/uf/ext/openvr/openvr.h | 24 +- engine/inc/uf/ext/vulkan/rendermodes/vr.h | 18 + engine/inc/uf/ext/vulkan/rendertarget.h | 1 + engine/inc/uf/utils/math/matrix/pod.inl | 30 +- engine/src/engine/ext/ext.cpp | 378 +++---- .../src/engine/ext/gui/manager/behavior.cpp | 1 + engine/src/engine/ext/scene/behavior.cpp | 5 +- engine/src/engine/graph/graph.cpp | 6 +- engine/src/ext/openvr/openvr.cpp | 976 ++++++++---------- .../src/ext/vulkan/rendermodes/deferred.cpp | 85 +- .../ext/vulkan/rendermodes/rendertarget.cpp | 20 + engine/src/ext/vulkan/rendermodes/vr.cpp | 250 +++++ engine/src/ext/vulkan/rendertarget.cpp | 22 +- engine/src/utils/camera/camera.cpp | 8 +- makefiles/platforms/win64.mk | 2 +- 21 files changed, 1099 insertions(+), 831 deletions(-) create mode 100644 bin/data/shaders/display/vr/flat.frag.glsl create mode 100644 bin/data/shaders/display/vr/flat.vert.glsl create mode 100644 bin/data/shaders/display/vr/stereo.frag.glsl create mode 100644 bin/data/shaders/display/vr/stereo.vert.glsl create mode 100644 engine/inc/uf/ext/vulkan/rendermodes/vr.h create mode 100644 engine/src/ext/vulkan/rendermodes/vr.cpp diff --git a/bin/data/config.json b/bin/data/config.json index 869709ce..4ddcb0df 100644 --- a/bin/data/config.json +++ b/bin/data/config.json @@ -116,7 +116,7 @@ "deferred": true, "gui": true, "vsync": true, // vsync on vulkan side rather than engine-side - "hdr": true, + "hdr": false, "vxgi": false, // to-do: fix issues "culling": false, "bloom": true, @@ -324,7 +324,7 @@ "preset": "native" // native (1x), quality (1.5x), balanced (1.7x), performance (2.0x), ultra (3.0x) }, "vr" : { - "enable" : false, + "enable" : true, "manifest": "./data/openvr_manifest.json", "swap eyes": false, "dominant eye": 0, @@ -357,7 +357,7 @@ "max": 0.1 // 0.2 }, "debug draw": { - "static": false, + "static": true, "dynamic": true, "trigger": false, "contacts": false, diff --git a/bin/data/shaders/display/vr/flat.frag.glsl b/bin/data/shaders/display/vr/flat.frag.glsl new file mode 100644 index 00000000..4fdd47c7 --- /dev/null +++ b/bin/data/shaders/display/vr/flat.frag.glsl @@ -0,0 +1,27 @@ +#version 450 +#pragma shader_stage(fragment) +#extension GL_EXT_samplerless_texture_functions : require + +layout (location = 0) in vec2 inUv; +layout (location = 1) flat in uint inPass; + +layout (location = 0) out vec4 outLeft; +layout (location = 1) out vec4 outRight; + +layout (binding = 0) uniform sampler2D samplerColor; + +layout(constant_id = 0) const int FORCE_OPAQUE = 0; + +#define TEXTURES 1 + +#include "../../common/macros.h" +#include "../../common/structs.h" +#include "../../common/functions.h" + +void main() { + vec4 outColor = texture( samplerColor, inUv ); + if ( FORCE_OPAQUE == 1 ) outColor.a = 1; + + if ( inPass == 0 ) outLeft = outColor; + else if ( inPass == 1 ) outRight = outColor; +} \ No newline at end of file diff --git a/bin/data/shaders/display/vr/flat.vert.glsl b/bin/data/shaders/display/vr/flat.vert.glsl new file mode 100644 index 00000000..da793524 --- /dev/null +++ b/bin/data/shaders/display/vr/flat.vert.glsl @@ -0,0 +1,23 @@ +#version 450 +#pragma shader_stage(vertex) + +#include "../../common/macros.h" +#include "../../common/structs.h" + +layout (location = 0) out vec2 outUv; +layout (location = 1) out flat uint outPass; + +layout( push_constant ) uniform PushBlock { + uint pass; + uint draw; +} PushConstant; + +layout (binding = 0) uniform Camera { + Viewport viewport[6]; +} camera; + +void main() { + outUv = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + outPass = PushConstant.pass; + gl_Position = camera.viewport[PushConstant.pass].projection * camera.viewport[PushConstant.pass].view * vec4(outUv * 2.0f + -1.0f, 0.0f, 1.0f); +} \ No newline at end of file diff --git a/bin/data/shaders/display/vr/stereo.frag.glsl b/bin/data/shaders/display/vr/stereo.frag.glsl new file mode 100644 index 00000000..426a7c60 --- /dev/null +++ b/bin/data/shaders/display/vr/stereo.frag.glsl @@ -0,0 +1,27 @@ +#version 450 +#pragma shader_stage(fragment) +#extension GL_EXT_samplerless_texture_functions : require + +layout (location = 0) in vec2 inUv; +layout (location = 1) flat in uint inPass; + +layout (location = 0) out vec4 outLeft; +layout (location = 1) out vec4 outRight; + +layout (binding = 0) uniform sampler2DArray samplerColor; + +layout(constant_id = 0) const int FORCE_OPAQUE = 0; + +#define TEXTURES 1 + +#include "../../common/macros.h" +#include "../../common/structs.h" +#include "../../common/functions.h" + +void main() { + vec4 outColor = texture( samplerColor, vec3(inUv, inPass) ); + if ( FORCE_OPAQUE == 1 ) outColor.a = 1; + + if ( inPass == 0 ) outLeft = outColor; + else if ( inPass == 1 ) outRight = outColor; +} \ No newline at end of file diff --git a/bin/data/shaders/display/vr/stereo.vert.glsl b/bin/data/shaders/display/vr/stereo.vert.glsl new file mode 100644 index 00000000..95fd44b3 --- /dev/null +++ b/bin/data/shaders/display/vr/stereo.vert.glsl @@ -0,0 +1,16 @@ +#version 450 +#pragma shader_stage(vertex) + +layout (location = 0) out vec2 outUv; +layout (location = 1) out flat uint outPass; + +layout( push_constant ) uniform PushBlock { + uint pass; + uint draw; +} PushConstant; + +void main() { + outUv = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + outPass = PushConstant.pass; + gl_Position = vec4(outUv * 2.0f + -1.0f, 0.0f, 1.0f); +} \ No newline at end of file diff --git a/client/client/ext.cpp b/client/client/ext.cpp index 5002b2cb..e4118e56 100644 --- a/client/client/ext.cpp +++ b/client/client/ext.cpp @@ -11,6 +11,8 @@ #include #include +#include // yuck + bool client::ready = false; bool client::terminated = false; uf::Window client::window; @@ -110,6 +112,9 @@ void client::initialize() { #else auto& configRenderJson = client::config["engine"]["ext"]["software"]; #endif + #if UF_USE_OPENVR + if ( ext::openvr::enabled ) return; + #endif if ( !ext::json::isArray( configRenderJson["framebuffer"]["size"] ) ) { uf::renderer::settings::width = payload.window.size.x; diff --git a/engine/inc/uf/ext/openvr/openvr.h b/engine/inc/uf/ext/openvr/openvr.h index 2e6f5a9e..de991a0d 100644 --- a/engine/inc/uf/ext/openvr/openvr.h +++ b/engine/inc/uf/ext/openvr/openvr.h @@ -24,27 +24,13 @@ namespace vr { namespace ext { namespace openvr { - struct Driver { - uf::stl::string name; - uf::stl::string serial; - uf::stl::string manifest; - vr::TrackedDevicePose_t poses[vr::k_unMaxTrackedDeviceCount]; - uf::stl::string types[vr::k_unMaxTrackedDeviceCount]; - vr::IVRRenderModels* renderModels; - }; - extern UF_API Driver driver; - extern UF_API vr::IVRSystem* context; - extern UF_API uint8_t renderPass; - extern UF_API float width, height; extern UF_API bool enabled; - extern UF_API bool swapEyes; - extern UF_API uint8_t dominantEye; - bool UF_API initialize( int stage = 0 ); + bool UF_API initialize(); void UF_API tick(); void UF_API terminate(); void UF_API submit(); - void UF_API synchronize( bool async = true ); + void UF_API synchronize(); float UF_API predictedTimeToDisplay( float additional = 1 ); float UF_API updateTracking( float additional = 1 ); void UF_API recommendedResolution( uint32_t& width, uint32_t& height ); @@ -71,8 +57,10 @@ namespace ext { bool UF_API controllerActive( vr::Controller_Hand ); bool UF_API requestRenderModel( const uf::stl::string& ); - uf::Graphic& UF_API getRenderModel( const uf::stl::string& ); - uf::Graphic& UF_API controllerRenderModel( vr::Controller_Hand ); + uf::Mesh& UF_API getRenderModel( const uf::stl::string& ); + uf::Mesh& UF_API controllerRenderModel( vr::Controller_Hand ); + uf::Image& UF_API getRenderTexture( const uf::stl::string& ); + uf::Image& UF_API controllerRenderTexture( vr::Controller_Hand ); void UF_API resetPosition(); /* diff --git a/engine/inc/uf/ext/vulkan/rendermodes/vr.h b/engine/inc/uf/ext/vulkan/rendermodes/vr.h new file mode 100644 index 00000000..ed7d39b7 --- /dev/null +++ b/engine/inc/uf/ext/vulkan/rendermodes/vr.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace ext { + namespace vulkan { + struct UF_API VrRenderMode : public ext::vulkan::RenderMode { + virtual const uf::stl::string getType() const; + + virtual void createCommandBuffers( const uf::stl::vector& graphics ); + virtual void initialize( Device& device ); + virtual void build( bool = true ); + virtual void tick(); + virtual void destroy(); + virtual void render(); + }; + } +} \ No newline at end of file diff --git a/engine/inc/uf/ext/vulkan/rendertarget.h b/engine/inc/uf/ext/vulkan/rendertarget.h index fb798a65..68c26370 100644 --- a/engine/inc/uf/ext/vulkan/rendertarget.h +++ b/engine/inc/uf/ext/vulkan/rendertarget.h @@ -18,6 +18,7 @@ namespace ext { uint32_t width = 0; uint32_t height = 0; + uint8_t layers = 0; } descriptor; VkImage image; diff --git a/engine/inc/uf/utils/math/matrix/pod.inl b/engine/inc/uf/utils/math/matrix/pod.inl index c271f8fd..9ab5b70f 100644 --- a/engine/inc/uf/utils/math/matrix/pod.inl +++ b/engine/inc/uf/utils/math/matrix/pod.inl @@ -486,27 +486,27 @@ pod::Matrix4t /*UF_API*/ uf::matrix::orthographic( T l, T r, T b, T t ) { } template pod::Matrix4t /*UF_API*/ uf::matrix::perspective( T fov, T raidou, T znear, T zfar ) { - pod::Matrix4t m = uf::matrix::identity(); + pod::Matrix4t m = uf::matrix::identity(); T f = static_cast(1) / tan(static_cast(0.5) * fov); - m(0,0) = f / raidou; - m(1,1) = f; + m(0,0) = f / raidou; + m(1,1) = f; #if UF_USE_VULKAN - m(1,1) = -f; + m(1,1) = -f; #endif - m(3,2) = 1; - m(3,3) = 0; + m(3,2) = 1; + m(3,3) = 0; - if ( zfar <= 0 ) { - m(2,2) = 0; - m(2,3) = znear; - } else { - T range = zfar - znear; - m(2,2) = zfar / range; - m(2,3) = -(zfar * znear) / range; - } + if ( zfar <= 0 ) { + m(2,2) = 0; + m(2,3) = znear; + } else { + T range = zfar - znear; + m(2,2) = zfar / range; + m(2,3) = -(zfar * znear) / range; + } - return m; + return m; } template T& uf::matrix::copy( T& destination, const T& source ) { FOR_EACH(T::rows * T::columns, { diff --git a/engine/src/engine/ext/ext.cpp b/engine/src/engine/ext/ext.cpp index c39041dd..8d9f8cb5 100644 --- a/engine/src/engine/ext/ext.cpp +++ b/engine/src/engine/ext/ext.cpp @@ -48,6 +48,10 @@ #include #include +#if UF_USE_OPENVR +#include +#endif + bool uf::ready = false; uf::stl::vector uf::arguments; uf::Serializer uf::config; @@ -112,15 +116,47 @@ void UF_API uf::load() { uf::config.readFromFile(uf::io::root+"config.json"); } void UF_API uf::load( ext::json::Value& json ) { + auto& configWindowJson = json["window"]; + auto& configEngineJson = json["engine"]; + + auto& configEngineExtJson = configEngineJson["ext"]; + auto& configEngineSceneJson = configEngineJson["scenes"]; + auto& configEngineGraphJson = configEngineJson["graph"]; + auto& configEngineDebugJson = configEngineJson["debug"]; + auto& configEngineAudioJson = configEngineJson["audio"]; + auto& configEngineLimitersJson = configEngineJson["limiters"]; + auto& configEngineThreadJson = configEngineJson["threads"]; + auto& configEnginePhysicsJson = configEngineJson["physics"]; + auto& configEnginePhysicsSolverJson = configEnginePhysicsJson["solvers"]; + auto& configEnginePhysicsCorrectionJson = configEnginePhysicsJson["correction"]; + auto& configEnginePhysicsDebugDrawJson = configEnginePhysicsJson["debug draw"]; +#if UF_USE_FFX_FSR || UF_USE_FFX_SDK + auto& configEngineExtFfxJson = configEngineExtJson["fsr"]; +#endif +#if UF_USE_VALL_E + auto& configEngineExtValleJson = configEngineExtJson["vall_e"]; +#endif +#if UF_USE_ULTRALIGHT + auto& configEngineExtUltralightJson = configEngineExtJson["ultralight"]; +#endif +#if UF_USE_VULKAN + auto& configRenderJson = configEngineExtJson["vulkan"]; +#elif UF_USE_OPENGL + auto& configRenderJson = configEngineExtJson["opengl"]; +#else + auto& configRenderJson = configEngineExtJson["software"]; +#endif + auto& configRenderInvariantJson = configRenderJson["invariant"]; + auto& configRenderExperimentalJson = configRenderJson["experimental"]; + auto& configRenderPipelinesJson = configRenderJson["pipelines"]; + // Scene settings { - auto& configEngineSceneJson = json["engine"]["scenes"]; uf::matrix::reverseInfiniteProjection = configEngineSceneJson["matrix"]["reverseInfinite"].as( uf::matrix::reverseInfiniteProjection ); } // Graph settings { - auto& configEngineGraphJson = json["engine"]["graph"]; uf::graph::initialBufferElements = configEngineGraphJson["initial buffer elements"].as(uf::graph::initialBufferElements); if ( configEngineGraphJson["storage mode"].is() ) { auto mode = uf::string::lowercase( configEngineGraphJson["storage mode"].as() ); @@ -135,15 +171,14 @@ void UF_API uf::load( ext::json::Value& json ) { // Various debug settings { - auto& configEngineDebugJson = json["engine"]["debug"]; - /*global*/::config.engine.gc.enabled = configEngineDebugJson["garbage collection"]["enabled"].as(/*global*/::config.engine.gc.enabled); - /*global*/::config.engine.gc.every = configEngineDebugJson["garbage collection"]["every"].as(/*global*/::config.engine.gc.every); - /*global*/::config.engine.gc.mode = configEngineDebugJson["garbage collection"]["mode"].as(/*global*/::config.engine.gc.mode); - /*global*/::config.engine.gc.announce = configEngineDebugJson["garbage collection"]["announce"].as(/*global*/::config.engine.gc.announce); + ::config.engine.gc.enabled = configEngineDebugJson["garbage collection"]["enabled"].as(::config.engine.gc.enabled); + ::config.engine.gc.every = configEngineDebugJson["garbage collection"]["every"].as(::config.engine.gc.every); + ::config.engine.gc.mode = configEngineDebugJson["garbage collection"]["mode"].as(::config.engine.gc.mode); + ::config.engine.gc.announce = configEngineDebugJson["garbage collection"]["announce"].as(::config.engine.gc.announce); - /*global*/::config.engine.limiter.print = configEngineDebugJson["framerate"]["print"].as(/*global*/::config.engine.limiter.print); - /*global*/::config.engine.fps.print = configEngineDebugJson["framerate"]["print"].as(/*global*/::config.engine.fps.print); - /*global*/::config.engine.fps.every = configEngineDebugJson["framerate"]["every"].as(/*global*/::config.engine.fps.every); + ::config.engine.limiter.print = configEngineDebugJson["framerate"]["print"].as(::config.engine.limiter.print); + ::config.engine.fps.print = configEngineDebugJson["framerate"]["print"].as(::config.engine.fps.print); + ::config.engine.fps.every = configEngineDebugJson["framerate"]["every"].as(::config.engine.fps.every); uf::Entity::deleteChildrenOnDestroy = configEngineDebugJson["entity"]["delete children on destroy"].as( uf::Entity::deleteChildrenOnDestroy ); uf::Entity::deleteComponentsOnDestroy = configEngineDebugJson["entity"]["delete components on destroy"].as( uf::Entity::deleteComponentsOnDestroy ); @@ -160,18 +195,17 @@ void UF_API uf::load( ext::json::Value& json ) { } // Limiter settings { - auto& configEngineLimitersJson = json["engine"]["limiters"]; - if ( configEngineLimitersJson["framerate"].as() == "auto" && json["window"]["refresh rate"].is() ) { + if ( configEngineLimitersJson["framerate"].as() == "auto" && configWindowJson["refresh rate"].is() ) { float scale = 1.0; - size_t refreshRate = json["window"]["refresh rate"].as(); + size_t refreshRate = configWindowJson["refresh rate"].as(); configEngineLimitersJson["framerate"] = refreshRate * scale; UF_MSG_DEBUG("Setting framerate cap to {}", (int) refreshRate * scale); } /* Frame limiter */ { size_t limit = configEngineLimitersJson["framerate"].as(); - /*global*/::times.limiter = limit != 0 ? 1.0 / limit : 0; - UF_MSG_DEBUG("Limiter set to {} ms", /*global*/::times.limiter); + ::times.limiter = limit != 0 ? 1.0 / limit : 0; + UF_MSG_DEBUG("Limiter set to {} ms", ::times.limiter); } /* Max delta time */{ size_t limit = configEngineLimitersJson["deltaTime"].as(); @@ -181,10 +215,9 @@ void UF_API uf::load( ext::json::Value& json ) { // Thread settings { - auto& configEngineThreadJson = json["engine"]["threads"]; - if ( configEngineThreadJson["frame limiter"].as() == "auto" && json["window"]["refresh rate"].is() ) { + if ( configEngineThreadJson["frame limiter"].as() == "auto" && configWindowJson["refresh rate"].is() ) { float scale = 2.0; - size_t refreshRate = json["window"]["refresh rate"].as(); + size_t refreshRate = configWindowJson["refresh rate"].as(); configEngineThreadJson["frame limiter"] = refreshRate * scale; UF_MSG_DEBUG("Setting thread frame limiter to {}", (int) refreshRate * scale); } @@ -209,14 +242,12 @@ void UF_API uf::load( ext::json::Value& json ) { // Physics settings { - auto& configEnginePhysicsJson = json["engine"]["physics"]; uf::physics::settings.async = configEnginePhysicsJson["async"].as(uf::physics::settings.async); uf::physics::settings.timestep = configEnginePhysicsJson["timestep"].as(uf::physics::settings.timestep); uf::physics::settings.fixedStep = configEnginePhysicsJson["fixed step"].as(uf::physics::settings.fixedStep); uf::physics::settings.substeps = configEnginePhysicsJson["substeps"].as(uf::physics::settings.substeps); uf::physics::settings.flattenTransforms = configEnginePhysicsJson["flatten transforms"].as(uf::physics::settings.flattenTransforms); - auto& configEnginePhysicsSolverJson = configEnginePhysicsJson["solvers"]; if ( ext::json::isObject( configEnginePhysicsSolverJson ) ) { uf::physics::settings.useGjk = configEnginePhysicsSolverJson["gjk"].as(uf::physics::settings.useGjk); uf::physics::settings.blockContactSolver = configEnginePhysicsSolverJson["block"].as(uf::physics::settings.blockContactSolver); @@ -224,14 +255,12 @@ void UF_API uf::load( ext::json::Value& json ) { uf::physics::settings.resolveBlockContact = configEnginePhysicsSolverJson["resolve invalid"].as(uf::physics::settings.resolveBlockContact); uf::physics::settings.solverIterations = configEnginePhysicsSolverJson["iterations"].as(uf::physics::settings.solverIterations); } - auto& configEnginePhysicsCorrectionJson = configEnginePhysicsJson["correction"]; if ( ext::json::isObject( configEnginePhysicsCorrectionJson ) ) { uf::physics::settings.ngsPositionSolver = configEnginePhysicsCorrectionJson["ngs"].as(uf::physics::settings.ngsPositionSolver); uf::physics::settings.baumgarteCorrectionPercent = configEnginePhysicsCorrectionJson["percent"].as(uf::physics::settings.baumgarteCorrectionPercent); uf::physics::settings.baumgarteCorrectionSlop = configEnginePhysicsCorrectionJson["slop"].as(uf::physics::settings.baumgarteCorrectionSlop); uf::physics::settings.maxLinearCorrection = configEnginePhysicsCorrectionJson["max"].as(uf::physics::settings.maxLinearCorrection); } - auto& configEnginePhysicsDebugDrawJson = configEnginePhysicsJson["debug draw"]; if ( ext::json::isObject( configEnginePhysicsDebugDrawJson ) ) { if ( configEnginePhysicsDebugDrawJson["static"].as() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_STATIC; if ( configEnginePhysicsDebugDrawJson["dynamic"].as() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_DYNAMIC; @@ -257,7 +286,6 @@ void UF_API uf::load( ext::json::Value& json ) { // Audio settings { - auto& configEngineAudioJson = json["engine"]["audio"]; uf::audio::muted = configEngineAudioJson["mute"].as( uf::audio::muted ); uf::audio::asyncUpdate = configEngineAudioJson["async update"].as( uf::audio::asyncUpdate ); uf::audio::streamsByDefault = configEngineAudioJson["streams by default"].as( uf::audio::streamsByDefault ); @@ -278,23 +306,29 @@ void UF_API uf::load( ext::json::Value& json ) { } // Various external settings +#if UF_USE_DISCORD { - /*global*/::config.engine.ext.discord.enabled = json["engine"]["ext"]["discord"]["enabled"].as(/*global*/::config.engine.ext.discord.enabled); - /*global*/::config.engine.ext.imgui.enabled = json["engine"]["ext"]["imgui"]["enabled"].as(/*global*/::config.engine.ext.imgui.enabled); + ::config.engine.ext.discord.enabled = configEngineExtJson["discord"]["enabled"].as(::config.engine.ext.discord.enabled); } +#endif +#if UF_USE_IMGUI + { + ::config.engine.ext.imgui.enabled = configEngineExtJson["imgui"]["enabled"].as(::config.engine.ext.imgui.enabled); + } +#endif +#if UF_USE_VALL_E // VALL-E settings { - auto& configEngineExtValleJson = json["engine"]["ext"]["vall_e"]; - /*global*/::config.engine.ext.vall_e.enabled = configEngineExtValleJson["enabled"].as(/*global*/::config.engine.ext.vall_e.enabled); - /*global*/::config.engine.ext.vall_e.model_path = configEngineExtValleJson["model_path"].as(/*global*/::config.engine.ext.vall_e.model_path); - /*global*/::config.engine.ext.vall_e.encodec_path = configEngineExtValleJson["encodec_path"].as(/*global*/::config.engine.ext.vall_e.encodec_path); + ::config.engine.ext.vall_e.enabled = configEngineExtValleJson["enabled"].as(::config.engine.ext.vall_e.enabled); + ::config.engine.ext.vall_e.model_path = configEngineExtValleJson["model_path"].as(::config.engine.ext.vall_e.model_path); + ::config.engine.ext.vall_e.encodec_path = configEngineExtValleJson["encodec_path"].as(::config.engine.ext.vall_e.encodec_path); } +#endif #if UF_USE_ULTRALIGHT // Ultralight settings (painfully unused) { - auto& configEngineExtUltralightJson = json["engine"]["ext"]["ultralight"]; - /*global*/::config.engine.ext.ultralight.enabled = configEngineExtUltralightJson["enabled"].as(/*global*/::config.engine.ext.ultralight.enabled); + ::config.engine.ext.ultralight.enabled = configEngineExtUltralightJson["enabled"].as(::config.engine.ext.ultralight.enabled); ext::ultralight::scale = configEngineExtUltralightJson["scale"].as( ext::ultralight::scale ); ext::ultralight::log = configEngineExtUltralightJson["log"].as( ext::ultralight::log ); } @@ -302,17 +336,6 @@ void UF_API uf::load( ext::json::Value& json ) { // Renderer settings { - #if UF_USE_VULKAN - auto& configRenderJson = json["engine"]["ext"]["vulkan"]; - #elif UF_USE_OPENGL - auto& configRenderJson = json["engine"]["ext"]["opengl"]; - #else - auto& configRenderJson = json["engine"]["ext"]["software"]; - #endif - auto& configRenderInvariantJson = configRenderJson["invariant"]; - auto& configRenderExperimentalJson = configRenderJson["experimental"]; - auto& configRenderPipelinesJson = configRenderJson["pipelines"]; - uf::renderer::settings::validation::messages = configRenderJson["validation"]["messages"].as( uf::renderer::settings::validation::messages ); uf::renderer::settings::validation::checkpoints = configRenderJson["validation"]["checkpoints"].as( uf::renderer::settings::validation::checkpoints ); @@ -323,7 +346,7 @@ void UF_API uf::load( ext::json::Value& json ) { uf::renderer::settings::defaultDeferBufferDestroy = configRenderInvariantJson["default defer buffer destroy"].as( uf::renderer::settings::defaultDeferBufferDestroy ); #if 0 uf::renderer::settings::defaultCommandBufferImmediate = true; - /*global*/::requestDeferredCommandBufferSubmit = !configRenderInvariantJson["default command buffer immediate"].as( uf::renderer::settings::defaultCommandBufferImmediate ); + ::requestDeferredCommandBufferSubmit = !configRenderInvariantJson["default command buffer immediate"].as( uf::renderer::settings::defaultCommandBufferImmediate ); #else uf::renderer::settings::defaultCommandBufferImmediate = configRenderInvariantJson["default command buffer immediate"].as( uf::renderer::settings::defaultCommandBufferImmediate ); #endif @@ -331,7 +354,7 @@ void UF_API uf::load( ext::json::Value& json ) { #endif #if 1 uf::renderer::settings::experimental::dedicatedThread = false; - /*global*/::requestDedicatedRenderThread = configRenderExperimentalJson["dedicated thread"].as( uf::renderer::settings::experimental::dedicatedThread ); + ::requestDedicatedRenderThread = configRenderExperimentalJson["dedicated thread"].as( uf::renderer::settings::experimental::dedicatedThread ); #else uf::renderer::settings::experimental::dedicatedThread = configRenderExperimentalJson["dedicated thread"].as( uf::renderer::settings::experimental::dedicatedThread ); #endif @@ -340,40 +363,44 @@ void UF_API uf::load( ext::json::Value& json ) { uf::renderer::settings::invariant::individualPipelines = configRenderInvariantJson["individual pipelines"].as( uf::renderer::settings::invariant::individualPipelines ); } #if UF_USE_FFX_FSR || UF_USE_FFX_SDK - ext::fsr::preset = json["engine"]["ext"]["fsr"]["preset"].as(ext::fsr::preset); - ext::fsr::jitterScale = json["engine"]["ext"]["fsr"]["jitter scale"].as(ext::fsr::jitterScale); - ext::fsr::sharpness = json["engine"]["ext"]["fsr"]["sharpness"].as(ext::fsr::sharpness); + ext::fsr::preset = configEngineExtFfxJson["preset"].as(ext::fsr::preset); + ext::fsr::jitterScale = configEngineExtFfxJson["jitter scale"].as(ext::fsr::jitterScale); + ext::fsr::sharpness = configEngineExtFfxJson["sharpness"].as(ext::fsr::sharpness); - ext::fsr::frameUpscale = json["engine"]["ext"]["fsr"]["upscale"].as(ext::fsr::frameUpscale); - ext::fsr::frameInterpolation = json["engine"]["ext"]["fsr"]["interpolation"].as(ext::fsr::frameInterpolation); + ext::fsr::frameUpscale = configEngineExtFfxJson["upscale"].as(ext::fsr::frameUpscale); + ext::fsr::frameInterpolation = configEngineExtFfxJson["interpolation"].as(ext::fsr::frameInterpolation); - { - bool enabled = json["engine"]["ext"]["fsr"]["enabled"].as(true); - if ( !enabled ) { - ext::fsr::frameUpscale = false; - ext::fsr::frameInterpolation = false; - } + if ( !configEngineExtFfxJson["enabled"].as(true) ) { + ext::fsr::frameUpscale = false; + ext::fsr::frameInterpolation = false; } #endif +#if 0 && UF_USE_OPENVR + static auto* vrRenderMode = new ext::vulkan::VrRenderMode; + uf::renderer::addRenderMode(vrRenderMode, "VR"); +#endif + + // shouldn't ever fire because the deferred rendermode is owned by the scene now if ( uf::renderer::hasRenderMode("", true) ) { auto& renderMode = uf::renderer::getRenderMode("", true); #if UF_USE_FFX_FSR || UF_USE_FFX_SDK if ( uf::renderer::settings::pipelines::fsr ) { + float factor = 1.0f; auto mode = uf::string::lowercase( ext::fsr::preset ); - if ( mode == "native" ) renderMode.scale = 1; - else if ( mode == "quality" ) renderMode.scale = 1.0f / (1.5f); - else if ( mode == "balanced" ) renderMode.scale = 1.0f / (1.7f); - else if ( mode == "performance" ) renderMode.scale = 1.0f / (2.0f); - else if ( mode == "ultra" ) renderMode.scale = 1.0f / (3.0f); + if ( mode == "native" ) factor = 1.0f; + else if ( mode == "quality" ) factor = 1.5f; + else if ( mode == "balanced" ) factor = 1.7f; + else if ( mode == "performance" ) factor = 2.0f; + else if ( mode == "ultra" ) factor = 3.0f; else { - renderMode.scale = 1; UF_MSG_WARNING("Invalid FFX FSR preset enum string specified: {}", mode); } + renderMode.scale = 1.0f / factor; UF_MSG_DEBUG("Using FFX FSR Preset: {} ({:.3f}% render scale)", mode, (100.0f / renderMode.scale)); } else #endif - renderMode.scale = uf::config["engine"]["ext"]["vulkan"]["framebuffer"]["size"].as(1.0f); + renderMode.scale = configRenderJson["framebuffer"]["size"].as(1.0f); UF_MSG_DEBUG("Geometry render scale: {:.3f}", renderMode.scale); } } @@ -434,11 +461,11 @@ void UF_API uf::initialize() { std::srand(std::time(NULL)); } /* Open output file */ { - /*global*/::io.filenames.output = uf::io::root+"/logs/output.txt"; - /*global*/::io.output.open(/*global*/::io.filenames.output); + ::io.filenames.output = uf::io::root+"/logs/output.txt"; + ::io.output.open(::io.filenames.output); } /* Initialize timers */ { - /*global*/::times.sys.start(); + ::times.sys.start(); } /* Read persistent data */ { // #include "./inits/persistence.inl" @@ -495,6 +522,7 @@ void UF_API uf::initialize() { } uf::allocator::override = configMemoryPoolJson["override"].as( uf::allocator::override ); } + /* Setup commands */ { uf::console::initialize(); } @@ -507,10 +535,9 @@ void UF_API uf::initialize() { } /* Create initial scene (kludge) */ { - uf::Scene& scene = uf::instantiator::instantiate(); //new uf::Scene; + uf::Scene& scene = uf::instantiator::instantiate(); uf::scene::scenes.emplace_back(&scene); auto& metadata = scene.getComponent(); - // metadata["system"]["config"] = uf::config; } uf::load( uf::config ); @@ -539,7 +566,7 @@ void UF_API uf::initialize() { } else if ( ext::json::isArray( configRenderJson["framebuffer"]["size"] ) ) { uf::renderer::settings::width = configRenderJson["framebuffer"]["size"][0].as(uf::renderer::settings::width); uf::renderer::settings::height = configRenderJson["framebuffer"]["size"][1].as(uf::renderer::settings::height); - uf::stl::string filter = uf::string::lowercase( configRenderJson["framebuffer"]["size"][2].as() ); + uf::stl::string filter = uf::string::lowercase( configRenderJson["framebuffer"]["size"][2].as() ); if ( filter == "nearest" ) uf::renderer::settings::swapchainUpscaleFilter = uf::renderer::enums::Filter::NEAREST; else if ( filter == "linear" ) uf::renderer::settings::swapchainUpscaleFilter = uf::renderer::enums::Filter::LINEAR; @@ -607,12 +634,9 @@ void UF_API uf::initialize() { #if UF_USE_FFX_FSR || UF_USE_FFX_SDK uf::renderer::settings::pipelines::fsr = configRenderPipelinesJson["fsr"].as( uf::renderer::settings::pipelines::fsr ); - // ext::fsr::enabled = uf::renderer::settings::pipelines::fsr; #endif if ( uf::renderer::settings::pipelines::rt ) { - // uf::renderer::settings::pipelines::vxgi = false; - // uf::renderer::settings::pipelines::culling = false; uf::config["engine"]["scenes"]["lights"]["shadows"]["enabled"] = false; } #define JSON_TO_FORMAT( key ) if ( configRenderJson["formats"][#key].is() ) {\ @@ -645,110 +669,13 @@ void UF_API uf::initialize() { } #endif - /* Physics */ { - // uf::physics::initialize(); - } - #if UF_USE_OPENVR { auto& configVrJson = uf::config["engine"]["ext"]["vr"]; - ext::openvr::enabled = configVrJson["enable"].as( ext::openvr::enabled ); - ext::openvr::swapEyes = configVrJson["swap eyes"].as( ext::openvr::swapEyes ); - - - if ( configVrJson["dominant eye"].is() ) { - ext::openvr::dominantEye = configVrJson["dominant eye"].as(); - } else if ( configVrJson["dominant eye"].as() == "left" ) ext::openvr::dominantEye = 0; - else if ( configVrJson["dominant eye"].as() == "right" ) ext::openvr::dominantEye = 1; - - ext::openvr::driver.manifest = configVrJson["manifest"].as(ext::openvr::driver.manifest); - - if ( ext::openvr::enabled ) uf::config["engine"]["render modes"]["stereo deferred"] = true; - } -#endif - - /* Initialize Vulkan */ { - // to-do: have this set per config value instead of relying on the scene to handle its own rendermodes - if ( false ) { - // setup render mode - if ( uf::config["engine"]["render modes"]["deferred"].as(true) ) { - auto* renderMode = new uf::renderer::DeferredRenderMode; - - if ( uf::config["engine"]["ext"]["vulkan"]["pipelines"]["postProcess"].is() ) - renderMode->metadata.json["postProcess"] = uf::config["engine"]["ext"]["vulkan"]["pipelines"]["postProcess"]; - - renderMode->blitter.descriptor.renderMode = "Swapchain"; - renderMode->blitter.descriptor.subpass = 0; - #if UF_USE_FFX_FSR || UF_USE_FFX_SDK - if ( uf::renderer::settings::pipelines::fsr ) { - auto mode = uf::string::lowercase( ext::fsr::preset ); - if ( mode == "native" ) renderMode->scale = 1; - else if ( mode == "quality" ) renderMode->scale = 1.0f / (1.5f); - else if ( mode == "balanced" ) renderMode->scale = 1.0f / (1.7f); - else if ( mode == "performance" ) renderMode->scale = 1.0f / (2.0f); - else if ( mode == "ultra" ) renderMode->scale = 1.0f / (3.0f); - else { - renderMode->scale = 1; - UF_MSG_WARNING("Invalid FFX FSR preset enum string specified: {}", mode); - } - UF_MSG_DEBUG("Using FFX FSR Preset: {} ({:.3f}% render scale)", mode, (100.0f / renderMode->scale)); - } else - #endif - renderMode->scale = uf::config["engine"]["ext"]["vulkan"]["framebuffer"]["size"].as(1.0f); - UF_MSG_DEBUG("Geometry render scale: {:.3f}", renderMode->scale); - - if ( uf::config["engine"]["render modes"]["stereo deferred"].as() ) { - renderMode->metadata.eyes = 2; - } - #if UF_USE_VULKAN - if ( uf::renderer::settings::pipelines::deferred ) { - renderMode->metadata.pipelines.emplace_back(uf::renderer::settings::pipelines::names::deferred); - } - if ( uf::renderer::settings::pipelines::culling ) { - renderMode->metadata.pipelines.emplace_back(uf::renderer::settings::pipelines::names::culling); - } - if ( uf::renderer::settings::pipelines::rt ) { - renderMode->metadata.pipelines.emplace_back("skinning"); - } - #endif - - uf::renderer::addRenderMode( renderMode, "" ); - } - - if ( uf::config["engine"]["render modes"]["gui"].as(true) ) { - auto* renderMode = new uf::renderer::RenderTargetRenderMode; - if ( uf::config["engine"]["ext"]["vulkan"]["pipelines"]["postProcess"].is() ) - renderMode->metadata.json["postProcess"] = uf::config["engine"]["ext"]["vulkan"]["pipelines"]["postProcess"]; - - uf::stl::string name = "Gui"; - renderMode->blitter.descriptor.renderMode = "Swapchain"; - renderMode->blitter.descriptor.subpass = 0; - renderMode->metadata.type = "single"; - uf::renderer::addRenderMode( renderMode, name ); - } - } - - } -#if UF_USE_OPENVR - if ( ext::openvr::enabled ) { - ext::openvr::initialize(); - - uint32_t width, height; - ext::openvr::recommendedResolution( width, height ); - - auto& renderMode = uf::renderer::getRenderMode("", true); - renderMode.width = width; - renderMode.height = height; - - UF_MSG_DEBUG("Recommended VR Resolution: {}, {}", renderMode.width, renderMode.height); - - if ( uf::config["engine"]["ext"]["vr"]["scale"].is() ) { - float scale = uf::config["engine"]["ext"]["vr"]["scale"].as(); - renderMode.width *= scale; - renderMode.height *= scale; - - UF_MSG_DEBUG("VR Resolution: {}, {}", renderMode.width, renderMode.height); + if ( ext::openvr::enabled && (ext::openvr::enabled = ext::openvr::initialize())) { + ext::openvr::recommendedResolution( uf::renderer::settings::width, uf::renderer::settings::height ); + UF_MSG_DEBUG("VR Resolution: {}, {}", uf::renderer::settings::width, uf::renderer::settings::height); } } #endif @@ -770,23 +697,23 @@ void UF_API uf::initialize() { } #endif #if UF_USE_DISCORD - /* Discord */ if ( /*global*/::config.engine.ext.discord.enabled ) { + /* Discord */ if ( ::config.engine.ext.discord.enabled ) { ext::discord::initialize(); } #endif #if UF_USE_ULTRALIGHT - /* Ultralight-UX */ if ( /*global*/::config.engine.ext.ultralight.enabled ) { + /* Ultralight-UX */ if ( ::config.engine.ext.ultralight.enabled ) { ext::ultralight::initialize(); } #endif #if UF_USE_IMGUI - if ( /*global*/::config.engine.ext.imgui.enabled ) { + if ( ::config.engine.ext.imgui.enabled ) { // ext::imgui::initialize(); } #endif #if 0 && UF_USE_VALL_E - if ( /*global*/::config.engine.ext.vall_e.enabled ) { - ext::vall_e::initialize( /*global*/::config.engine.ext.vall_e.model_path, /*global*/::config.engine.ext.vall_e.encodec_path ); + if ( ::config.engine.ext.vall_e.enabled ) { + ext::vall_e::initialize( ::config.engine.ext.vall_e.model_path, ::config.engine.ext.vall_e.encodec_path ); // bind the hook uf::hooks.addHook( "llm:VALL-E.synthesize", [&](ext::json::Value& json){ @@ -811,8 +738,8 @@ void UF_API uf::initialize() { #endif /* Add hooks */ { uf::hooks.addHook( "game:Scene.Load", [&](ext::json::Value& json){ - /*global*/::sceneTransition.payload = json; - /*global*/::sceneTransition.phase = 0; + ::sceneTransition.payload = json; + ::sceneTransition.phase = 0; }); uf::hooks.addHook( "system:Quit", [&](ext::json::Value& json){ if ( json["message"].is() ) { @@ -825,12 +752,12 @@ void UF_API uf::initialize() { /* Initialize root scene*/ { ext::json::Value payload; payload["scene"] = uf::config["engine"]["scenes"]["start"]; - /*global*/::sceneTransition.payload = payload; - /*global*/::sceneTransition.phase = 0; + ::sceneTransition.payload = payload; + ::sceneTransition.phase = 0; } uf::ready = true; - UF_MSG_INFO("EXT took {} seconds to initialize", /*global*/::times.sys.elapsed().asDouble()); + UF_MSG_INFO("EXT took {} seconds to initialize", ::times.sys.elapsed().asDouble()); } void UF_API uf::tick() { @@ -843,9 +770,9 @@ void UF_API uf::tick() { #if 1 // skip the next tick to load the next scene to ensure nothing's happening - if ( /*global*/::sceneTransition.phase >= 0 ) { - auto target = /*global*/::sceneTransition.payload["scene"].as(); - auto& phase = /*global*/::sceneTransition.phase; + if ( ::sceneTransition.phase >= 0 ) { + auto target = ::sceneTransition.payload["scene"].as(); + auto& phase = ::sceneTransition.phase; ++phase; @@ -856,12 +783,12 @@ void UF_API uf::tick() { uf::renderer::synchronize(); if ( uf::renderer::settings::experimental::dedicatedThread ) { - /*global*/::requestDedicatedRenderThread = true; + ::requestDedicatedRenderThread = true; uf::renderer::settings::experimental::dedicatedThread = !uf::renderer::settings::experimental::dedicatedThread; } #if UF_USE_VULKAN if ( !uf::renderer::settings::defaultCommandBufferImmediate ) { - /*global*/::requestDeferredCommandBufferSubmit = true; + ::requestDeferredCommandBufferSubmit = true; uf::renderer::settings::defaultCommandBufferImmediate = !uf::renderer::settings::defaultCommandBufferImmediate; } #endif @@ -869,7 +796,7 @@ void UF_API uf::tick() { uf::scene::unloadScene(); uf::scene::loadScene( target ); - /*global*/::sceneTransition.phase = -1; + ::sceneTransition.phase = -1; #if UF_USE_VULKAN uf::renderer::flushCommandBuffers(); @@ -884,7 +811,7 @@ void UF_API uf::tick() { spec::controller::tick(); } #if UF_USE_OPENVR - /* OpenVR */ if ( ext::openvr::context ) { + /* OpenVR */ if ( ext::openvr::enabled ) { ext::openvr::tick(); } #endif @@ -944,7 +871,7 @@ void UF_API uf::tick() { uf::thread::process( threadMain ); } #if UF_USE_ULTRALIGHT - /* Ultralight-UX */ if ( /*global*/::config.engine.ext.ultralight.enabled ) { + /* Ultralight-UX */ if ( ::config.engine.ext.ultralight.enabled ) { ext::ultralight::tick(); } #endif @@ -953,22 +880,22 @@ void UF_API uf::tick() { } #if UF_USE_DISCORD - /* Discord */ if ( /*global*/::config.engine.ext.discord.enabled ) { + /* Discord */ if ( ::config.engine.ext.discord.enabled ) { ext::discord::tick(); } #endif #if UF_USE_IMGUI - if ( /*global*/::config.engine.ext.imgui.enabled ) { + if ( ::config.engine.ext.imgui.enabled ) { ext::imgui::tick(); } #endif // perform GC on entities - if ( /*global*/::config.engine.gc.enabled ) { - TIMER( /*global*/::config.engine.gc.every ) { - size_t collected = uf::instantiator::collect( /*global*/::config.engine.gc.mode ); + if ( ::config.engine.gc.enabled ) { + TIMER( ::config.engine.gc.every ) { + size_t collected = uf::instantiator::collect( ::config.engine.gc.mode ); if ( collected > 0 ) { - if ( /*global*/::config.engine.gc.announce ) UF_MSG_DEBUG("GC collected {} unused entities", (int) collected); + if ( ::config.engine.gc.announce ) UF_MSG_DEBUG("GC collected {} unused entities", (int) collected); } } } @@ -978,10 +905,10 @@ void UF_API uf::tick() { #endif #if !UF_ENV_DREAMCAST - if ( /*global*/::times.limiter > 0 ) { + if ( ::times.limiter > 0 ) { static auto nextFrameTime = std::chrono::steady_clock::now(); - double limiterMs = /*global*/::times.limiter * 1000.0; + double limiterMs = ::times.limiter * 1000.0; auto limiterDuration = std::chrono::duration_cast(std::chrono::duration(limiterMs)); nextFrameTime += limiterDuration; @@ -996,14 +923,14 @@ void UF_API uf::tick() { } auto& controller = uf::scene::getCurrentScene().getController(); - if ( /*global*/::requestDedicatedRenderThread && controller.getName() == "Player" ) { - /*global*/::requestDedicatedRenderThread = false; + if ( ::requestDedicatedRenderThread && controller.getName() == "Player" ) { + ::requestDedicatedRenderThread = false; uf::renderer::settings::experimental::dedicatedThread = true; UF_MSG_DEBUG("Dedicated render requested"); } #if UF_USE_VULKAN - if ( /*global*/::requestDeferredCommandBufferSubmit && controller.getName() == "Player" ) { - /*global*/::requestDeferredCommandBufferSubmit = false; + if ( ::requestDeferredCommandBufferSubmit && controller.getName() == "Player" ) { + ::requestDeferredCommandBufferSubmit = false; uf::renderer::settings::defaultCommandBufferImmediate = false; UF_MSG_DEBUG("Defer command buffer submit requested"); } @@ -1022,11 +949,11 @@ void UF_API uf::tick() { threadMain.metrics.totalFrameTimeMs.store(frameTime.count(), std::memory_order_relaxed); #endif - /* FPS Print */ if ( /*global*/::config.engine.fps.print ) { - ++/*global*/::times.frames; - ++/*global*/::times.total.frames; - TIMER( /*global*/::config.engine.fps.every ) { - UF_MSG_DEBUG("System: {:.3f} ms/frame | Time: {:.3f} | Frames: {} | FPS: {:.3f}", (time * 1000.0 / /*global*/::times.frames), time, /*global*/::times.frames, /*global*/::times.frames / time); + /* FPS Print */ if ( ::config.engine.fps.print ) { + ++::times.frames; + ++::times.total.frames; + TIMER( ::config.engine.fps.every ) { + UF_MSG_DEBUG("System: {:.3f} ms/frame | Time: {:.3f} | Frames: {} | FPS: {:.3f}", (time * 1000.0 / ::times.frames), time, ::times.frames, ::times.frames / time); #if UF_ENV_DREAMCAST DC_STATS(); #endif @@ -1035,25 +962,25 @@ void UF_API uf::tick() { for ( auto& [ name, stats ] : metrics ) UF_MSG_DEBUG("Thread {}: active={}, idle={}, total={}, tasks={}", name, std::get<0>(stats), std::get<1>(stats), std::get<2>(stats), std::get<3>(stats) ); #endif - /*global*/::times.frames = 0; + ::times.frames = 0; } } } void UF_API uf::render() { if ( uf::scene::scenes.empty() ) return; - if ( /*global*/::sceneTransition.phase >= 0 ) { + if ( ::sceneTransition.phase >= 0 ) { return; } #if UF_USE_ULTRALIGHT - /* Ultralight-UX */ if ( /*global*/::config.engine.ext.ultralight.enabled ) { + /* Ultralight-UX */ if ( ::config.engine.ext.ultralight.enabled ) { ext::ultralight::render(); } #endif #if UF_USE_OPENVR - if ( ext::openvr::context ) { - vr::VRCompositor()->SubmitExplicitTimingData(); + { + ext::openvr::synchronize(); } #endif /* Render scene */ { @@ -1061,8 +988,7 @@ void UF_API uf::render() { uf::renderer::render(); } #if UF_USE_OPENVR - if ( ext::openvr::context ) { - ext::openvr::synchronize(); + if ( !uf::renderer::hasRenderMode("VR") ) { ext::openvr::submit(); } #endif @@ -1075,22 +1001,22 @@ void UF_API uf::terminate() { spec::controller::terminate(); } #if UF_USE_VALL_E - if ( /*global*/::config.engine.ext.vall_e.enabled ) { + if ( ::config.engine.ext.vall_e.enabled ) { ext::vall_e::terminate(); } #endif #if UF_USE_IMGUI - if ( /*global*/::config.engine.ext.imgui.enabled ) { + if ( ::config.engine.ext.imgui.enabled ) { ext::imgui::terminate(); } #endif #if UF_USE_ULTRALIGHT - /* Ultralight-UX */ if ( /*global*/::config.engine.ext.ultralight.enabled ) { + /* Ultralight-UX */ if ( ::config.engine.ext.ultralight.enabled ) { ext::ultralight::terminate(); } #endif #if UF_USE_OPENVR - /* OpenVR */ if ( ext::openvr::context ) { + /* OpenVR */ if ( ext::openvr::enabled ) { ext::openvr::terminate(); } #endif @@ -1113,10 +1039,10 @@ void UF_API uf::terminate() { { uf::scene::destroy(); } - /* Garbage collection */ if ( /*global*/::config.engine.gc.enabled ) { - size_t collected = uf::instantiator::collect( /*global*/::config.engine.gc.mode ); + /* Garbage collection */ if ( ::config.engine.gc.enabled ) { + size_t collected = uf::instantiator::collect( ::config.engine.gc.mode ); if ( collected > 0 ) { - if ( /*global*/::config.engine.gc.announce ) UF_MSG_DEBUG("GC collected {} unused entities", (int) collected); + if ( ::config.engine.gc.announce ) UF_MSG_DEBUG("GC collected {} unused entities", (int) collected); } } @@ -1144,14 +1070,14 @@ void UF_API uf::terminate() { } /* Print system stats */ { - /*global*/::times.total.time = /*global*/::times.sys.elapsed().asDouble(); - UF_MSG_DEBUG("System: Total Time: {} | Total Frames: {} | Average FPS: {}", /*global*/::times.total.time, /*global*/::times.total.frames, /*global*/::times.total.frames / /*global*/::times.total.time); + ::times.total.time = ::times.sys.elapsed().asDouble(); + UF_MSG_DEBUG("System: Total Time: {} | Total Frames: {} | Average FPS: {}", ::times.total.time, ::times.total.frames, ::times.total.frames / ::times.total.time); } /* Flush input buffer */ { - /*global*/::io.output << /*global*/::io.input << "\n"; - for ( const auto& str : uf::iostream.getHistory() ) /*global*/::io.output << str << "\n"; - /*global*/::io.output << "\nTerminated after " << /*global*/::times.sys.elapsed().asDouble() << " seconds" << "\n"; - /*global*/::io.output.close(); + ::io.output << ::io.input << "\n"; + for ( const auto& str : uf::iostream.getHistory() ) ::io.output << str << "\n"; + ::io.output << "\nTerminated after " << ::times.sys.elapsed().asDouble() << " seconds" << "\n"; + ::io.output.close(); } } \ No newline at end of file diff --git a/engine/src/engine/ext/gui/manager/behavior.cpp b/engine/src/engine/ext/gui/manager/behavior.cpp index f94b5fb5..d7cb5b51 100644 --- a/engine/src/engine/ext/gui/manager/behavior.cpp +++ b/engine/src/engine/ext/gui/manager/behavior.cpp @@ -52,6 +52,7 @@ void ext::GuiManagerBehavior::tick( uf::Object& self ) { renderMode.blitter.descriptor.subpass = 0; renderMode.metadata.type = "single"; renderMode.metadata.name = name; + renderMode.metadata.json["vr"] = true; if ( uf::renderer::settings::experimental::registerRenderMode ) uf::renderer::addRenderMode( &renderMode, name ); metadata.boundGui = true; diff --git a/engine/src/engine/ext/scene/behavior.cpp b/engine/src/engine/ext/scene/behavior.cpp index aea38229..5ae7ef94 100644 --- a/engine/src/engine/ext/scene/behavior.cpp +++ b/engine/src/engine/ext/scene/behavior.cpp @@ -28,6 +28,7 @@ #include #include +#include #include "../light/behavior.h" #include "../voxelizer/behavior.h" @@ -158,11 +159,9 @@ void ext::ExtSceneBehavior::initialize( uf::Object& self ) { #endif renderMode.scale = 1.0f; // ::json["engine"]["ext"]["vulkan"]["framebuffer"]["size"].as(1.0f); UF_MSG_DEBUG("Geometry render scale: {:.3f}", renderMode.scale); - /* - if ( ::json["engine"]["render modes"]["stereo deferred"].as() ) { + if ( ext::openvr::enabled ) { renderMode.metadata.eyes = 2; } - */ #if UF_USE_VULKAN if ( uf::renderer::settings::pipelines::deferred ) { renderMode.metadata.pipelines.emplace_back(uf::renderer::settings::pipelines::names::deferred); diff --git a/engine/src/engine/graph/graph.cpp b/engine/src/engine/graph/graph.cpp index b4dcd45f..9fb4606b 100644 --- a/engine/src/engine/graph/graph.cpp +++ b/engine/src/engine/graph/graph.cpp @@ -1707,10 +1707,8 @@ bool uf::graph::tick( pod::Graph::Storage& storage ) { auto& metadata = entity.getComponent(); auto& transform = entity.getComponent>(); - if ( !metadata.system.ignoreGraph ) { - object.previous = object.model; - object.model = uf::transform::model( transform ); - } + object.previous = object.model; + object.model = uf::transform::model( transform ); } objects.emplace_back( object ); diff --git a/engine/src/ext/openvr/openvr.cpp b/engine/src/ext/openvr/openvr.cpp index 18f28eb7..4d66ea52 100644 --- a/engine/src/ext/openvr/openvr.cpp +++ b/engine/src/ext/openvr/openvr.cpp @@ -5,726 +5,616 @@ #include #include #include +#include +#include #include -#define DEBUG_MARKER 0 - -vr::IVRSystem* ext::openvr::context; -ext::openvr::Driver ext::openvr::driver; -uint8_t ext::openvr::renderPass = 0; -float ext::openvr::width = 0; -float ext::openvr::height = 0; -bool ext::openvr::enabled = false; -bool ext::openvr::swapEyes = false; -uint8_t ext::openvr::dominantEye = 0; #define VR_CHECK_INPUT_RESULT(f)\ - if ( f != vr::VRInputError_None ) {\ - uf::iostream << __FILE__ << ":" << __FUNCTION__ << ":L" << __LINE__ << ": VR Error: " << vr::VR_GetVRInitErrorAsEnglishDescription(err) << "\n";\ + if ( (err = (f)) != vr::VRInputError_None ) {\ + UF_MSG_ERROR("VR Error: {}", err);\ return false;\ } -// toPort +#define VR_CHECK_COMPOSITOR_RESULT(f)\ + if ( (err = (f)) != vr::VRCompositorError_None ) UF_MSG_ERROR("VR Error: {}", err); + namespace { - uf::stl::string GetTrackedDeviceString(vr::IVRSystem *pHmd, vr::TrackedDeviceIndex_t unDevice, vr::TrackedDeviceProperty prop, vr::TrackedPropertyError *peError = NULL) { - uint32_t requiredBufferLen = pHmd->GetStringTrackedDeviceProperty(unDevice, prop, NULL, 0, peError); - if( requiredBufferLen == 0 ) return ""; + struct VRContext { + vr::IVRSystem* system = nullptr; + vr::IVRCompositor* compositor = nullptr; - char *pchBuffer = new char[requiredBufferLen]; - requiredBufferLen = pHmd->GetStringTrackedDeviceProperty(unDevice,prop,pchBuffer,requiredBufferLen,peError); - uf::stl::string sResult = pchBuffer; - delete[] pchBuffer; + struct Driver { + uf::stl::string name; + uf::stl::string serial; + uf::stl::string manifest = "./data/openvr_manifest.json"; + vr::TrackedDevicePose_t poses[vr::k_unMaxTrackedDeviceCount]; + uf::stl::string types[vr::k_unMaxTrackedDeviceCount]; + vr::IVRRenderModels* renderModels; + } driver; - return sResult; - } + uint8_t renderPass = 0; + float width = 0; + float height = 0; - uf::stl::string GetTrackedDeviceClassString(vr::ETrackedDeviceClass klass) { - uf::stl::string str = "Unknown class"; + bool enabled = false; + bool swapEyes = false; + uint8_t dominantEye = 0; - switch ( klass ) { - case vr::TrackedDeviceClass_Invalid: // = 0, the ID was not valid. - str = "invalid"; - break; - case vr::TrackedDeviceClass_HMD: // = 1, Head-Mounted Displays - str = "hmd"; - break; - case vr::TrackedDeviceClass_Controller: // = 2, Tracked controllers - str = "controller"; - break; - case vr::TrackedDeviceClass_GenericTracker: // = 3, Generic trackers, similar to controllers - str = "generic tracker"; - break; - case vr::TrackedDeviceClass_TrackingReference: // = 4, Camera and base stations that serve as tracking reference points - str = "base station"; - break; - case vr::TrackedDeviceClass_DisplayRedirect: // = 5, Accessories that aren't necessarily tracked themselves, but may redirect video output from other tracked devices - str = "display redirect"; - break; - } - - return str; - } - struct { struct { - pod::Matrix4t<> matrix; - } hmd; - struct { - struct { + struct { pod::Matrix4t<> matrix; } hmd; + struct ControllerState { uint32_t index; bool active; uf::Serializer state; pod::Matrix4t<> matrix; pod::Matrix4t<> tip; - uf::Graphic graphic; } left, right; - } controllers; - } devices; + } devices; - struct { - uf::stl::unordered_map actions; - uf::stl::unordered_map actionSets; - // uf::stl::vector inputs; - uf::stl::vector activeActionSets; - } handles; + struct { + uf::stl::unordered_map actions; + uf::stl::unordered_map actionSets; + uf::stl::vector activeActionSets; + } handles; - struct QueuedRenderModel { - vr::RenderModel_t* model; - vr::RenderModel_TextureMap_t* texture; - }; - uf::stl::unordered_map queuedRenderModels; - uf::stl::unordered_map renderModels; - uf::stl::vector renderModelNames; + struct QueuedRenderModel { + vr::RenderModel_t* model = nullptr; + vr::RenderModel_TextureMap_t* texture = nullptr; + }; + uf::stl::unordered_map queuedRenderModels; + uf::stl::unordered_map renderModels; + uf::stl::unordered_map renderTextures; + + uf::stl::vector renderModelNames; + } g_vr; + + inline pod::Matrix4t<> convertMatrix(const vr::HmdMatrix34_t& mat) { + return { + mat.m[0][0], mat.m[1][0], mat.m[2][0], 0.0f, + mat.m[0][1], mat.m[1][1], mat.m[2][1], 0.0f, + mat.m[0][2], mat.m[1][2], mat.m[2][2], 0.0f, + mat.m[0][3], mat.m[1][3], mat.m[2][3], 1.0f + }; + } + + inline uf::stl::string getTrackedDeviceString(vr::IVRSystem *pHmd, vr::TrackedDeviceIndex_t unDevice, vr::TrackedDeviceProperty prop, vr::TrackedPropertyError *peError = nullptr) { + uint32_t requiredBufferLen = pHmd->GetStringTrackedDeviceProperty(unDevice, prop, nullptr, 0, peError); + if( requiredBufferLen == 0 ) return ""; + + uf::stl::string sResult; + sResult.resize(requiredBufferLen); + pHmd->GetStringTrackedDeviceProperty(unDevice, prop, sResult.data(), requiredBufferLen, peError); + + while(!sResult.empty() && sResult.back() == '\0') sResult.pop_back(); + return sResult; + } + + inline uf::stl::string getTrackedDeviceClassString(vr::ETrackedDeviceClass klass) { + switch ( klass ) { + case vr::TrackedDeviceClass_HMD: return "hmd"; + case vr::TrackedDeviceClass_Controller: return "controller"; + case vr::TrackedDeviceClass_GenericTracker: return "generic tracker"; + case vr::TrackedDeviceClass_TrackingReference: return "base station"; + case vr::TrackedDeviceClass_DisplayRedirect: return "display redirect"; + default: return "invalid"; + } + } } -bool ext::openvr::initialize( int stage ) { - if ( stage == 1 ) { - if ( !vr::VRCompositor() ) { - uf::iostream << "Error while initializing HMD: VRCompositor() failed" << "\n"; - return false; - } - return true; - } - if ( !vr::VR_IsHmdPresent() ) { - uf::iostream << "Error while initializing HMD: not present" << "\n"; +bool ext::openvr::enabled = false; +bool ext::openvr::initialize() { + if ( !ext::openvr::enabled ) { return false; } - if ( !vr::VR_IsRuntimeInstalled() ) { - uf::iostream << "Error while initializing HMD: no runtime is installed" << "\n"; + + if ( !vr::VR_IsHmdPresent() || !vr::VR_IsRuntimeInstalled() ) { + UF_MSG_ERROR("VR Error: HMD not present or runtime missing."); return false; } - - int baseStations = 0; - // const char* runtime_path = vr::VR_GetRuntimePath(); + vr::HmdError err; - ext::openvr::context = vr::VR_Init( &err, vr::EVRApplicationType::VRApplication_Scene ); - if ( err != vr::VRInitError_None || ext::openvr::context == NULL ) { - uf::iostream << "Error while initializing SteamVR runtime: " << vr::VR_GetVRInitErrorAsEnglishDescription(err) << "\n"; + g_vr.system = vr::VR_Init( &err, vr::EVRApplicationType::VRApplication_Scene ); + if ( err != vr::VRInitError_None || !g_vr.system ) { + UF_MSG_ERROR("Error initializing SteamVR: {}", vr::VR_GetVRInitErrorAsEnglishDescription(err)); return false; } - // Get connected devices + // get connected devices + int baseStations = 0; for ( uint32_t i = vr::k_unTrackedDeviceIndex_Hmd; i < vr::k_unMaxTrackedDeviceCount; ++i ) { - if ( !ext::openvr::context->IsTrackedDeviceConnected(i) ) continue; - vr::ETrackedDeviceClass trackedDeviceClass = ext::openvr::context->GetTrackedDeviceClass(i); - uf::stl::string trackedDeviceType = ::GetTrackedDeviceClassString(trackedDeviceClass); - ext::openvr::driver.types[i] = trackedDeviceType; + if ( !g_vr.system->IsTrackedDeviceConnected(i) ) continue; - uf::iostream << "Tracking device " << i << " is connected " << "\n"; - uf::iostream << " Device type: " << trackedDeviceType << ". Name: " << ::GetTrackedDeviceString(ext::openvr::context, i, vr::Prop_TrackingSystemName_String) << "\n"; + vr::ETrackedDeviceClass trackedClass = g_vr.system->GetTrackedDeviceClass(i); + uf::stl::string typeName = getTrackedDeviceClassString(trackedClass); + g_vr.driver.types[i] = typeName; - if (trackedDeviceClass == vr::ETrackedDeviceClass::TrackedDeviceClass_TrackingReference) + UF_MSG_DEBUG("Tracking device {} connected ({}): {}", i, typeName, getTrackedDeviceString(g_vr.system, i, vr::Prop_TrackingSystemName_String)); + + if ( trackedClass == vr::ETrackedDeviceClass::TrackedDeviceClass_TrackingReference ) { ++baseStations; - else if ( trackedDeviceClass == vr::ETrackedDeviceClass::TrackedDeviceClass_Controller ) { - vr::ETrackedControllerRole role = ext::openvr::context->GetControllerRoleForTrackedDeviceIndex(i); - if ( role == vr::TrackedControllerRole_LeftHand ) ::devices.controllers.left.index = i; - else if ( role == vr::TrackedControllerRole_RightHand ) ::devices.controllers.right.index = i; + } else if ( trackedClass == vr::ETrackedDeviceClass::TrackedDeviceClass_Controller ) { + vr::ETrackedControllerRole role = g_vr.system->GetControllerRoleForTrackedDeviceIndex(i); + if ( role == vr::TrackedControllerRole_LeftHand ) g_vr.devices.left.index = i; + else if ( role == vr::TrackedControllerRole_RightHand ) g_vr.devices.right.index = i; } + if (i == vr::k_unTrackedDeviceIndex_Hmd) { - ext::openvr::driver.name = ::GetTrackedDeviceString(ext::openvr::context, i, vr::Prop_TrackingSystemName_String); - ext::openvr::driver.serial = ::GetTrackedDeviceString(ext::openvr::context, i, vr::Prop_SerialNumber_String); + g_vr.driver.name = getTrackedDeviceString(g_vr.system, i, vr::Prop_TrackingSystemName_String); + g_vr.driver.serial = getTrackedDeviceString(g_vr.system, i, vr::Prop_SerialNumber_String); } } -/* - // Get render models - driver.renderModels = (vr::IVRRenderModels*) vr::VR_GetGenericInterface( vr::IVRRenderModels_Version, &err ); - if ( err != vr::VRInitError_None ) { - uf::iostream << "Error while retrieving render model interface: " << vr::VR_GetVRInitErrorAsEnglishDescription(err) << "\n"; + + UF_MSG_DEBUG("HMD name={}, serial={}", g_vr.driver.name, g_vr.driver.serial); + + // load manifest and action handles + { + vr::EVRInputError err; + VR_CHECK_INPUT_RESULT( vr::VRInput()->SetActionManifestPath( uf::io::absolute(g_vr.driver.manifest).c_str() ) ); + + uf::Serializer manifest; + manifest.readFromFile(g_vr.driver.manifest); + + for ( auto i = 0; i < manifest["action_sets"].size(); ++i ) { + uf::stl::string name = manifest["action_sets"][i]["name"].as(); + vr::VRActionSetHandle_t handle; + VR_CHECK_INPUT_RESULT( vr::VRInput()->GetActionSetHandle( name.c_str(), &handle ) ); + g_vr.handles.actionSets[name] = handle; + } + + for ( auto i = 0; i < manifest["actions"].size(); ++i ) { + uf::stl::string name = manifest["actions"][i]["name"].as(); + vr::VRActionHandle_t handle; + VR_CHECK_INPUT_RESULT( vr::VRInput()->GetActionHandle( name.c_str(), &handle ) ); + g_vr.handles.actions[name] = handle; + + // add haptics to hooks + uf::stl::vector split = uf::string::split( name, "/" ); + uf::stl::string shortname = split.back(); + split = uf::string::split( shortname, "." ); + + if ( split.front() == "hapticVibration" ) { + uf::hooks.addHook( "VR:Haptics." + split.back(), [handle](ext::json::Value& json){ + vr::VRInput()->TriggerHapticVibrationAction( + handle, json["delay"].as(), json["duration"].as(), + json["frequency"].as(), json["amplitude"].as(), + vr::k_ulInvalidInputValueHandle + ); + }); + } + } + } + + // cache model names + size_t count = vr::VRRenderModels()->GetRenderModelCount(); + g_vr.renderModelNames.reserve(count); + for ( size_t i = 0; i < count; ++i ) { + size_t strlen = vr::VRRenderModels()->GetRenderModelName( i, nullptr, 0 ); + if ( strlen == 0 ) continue; + uf::stl::string name(strlen, '\0'); + vr::VRRenderModels()->GetRenderModelName( i, name.data(), strlen ); + name.pop_back(); + g_vr.renderModelNames.emplace_back( name ); + } + + uf::hooks.addHook( "VR:Seat.Reset", [&](ext::json::Value& json){ resetPosition(); }); + + g_vr.compositor = vr::VRCompositor(); + if ( !g_vr.compositor ) { + UF_MSG_ERROR("VR Error: Failed to initialize VR Compositor!"); + vr::VR_Shutdown(); + g_vr.system = nullptr; return false; } -*/ - uf::iostream << "HMD " << ext::openvr::driver.name << " " << ext::openvr::driver.serial << "\n"; - // load manifest - { - VR_CHECK_INPUT_RESULT( vr::VRInput()->SetActionManifestPath( uf::io::absolute(ext::openvr::driver.manifest).c_str() ) ); - // configure action handles - { - uf::Serializer manifest; - manifest.readFromFile(ext::openvr::driver.manifest); - - if ( DEBUG_MARKER ) UF_MSG_DEBUG(ext::openvr::driver.manifest); - for ( auto i = 0; i < manifest["action_sets"].size(); ++i ) { - if ( DEBUG_MARKER ) UF_MSG_DEBUG(manifest["action_sets"][i]); - uf::stl::string name = manifest["action_sets"][i]["name"].as(); - vr::VRActionSetHandle_t handle; - VR_CHECK_INPUT_RESULT( vr::VRInput()->GetActionSetHandle( name.c_str(), &handle ) ); - handles.actionSets[name] = handle; - if ( DEBUG_MARKER ) UF_MSG_DEBUG("Registered action set: {}", name); - // handles.actionSets.push_back(handle); - } - for ( auto i = 0; i < manifest["actions"].size(); ++i ) { - uf::stl::string name = manifest["actions"][i]["name"].as(); - vr::VRActionHandle_t handle; - VR_CHECK_INPUT_RESULT( vr::VRInput()->GetActionHandle( name.c_str(), &handle ) ); - handles.actions[name] = handle; - if ( DEBUG_MARKER ) UF_MSG_DEBUG("Registered action: {}", name); - // handles.actions.push_back(handle); - - // add haptics to hooks - { - uf::stl::vector split = uf::string::split( name, "/" ); - uf::stl::string shortname = split.back(); - split = uf::string::split( shortname, "." ); - if ( split.front() == "hapticVibration" ) { - if ( DEBUG_MARKER ) UF_MSG_DEBUG("Registered hook for haptic: {}", ("VR:Haptics."+split.back())); - uf::hooks.addHook( "VR:Haptics."+split.back(), [&](ext::json::Value& json){ - if ( vr::VRInputError_None != vr::VRInput()->TriggerHapticVibrationAction( handle, json["delay"].as(), json["duration"].as(), json["frequency"].as(), json["amplitude"].as(), vr::k_ulInvalidInputValueHandle ) ) - return; - return; - }); - } - } - } - } - } - - // load render models - { - size_t count = vr::VRRenderModels()->GetRenderModelCount(); - ::renderModelNames.reserve(count); - for ( size_t i = 0; i < count; ++i ) { - size_t strlen = vr::VRRenderModels()->GetRenderModelName( i, NULL, 0 ); - char buffer[strlen]; - vr::VRRenderModels()->GetRenderModelName( i, &buffer[0], strlen ); - uf::stl::string name = buffer; - ::renderModelNames.push_back( name ); - } - } - - uf::hooks.addHook( "VR:Seat.Reset", [&](ext::json::Value& json){ - ext::openvr::resetPosition(); - }); - - vr::VRCompositor()->WaitGetPoses(&driver.poses[0], vr::k_unMaxTrackedDeviceCount, nullptr, 0); - vr::VRCompositor()->SetExplicitTimingMode(vr::EVRCompositorTimingMode::VRCompositorTimingMode_Explicit_ApplicationPerformsPostPresentHandoff); + g_vr.compositor->SetExplicitTimingMode(vr::EVRCompositorTimingMode::VRCompositorTimingMode_Explicit_ApplicationPerformsPostPresentHandoff); + g_vr.compositor->WaitGetPoses(&g_vr.driver.poses[0], vr::k_unMaxTrackedDeviceCount, nullptr, 0); return true; } void ext::openvr::terminate() { -// ::devices.controllers.left.graphic.destroy(); -// ::devices.controllers.right.graphic.destroy(); - for ( auto pair : ::renderModels ) { - pair.second.destroy(); - } - + for ( auto& pair : g_vr.renderModels ) pair.second.destroy(); + g_vr.renderModels.clear(); vr::VR_Shutdown(); - ext::openvr::context = NULL; + g_vr.system = nullptr; } void ext::openvr::resetPosition() { -// ext::openvr::context->ResetSeatedZeroPose(); + // if ( g_vr.system ) g_vr.system->ResetSeatedZeroPose(); } void ext::openvr::tick() { - if ( !ext::openvr::context ) return; - - if ( DEBUG_MARKER ) UF_MSG_DEBUG("OPENVR TICK"); + if ( !g_vr.system ) return; - // load render model from api - for ( auto& pair : ::queuedRenderModels ) { - if ( DEBUG_MARKER ) UF_MSG_DEBUG("QUEUED: {}", pair.first); - uf::stl::string name = pair.first; - auto& queued = pair.second; - { + // process queued render models + for ( auto it = g_vr.queuedRenderModels.begin(); it != g_vr.queuedRenderModels.end(); ) { + uf::stl::string name = it->first; + auto& queued = it->second; + + if ( !queued.model ) { vr::EVRRenderModelError status = vr::VRRenderModels()->LoadRenderModel_Async( name.c_str(), &queued.model ); - // wait for load - if ( status == vr::VRRenderModelError_Loading ) continue; - // error'd out + if ( status == vr::VRRenderModelError_Loading ) { ++it; continue; } if ( status != vr::VRRenderModelError_None ) { - if ( DEBUG_MARKER ) UF_MSG_DEBUG("Failed to load render model {}: {}", name, vr::VRRenderModels()->GetRenderModelErrorNameFromEnum( status )); - ::queuedRenderModels.erase(name); - break; + it = g_vr.queuedRenderModels.erase(it); + continue; } } - // loaded, now to load texture - { - vr::EVRRenderModelError status = vr::VRRenderModels()->LoadTexture_Async( queued.model->diffuseTextureId, &queued.texture ); - // wait for load - if ( status == vr::VRRenderModelError_Loading ) continue; - // error'd out - if ( status != vr::VRRenderModelError_None ) { - if ( DEBUG_MARKER ) UF_MSG_DEBUG("Failed to load render model {}'s texture: ", name, vr::VRRenderModels()->GetRenderModelErrorNameFromEnum( status )); - vr::VRRenderModels()->FreeRenderModel( queued.model ); - ::queuedRenderModels.erase(name); - break; - } - } - // loaded texture, process - { - // uf::Mesh& mesh = renderModels[name]; - uf::Graphic& graphic = renderModels[name]; - uf::Mesh mesh; - mesh.vertices.reserve(queued.model->unVertexCount); + if ( !queued.texture ) { + vr::EVRRenderModelError status = vr::VRRenderModels()->LoadTexture_Async( queued.model->diffuseTextureId, &queued.texture ); + if ( status == vr::VRRenderModelError_Loading ) { ++it; continue; } + if ( status != vr::VRRenderModelError_None ) { + vr::VRRenderModels()->FreeRenderModel( queued.model ); + it = g_vr.queuedRenderModels.erase(it); + continue; + } + } + + // process mesh + { + uf::stl::vector vertices; + uf::stl::vector indices; + + vertices.reserve( queued.model->unVertexCount ); + indices.reserve(queued.model->unTriangleCount * 3); + for ( size_t i = 0; i < queued.model->unVertexCount; ++i ) { auto& v = queued.model->rVertexData[i]; - - uf::Mesh::vertex_t vertex; - vertex.position = pod::Vector3f{ v.vPosition.v[0], v.vPosition.v[1], -v.vPosition.v[2] }; - vertex.normal = pod::Vector3f{ v.vNormal.v[0], v.vNormal.v[1], v.vNormal.v[2] }; + + // might need to invert z + auto& vertex = vertices.emplace_back(); + vertex.position = pod::Vector3f{ v.vPosition.v[0], v.vPosition.v[1], v.vPosition.v[2] }; vertex.uv = pod::Vector2f{ v.rfTextureCoord[0], v.rfTextureCoord[1] }; - - mesh.vertices.push_back(vertex); + vertex.normal = pod::Vector3f{ v.vNormal.v[0], v.vNormal.v[1], v.vNormal.v[2] }; } - mesh.indices.reserve(queued.model->unTriangleCount * 3); - for ( std::size_t i = 0; i < queued.model->unTriangleCount * 3; ++i ) { - mesh.indices.push_back( queued.model->rIndexData[i] ); + + for ( std::size_t i = 0; i < queued.model->unTriangleCount * 3; i += 3 ) { + indices.emplace_back( queued.model->rIndexData[i + 0] ); + indices.emplace_back( queued.model->rIndexData[i + 1] ); + indices.emplace_back( queued.model->rIndexData[i + 2] ); } - // grab texture - size_t len = queued.texture->unWidth * queued.texture->unHeight * 4; - graphic.initialize(); - graphic.initializeMesh(mesh); - - auto& texture = graphic.material.textures.emplace_back(); - texture.fromBuffers( (void*) queued.texture->rubTextureMapData, len, uf::renderer::enums::Format::R8G8B8A8_UNORM, queued.texture->unWidth, queued.texture->unHeight ); + + auto& mesh = g_vr.renderModels[name]; + mesh.bind(); + mesh.insertVertices(vertices); + mesh.insertIndices(indices); } - // call hook + // process texture + { + auto& image = g_vr.renderTextures[name]; + uf::image::load( image, queued.texture->rubTextureMapData, { queued.texture->unWidth, queued.texture->unHeight }, 8, 4 ); + } + ext::json::Value payload; payload["name"] = name; uf::hooks.call( "VR:Model.Loaded", payload ); - - // clear - { - if ( DEBUG_MARKER ) UF_MSG_DEBUG("QUEUED MODEL: {}", queued.model); - if ( DEBUG_MARKER ) UF_MSG_DEBUG("QUEUED TEXTURE: {}", queued.texture); - vr::VRRenderModels()->FreeRenderModel( queued.model ); - vr::VRRenderModels()->FreeTexture( queued.texture ); - } - { - if ( DEBUG_MARKER ) UF_MSG_DEBUG("FINISHED LOADING: {}", name); - ::queuedRenderModels.erase(name); - break; - } - } - // - // Obtain tracking device poses - float fPredictedSecondsFromNow = ext::openvr::predictedTimeToDisplay(); - // Parse actions - { - { - handles.activeActionSets.clear(); - for ( auto pair : handles.actionSets ) { - vr::VRActiveActionSet_t actionSet = { 0 }; - actionSet.ulActionSet = pair.second; - handles.activeActionSets.push_back(actionSet); + vr::VRRenderModels()->FreeRenderModel( queued.model ); + vr::VRRenderModels()->FreeTexture( queued.texture ); + it = g_vr.queuedRenderModels.erase( it ); + } + + // parse actions + float fPredictedSecondsFromNow = predictedTimeToDisplay(); + + g_vr.handles.activeActionSets.clear(); + for ( const auto& pair : g_vr.handles.actionSets ) { + vr::VRActiveActionSet_t actionSet = { 0 }; + actionSet.ulActionSet = pair.second; + g_vr.handles.activeActionSets.emplace_back(actionSet); + } + + vr::VRInput()->UpdateActionState( g_vr.handles.activeActionSets.data(), sizeof(vr::VRActiveActionSet_t), g_vr.handles.activeActionSets.size() ); + + for ( const auto& pair : g_vr.handles.actions ) { + uf::stl::string name = pair.first; + auto& handle = pair.second; + + uf::stl::vector split = uf::string::split( name, "/" ); + uf::stl::string shortname = split.back(); + split = uf::string::split( shortname, "." ); + auto actionStr = split.front(); + auto handStr = split.back(); + + auto& controller = (handStr == "left") ? g_vr.devices.left : g_vr.devices.right; + + vr::InputDigitalActionData_t digitalData; + if ( vr::VRInputError_None == vr::VRInput()->GetDigitalActionData(handle, &digitalData, sizeof(digitalData), vr::k_ulInvalidInputValueHandle) ) { + if ( digitalData.bActive ) { + + if ( handStr == "left" ) { + if ( actionStr == "A" ) uf::inputs::controller::states::L_A = digitalData.bState; + else if ( actionStr == "B" ) uf::inputs::controller::states::L_B = digitalData.bState; + else if ( actionStr == "X" ) uf::inputs::controller::states::L_X = digitalData.bState; + else if ( actionStr == "Y" ) uf::inputs::controller::states::L_Y = digitalData.bState; + else if ( actionStr == "DPadUp" ) uf::inputs::controller::states::L_DPAD_UP = digitalData.bState; + else if ( actionStr == "DPadDown" ) uf::inputs::controller::states::L_DPAD_DOWN = digitalData.bState; + else if ( actionStr == "DPadLeft" ) uf::inputs::controller::states::L_DPAD_LEFT = digitalData.bState; + else if ( actionStr == "DPadRight" ) uf::inputs::controller::states::L_DPAD_RIGHT = digitalData.bState; + else if ( actionStr == "Start" ) uf::inputs::controller::states::START = digitalData.bState; + } else if ( handStr == "right" ) { + if ( actionStr == "A" ) uf::inputs::controller::states::R_A = digitalData.bState; + else if ( actionStr == "B" ) uf::inputs::controller::states::R_B = digitalData.bState; + else if ( actionStr == "X" ) uf::inputs::controller::states::R_X = digitalData.bState; + else if ( actionStr == "Y" ) uf::inputs::controller::states::R_Y = digitalData.bState; + else if ( actionStr == "DPadUp" ) uf::inputs::controller::states::R_DPAD_UP = digitalData.bState; + else if ( actionStr == "DPadDown" ) uf::inputs::controller::states::R_DPAD_DOWN = digitalData.bState; + else if ( actionStr == "DPadLeft" ) uf::inputs::controller::states::R_DPAD_LEFT = digitalData.bState; + else if ( actionStr == "DPadRight" ) uf::inputs::controller::states::R_DPAD_RIGHT = digitalData.bState; + else if ( actionStr == "Start" ) uf::inputs::controller::states::START = digitalData.bState; + } + + if ( digitalData.bChanged ) { + ext::json::Value payload; + payload["name"] = actionStr; + payload["hand"] = handStr; + payload["full name"] = name; + payload["deltaTime"] = digitalData.fUpdateTime; + payload["state"] = digitalData.bState; + controller.state[actionStr] = payload; + uf::hooks.call( "VR:Input.Digital", payload ); + } } } - vr::VRInput()->UpdateActionState( handles.activeActionSets.data(), sizeof(vr::VRActiveActionSet_t), handles.activeActionSets.size() ); - for ( auto pair : handles.actions ) { - uf::stl::string name = pair.first; - auto& handle = pair.second; - // analog data - { - vr::InputAnalogActionData_t data; - if ( vr::VRInputError_None == vr::VRInput()->GetAnalogActionData(handle, &data, sizeof(data), vr::k_ulInvalidInputValueHandle) ) { - float deltaTime = data.fUpdateTime; - pod::Vector3f delta = { data.deltaX, data.deltaY, data.deltaZ }; - pod::Vector3f position = { data.x, data.y, data.z }; - if ( data.bActive ) { - ext::json::Value payload; - uf::stl::vector split = uf::string::split( name, "/" ); - uf::stl::string shortname = split.back(); - split = uf::string::split( shortname, "." ); - - payload["name"] = split.front(); - payload["hand"] = split.back(); - payload["full name"] = name; - payload["deltaTime"] = deltaTime; - payload["analog"]["position"][0] = position.x; - payload["analog"]["position"][1] = position.y; - payload["analog"]["position"][2] = position.z; - payload["analog"]["delta"][0] = delta.x; - payload["analog"]["delta"][1] = delta.y; - payload["analog"]["delta"][2] = delta.z; - // store state - if ( split.back() == "left" ) ::devices.controllers.left.state[split.front()] = payload; - else if ( split.back() == "right" ) ::devices.controllers.right.state[split.front()] = payload; - // send payload only on deltas - if ( delta != pod::Vector3f{ 0, 0, 0 } ) uf::hooks.call( "VR:Input.Analog", payload ); + + vr::InputAnalogActionData_t analogData; + if ( vr::VRInputError_None == vr::VRInput()->GetAnalogActionData(handle, &analogData, sizeof(analogData), vr::k_ulInvalidInputValueHandle) ) { + if ( analogData.bActive ) { + if ( handStr == "left" ) { + if ( actionStr == "Joystick" || actionStr == "Trackpad" ) { + uf::inputs::controller::states::L_JOYSTICK.x = analogData.x; + uf::inputs::controller::states::L_JOYSTICK.y = analogData.y; + } else if ( actionStr == "Trigger" ) { + uf::inputs::controller::states::L_TRIGGER = analogData.x; } + } else if ( handStr == "right" ) { + if ( actionStr == "Joystick" || actionStr == "Trackpad" ) { + uf::inputs::controller::states::R_JOYSTICK.x = analogData.x; + uf::inputs::controller::states::R_JOYSTICK.y = analogData.y; + } else if ( actionStr == "Trigger" ) { + uf::inputs::controller::states::R_TRIGGER = analogData.x; + } + } + + pod::Vector3f delta = { analogData.deltaX, analogData.deltaY, analogData.deltaZ }; + pod::Vector3f position = { analogData.x, analogData.y, analogData.z }; + if ( delta != pod::Vector3f{ 0, 0, 0 } ) { + ext::json::Value payload; + payload["name"] = actionStr; + payload["hand"] = handStr; + payload["full name"] = name; + payload["deltaTime"] = uf::time::delta; + payload["analog"]["position"] = uf::vector::encode( position ); + payload["analog"]["delta"] = uf::vector::encode( delta ); + controller.state[actionStr] = payload; + uf::hooks.call( "VR:Input.Analog", payload ); } } - // digital data - { - vr::InputDigitalActionData_t data; - if ( vr::VRInputError_None == vr::VRInput()->GetDigitalActionData(handle, &data, sizeof(data), vr::k_ulInvalidInputValueHandle) ) { - if ( data.bActive ) { - ext::json::Value payload; - uf::stl::vector split = uf::string::split( name, "/" ); - uf::stl::string shortname = split.back(); - split = uf::string::split( shortname, "." ); - - payload["name"] = split.front(); - payload["hand"] = split.back(); - payload["full name"] = name; - payload["deltaTime"] = data.fUpdateTime; - payload["state"] = data.bState; - // store state - if ( split.back() == "left" ) ::devices.controllers.left.state[split.front()] = payload; - else if ( split.back() == "right" ) ::devices.controllers.right.state[split.front()] = payload; - // send payload only on deltas - if ( data.bChanged ) uf::hooks.call( "VR:Input.Digital", payload ); - } - } - } - // pose data - { - vr::InputPoseActionData_t data; - if ( vr::VRInputError_None == vr::VRInput()->GetPoseActionDataRelativeToNow(handle, vr::TrackingUniverseStanding, fPredictedSecondsFromNow, &data, sizeof(data), vr::k_ulInvalidInputValueHandle) ) { - uf::stl::vector split = uf::string::split( name, "/" ); - uf::stl::string shortname = split.back(); - split = uf::string::split( shortname, "." ); - if ( split.front() == "handPose" ) { - if ( split.back() == "left" ) ::devices.controllers.left.active = data.bActive; - else if ( split.back() == "right" ) ::devices.controllers.right.active = data.bActive; - } - if ( data.bActive ) { - vr::HmdMatrix34_t mat = data.pose.mDeviceToAbsoluteTracking; - pod::Matrix4t<> res = { - mat.m[0][0], mat.m[1][0], mat.m[2][0], 0.0, - mat.m[0][1], mat.m[1][1], mat.m[2][1], 0.0, - mat.m[0][2], mat.m[1][2], mat.m[2][2], 0.0, - mat.m[0][3], mat.m[1][3], mat.m[2][3], 1.0f - }; - if ( split.back() == "left" ) { - if ( split.front() == "handPose" ) ::devices.controllers.left.matrix = res; - else if ( split.front() == "handTip" ) ::devices.controllers.left.tip = res; - } - else if ( split.back() == "right" ) { - if ( split.front() == "handPose" ) ::devices.controllers.right.matrix = res; - else if ( split.front() == "handTip" ) ::devices.controllers.right.tip = res; - } - } - } + } + + vr::InputPoseActionData_t poseData; + if ( vr::VRInputError_None == vr::VRInput()->GetPoseActionDataRelativeToNow(handle, vr::TrackingUniverseStanding, fPredictedSecondsFromNow, &poseData, sizeof(poseData), vr::k_ulInvalidInputValueHandle) ) { + if ( actionStr == "handPose" ) controller.active = poseData.bActive; + if ( poseData.bActive ) { + pod::Matrix4t<> res = convertMatrix(poseData.pose.mDeviceToAbsoluteTracking); + if ( actionStr == "handPose" ) controller.matrix = res; + else if ( actionStr == "handTip" ) controller.tip = res; } } } } bool ext::openvr::requestRenderModel( const uf::stl::string& name ) { - if ( DEBUG_MARKER ) UF_MSG_DEBUG("Requesting render model: {}", name); - if ( std::find( renderModelNames.begin(), renderModelNames.end(), name ) == renderModelNames.end() ) return false; - if ( renderModels.count(name) == 1 ) return true; - ::queuedRenderModels[name]; - return false; + if ( std::find( g_vr.renderModelNames.begin(), g_vr.renderModelNames.end(), name ) == g_vr.renderModelNames.end() ) return false; + if ( g_vr.renderModels.count(name) == 1 ) return true; + g_vr.queuedRenderModels[name]; + return false; } -void ext::openvr::submit() { bool invert = swapEyes; +void ext::openvr::submit() { + if ( !g_vr.system ) return; #if UF_USE_VULKAN - auto& renderMode = uf::renderer::getRenderMode(""); - + bool invert = g_vr.swapEyes; + + auto& scene = uf::scene::getCurrentScene(); + uf::renderer::RenderMode* renderModePointer = nullptr; + if ( uf::renderer::hasRenderMode("VR") ) { + renderModePointer = &uf::renderer::getRenderMode("VR"); + } else if ( scene.hasComponent() ) { + renderModePointer = &scene.getComponent(); + } else { + renderModePointer = &uf::renderer::getRenderMode("", true); + } + if ( !renderModePointer ) return; + auto& renderMode = *renderModePointer; float width = renderMode.width > 0 ? renderMode.width : uf::renderer::settings::width; float height = renderMode.height > 0 ? renderMode.height : uf::renderer::settings::height; - auto& leftOutputAttachment = renderMode.renderTarget.attachments[renderMode.metadata.outputs[0]]; - auto& rightOutputAttachment = renderMode.renderTarget.attachments[renderMode.metadata.outputs[1]]; - // Submit to SteamVR + if ( !renderMode.hasAttachment("left") || !renderMode.hasAttachment("right") ) return; + + auto& leftEyeAttachment = renderMode.getAttachment("left"); + auto& rightEyeAttachment = renderMode.getAttachment("right"); + vr::VRTextureBounds_t bounds; bounds.uMin = 0.0f; bounds.uMax = 1.0f; bounds.vMin = 0.0f; bounds.vMax = 1.0f; - vr::VRVulkanTextureData_t vulkanData; + vr::VRVulkanTextureData_t vulkanData = {}; vulkanData.m_pDevice = ( VkDevice_T * ) uf::renderer::device; vulkanData.m_pPhysicalDevice = ( VkPhysicalDevice_T * ) uf::renderer::device.physicalDevice; vulkanData.m_pInstance = ( VkInstance_T *) uf::renderer::device.instance; vulkanData.m_pQueue = ( VkQueue_T * ) uf::renderer::device.getQueue( uf::renderer::QueueEnum::PRESENT ); vulkanData.m_nQueueFamilyIndex = uf::renderer::device.queueFamilyIndices.present; - vulkanData.m_nWidth = width; vulkanData.m_nHeight = height; vulkanData.m_nSampleCount = 1; vr::Texture_t texture = { &vulkanData, vr::TextureType_Vulkan, vr::ColorSpace_Auto }; - - vulkanData.m_nFormat = leftOutputAttachment.descriptor.format; - vulkanData.m_nImage = (uint64_t) (VkImage) leftOutputAttachment.image; - vr::VRCompositor()->Submit( invert ? vr::Eye_Right : vr::Eye_Left, &texture, &bounds ); - vulkanData.m_nFormat = rightOutputAttachment.descriptor.format; - vulkanData.m_nImage = (uint64_t) (VkImage) rightOutputAttachment.image; - vr::VRCompositor()->Submit( invert ? vr::Eye_Left : vr::Eye_Right, &texture, &bounds ); + vr::EVRCompositorError err; + vulkanData.m_nFormat = leftEyeAttachment.descriptor.format; + vulkanData.m_nImage = (uint64_t) (VkImage) leftEyeAttachment.image; + VR_CHECK_COMPOSITOR_RESULT(vr::VRCompositor()->Submit( invert ? vr::Eye_Right : vr::Eye_Left, &texture, &bounds )); + + + vulkanData.m_nFormat = rightEyeAttachment.descriptor.format; + vulkanData.m_nImage = (uint64_t) (VkImage) rightEyeAttachment.image; + VR_CHECK_COMPOSITOR_RESULT(vr::VRCompositor()->Submit( invert ? vr::Eye_Left : vr::Eye_Right, &texture, &bounds )); vr::VRCompositor()->PostPresentHandoff(); #endif } -void ext::openvr::synchronize( bool async ) { - if ( async ) uf::thread::queue( uf::thread::fetchWorker(), [](){ - vr::VRCompositor()->WaitGetPoses(nullptr, 0, nullptr, 0); - }); - else vr::VRCompositor()->WaitGetPoses(nullptr, 0, nullptr, 0); - - ext::openvr::updateTracking(1); +void ext::openvr::synchronize() { + if ( !g_vr.system ) return; + vr::VRCompositor()->WaitGetPoses(nullptr, 0, nullptr, 0); + updateTracking(1); } float ext::openvr::predictedTimeToDisplay( float additional ) { - float displayFrequency = ext::openvr::context->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float); - float fFrameDuration = 1.f / displayFrequency; - + float displayFreq = g_vr.system->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float); + float fFrameDuration = 1.f / displayFreq; + float fSecondsSinceLastVsync = 0.0f; - ext::openvr::context->GetTimeSinceLastVsync( &fSecondsSinceLastVsync, NULL ); - - float fSecondsFromVSyncToPhotons = ext::openvr::context->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SecondsFromVsyncToPhotons_Float); - float fPredictedSecondsFromNow = additional * fFrameDuration - fSecondsSinceLastVsync + fSecondsFromVSyncToPhotons; -// fFrameDuration - fSecondsSinceLastVsync + fFrameDuration + fSecondsFromVSyncToPhotons; -// frameDuration + vsyncToPhotons; - return fPredictedSecondsFromNow; + g_vr.system->GetTimeSinceLastVsync( &fSecondsSinceLastVsync, nullptr ); + + float fSecondsFromVSyncToPhotons = g_vr.system->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SecondsFromVsyncToPhotons_Float); + return additional * fFrameDuration - fSecondsSinceLastVsync + fSecondsFromVSyncToPhotons; } float ext::openvr::updateTracking(float additional) { - float fPredictedSecondsFromNow = ext::openvr::predictedTimeToDisplay(additional); - ext::openvr::context->GetDeviceToAbsoluteTrackingPose( + float fPredicted = predictedTimeToDisplay(additional); + g_vr.system->GetDeviceToAbsoluteTrackingPose( vr::ETrackingUniverseOrigin::TrackingUniverseStanding, - fPredictedSecondsFromNow, - ext::openvr::driver.poses, - vr::k_unMaxTrackedDeviceCount + fPredicted, g_vr.driver.poses, vr::k_unMaxTrackedDeviceCount ); -// vr::VRCompositor()->WaitGetPoses(&driver.poses[0], vr::k_unMaxTrackedDeviceCount, nullptr, 0); - if ( ext::openvr::driver.poses[vr::k_unTrackedDeviceIndex_Hmd].bDeviceIsConnected && ext::openvr::driver.poses[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid ) { - vr::HmdMatrix34_t mat = ext::openvr::driver.poses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking; - ::devices.hmd.matrix = { - mat.m[0][0], mat.m[1][0], mat.m[2][0], 0.0, - mat.m[0][1], mat.m[1][1], mat.m[2][1], 0.0, - mat.m[0][2], mat.m[1][2], mat.m[2][2], 0.0, - mat.m[0][3], mat.m[1][3], mat.m[2][3], 1.0f - }; - ::devices.hmd.matrix = uf::matrix::invert(::devices.hmd.matrix); + if ( g_vr.driver.poses[vr::k_unTrackedDeviceIndex_Hmd].bDeviceIsConnected && g_vr.driver.poses[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid ) { + g_vr.devices.hmd.matrix = convertMatrix(g_vr.driver.poses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking); + g_vr.devices.hmd.matrix = uf::matrix::inverse(g_vr.devices.hmd.matrix); } - return fPredictedSecondsFromNow; + return fPredicted; } void ext::openvr::recommendedResolution( uint32_t& width, uint32_t& height ) { - ext::openvr::width = width; - ext::openvr::height = height; - ext::openvr::context->GetRecommendedRenderTargetSize( &width, &height ); + g_vr.system->GetRecommendedRenderTargetSize( &width, &height ); + g_vr.width = width; g_vr.height = height; } pod::Matrix4t<> ext::openvr::hmdHeadPositionMatrix() { - return ::devices.hmd.matrix; + return g_vr.devices.hmd.matrix; } pod::Matrix4t<> ext::openvr::hmdEyePositionMatrix( vr::Hmd_Eye eye ) { - vr::HmdMatrix34_t mat = ext::openvr::context->GetEyeToHeadTransform( eye ); - pod::Matrix4t<> res = { - mat.m[0][0], mat.m[1][0], mat.m[2][0], 0.0, - mat.m[0][1], mat.m[1][1], mat.m[2][1], 0.0, - mat.m[0][2], mat.m[1][2], mat.m[2][2], 0.0, - mat.m[0][3], mat.m[1][3], mat.m[2][3], 1.0f - }; - res = uf::matrix::inverse( res ); - return res; + return uf::matrix::inverse( convertMatrix(g_vr.system->GetEyeToHeadTransform( eye )) ); } pod::Matrix4t<> ext::openvr::hmdViewTranslationMatrix() { return uf::matrix::translate( uf::matrix::identity(), hmdPosition() ); } pod::Matrix4t<> ext::openvr::hmdViewRotationMatrix() { pod::Matrix4t<> mat = hmdHeadPositionMatrix(); - mat[(4*3)+0] = 0; - mat[(4*3)+1] = 0; - mat[(4*3)+2] = 0; + mat(3,0) = 0; + mat(3,1) = 0; + mat(3,2) = 0; return mat; } pod::Vector3f ext::openvr::hmdPosition() { pod::Matrix4t<> mat = hmdHeadPositionMatrix(); return { - mat[(4*3)+0], - mat[(4*3)+1], - -mat[(4*3)+2], + mat(3,0), + mat(3,1), + -mat(3,2), }; } pod::Vector3f ext::openvr::hmdEyePosition( vr::Hmd_Eye eye ) { - vr::HmdMatrix34_t mat = ext::openvr::context->GetEyeToHeadTransform( eye ); - pod::Vector3f offset = { mat.m[0][3], mat.m[1][3], mat.m[2][3] }; - return offset; + vr::HmdMatrix34_t mat = g_vr.system->GetEyeToHeadTransform( eye ); + return { mat.m[0][3], mat.m[1][3], mat.m[2][3] }; } pod::Vector3f ext::openvr::hmdPosition( vr::Hmd_Eye eye ) { return hmdPosition() + hmdEyePosition( eye ); } pod::Quaternion<> ext::openvr::hmdQuaternion() { - pod::Quaternion<> q = uf::quaternion::fromMatrix( hmdHeadPositionMatrix() ); -// q.w *= -1; - return q; -/* - pod::Matrix4t<> mat = hmdHeadPositionMatrix(); - pod::Quaternion<> q; - q.w = sqrt(fmax(0, 1 + mat[(4*0)+0] + mat[(4*1)+1] + mat[(4*2)+2])) / 2; - q.x = sqrt(fmax(0, 1 + mat[(4*0)+0] - mat[(4*1)+1] - mat[(4*2)+2])) / 2; - q.y = sqrt(fmax(0, 1 - mat[(4*0)+0] + mat[(4*1)+1] - mat[(4*2)+2])) / 2; - q.z = sqrt(fmax(0, 1 - mat[(4*0)+0] - mat[(4*1)+1] + mat[(4*2)+2])) / 2; - - q.x = copysign(q.x, mat[(4*1)+2] - mat[(4*2)+1]); - q.y = copysign(q.y, mat[(4*2)+0] - mat[(4*0)+2]); - q.z = copysign(q.z, mat[(4*0)+1] - mat[(4*1)+0]); - - q.w *= -1; - return q; // * pod::Vector4f{ 1, 1, -1, -1 }; -*/ + return uf::quaternion::fromMatrix( hmdHeadPositionMatrix() ); } pod::Matrix4t<> ext::openvr::hmdViewMatrix( vr::Hmd_Eye eye, const pod::Matrix4f& view ) { - return - hmdEyePositionMatrix( eye ) * + return hmdEyePositionMatrix( eye ) * uf::matrix::translate( uf::matrix::identity(), hmdPosition() ) * - uf::matrix::inverse( uf::quaternion::matrix( ext::openvr::hmdQuaternion() * pod::Vector4f{ 1, 1, -1, 1 } ) ) * - // uf::matrix::inverse( uf::quaternion::matrix( ext::openvr::hmdQuaternion() * pod::Vector4f{ 1, 1, -1, -1 } ) ) * + uf::matrix::inverse( uf::quaternion::matrix( hmdQuaternion() * pod::Vector4f{ 1, 1, -1, 1 } ) ) * view; -// return hmdEyePositionMatrix( eye ) * uf::matrix::translate( uf::matrix::identity(), hmdPosition() ) * uf::matrix::inverse( uf::quaternion::matrix( ext::openvr::hmdQuaternion() * pod::Vector4f{ 1, 1, -1, -1 } ) ) * view; -// return uf::matrix::translate( uf::matrix::identity(), hmdEyePosition( eye ) ) * uf::matrix::inverse( uf::quaternion::matrix( ext::openvr::hmdQuaternion() ) ) * view; -// return uf::matrix::scale(uf::matrix::identity(), pod::Vector3f{1,1,-1}) * hmdEyePositionMatrix( eye ) * hmdHeadPositionMatrix() * view; } pod::Matrix4t<> ext::openvr::hmdProjectionMatrix( vr::Hmd_Eye eye, float zNear, float zFar ) { - struct { - float left; - float right; - float top; - float bottom; - } frustum; - ext::openvr::context->GetProjectionRaw( eye, &frustum.left, &frustum.right, &frustum.top, &frustum.bottom ); + float left, right, top, bottom; + g_vr.system->GetProjectionRaw( eye, &left, &right, &top, &bottom ); - if ( true ) { - frustum.left = -abs( frustum.left ); - frustum.right = abs( frustum.right ); - frustum.top = -abs( frustum.top ); - frustum.bottom = abs( frustum.bottom ); + pod::Matrix4t<> m = uf::matrix::identity(); + + float idx = 1.0f / (right - left); + float idy = 1.0f / (bottom - top); + + m(0,0) = 2.0f * idx; + m(1,1) = 2.0f * idy; + m(0,2) = (right + left) * idx; + m(1,2) = (bottom + top) * idy; + m(3,2) = 1.0f; + m(3,3) = 0.0f; + + if ( zFar <= 0.0f ) { + m(2,2) = 0.0f; + m(2,3) = zNear; } else { + float range = zFar - zNear; + m(2,2) = zFar / range; + m(2,3) = -(zFar * zNear) / range; } - /* - float fov = this->m_settings.perspective.fov * (3.14159265358f / 180.0f); - float raidou = (float) this->m_settings.perspective.size.x / (float) this->m_settings.perspective.size.y; - float f = 1.0f / tan( 0.5f * fov ); - this->m_matrices.projection = { - f / raidou, 0.0f, 0.0f, 0.0f, - 0.0f, -f, 0.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f, - 0.0f, 0.0f, this->m_settings.perspective.bounds.x, 0.0f - }; - */ - - switch ( 0 ) { - case 0: { - float Sx = 2 / (frustum.right - frustum.left); - float Sy = 2 / (frustum.top - frustum.bottom); - float S20 = (frustum.right + frustum.left) / (frustum.right - frustum.left); - float S21 = (frustum.top + frustum.bottom) / (frustum.top - frustum.bottom); - float Sz = -zFar / (zFar - zNear); - float S32 = -zFar * zNear / (zFar - zNear); - /* - return { - Sx, 0, 0, 0, - 0, Sy, 0, 0, - S20, S21, 0, 1, - 0, 0, zNear, 0, - }; - */ - return uf::matrix::multiply( pod::Matrix4f{ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1, - }, pod::Matrix4f{ - Sx, 0, 0, 0, - 0, Sy, 0, 0, - -S20, -S21, 0, 1, - 0, 0, zNear, 0, - }); - } break; - default: { - vr::HmdMatrix44_t mat = ext::openvr::context->GetProjectionMatrix( eye, zFar, zNear ); - return uf::matrix::multiply( pod::Matrix4f{ - 1, 0, 0, 0, - 0,-1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1, - }, pod::Matrix4f{ - mat.m[0][0], mat.m[1][0], mat.m[2][0], mat.m[3][0], - mat.m[0][1], mat.m[1][1], mat.m[2][1], mat.m[3][1], - -mat.m[0][2], -mat.m[1][2], mat.m[2][2],-mat.m[3][2], - mat.m[0][3], mat.m[1][3], mat.m[2][3], mat.m[3][3] - }); - } break; - } + return m; } uf::Serializer ext::openvr::controllerState( vr::Controller_Hand hand, const uf::stl::string& key ) { - if ( hand == vr::Controller_Hand::Hand_Left ) return key == "" ? ::devices.controllers.left.state : ::devices.controllers.left.state[key]; - else if ( hand == vr::Controller_Hand::Hand_Right ) return key == "" ? ::devices.controllers.right.state : ::devices.controllers.right.state[key]; - return uf::Serializer(); + auto& state = ( hand == vr::Controller_Hand::Hand_Left ) ? g_vr.devices.left.state : g_vr.devices.right.state; + return key == "" ? state : state[key]; } pod::Vector3f ext::openvr::controllerPosition( vr::Controller_Hand hand, bool tip ) { pod::Matrix4t<> mat = controllerMatrix( hand, tip ); return { - mat[(4*3)+0], - mat[(4*3)+1], - -mat[(4*3)+2], + mat(3,0), + mat(3,1), + -mat(3,2), }; } pod::Quaternion<> ext::openvr::controllerQuaternion( vr::Controller_Hand hand, bool tip ) { pod::Quaternion<> q = uf::quaternion::fromMatrix( controllerMatrix( hand, tip ) ); q.w *= -1; return q; -/* - pod::Matrix4t<> mat = controllerMatrix( hand, tip ); - pod::Quaternion<> q; - q.w = sqrt(fmax(0, 1 + mat[(4*0)+0] + mat[(4*1)+1] + mat[(4*2)+2])) / 2; - q.x = sqrt(fmax(0, 1 + mat[(4*0)+0] - mat[(4*1)+1] - mat[(4*2)+2])) / 2; - q.y = sqrt(fmax(0, 1 - mat[(4*0)+0] + mat[(4*1)+1] - mat[(4*2)+2])) / 2; - q.z = sqrt(fmax(0, 1 - mat[(4*0)+0] - mat[(4*1)+1] + mat[(4*2)+2])) / 2; - - q.x = copysign(q.x, mat[(4*1)+2] - mat[(4*2)+1]); - q.y = copysign(q.y, mat[(4*2)+0] - mat[(4*0)+2]); - q.z = copysign(q.z, mat[(4*0)+1] - mat[(4*1)+0]); - return q * pod::Vector4f{ 1, 1, -1, 1 }; -*/ } pod::Matrix4t<> ext::openvr::controllerTranslationMatrix( vr::Controller_Hand hand, bool tip ) { return uf::matrix::translate( uf::matrix::identity(), controllerPosition( hand, tip ) ); } pod::Matrix4t<> ext::openvr::controllerRotationMatrix( vr::Controller_Hand hand, bool tip ) { pod::Matrix4t<> mat = controllerMatrix( hand, tip ); - mat[(4*3)+0] = 0; - mat[(4*3)+1] = 0; - mat[(4*3)+2] = 0; + mat(3,0) = 0; + mat(3,1) = 0; + mat(3,2) = 0; return uf::matrix::inverse( mat ); } pod::Matrix4t<> ext::openvr::controllerModelMatrix( vr::Controller_Hand hand, bool tip ) { - return - uf::matrix::translate( uf::matrix::identity(), controllerPosition( hand, tip ) ) * - uf::quaternion::matrix( controllerQuaternion( hand, tip ) ); -// return uf::matrix::translate( uf::matrix::identity(), controllerPosition( hand, tip ) ) * controllerRotationMatrix( hand, tip ); -// return uf::matrix::scale( uf::matrix::identity(), pod::Vector3f{ 1, 1, -1 } ) * controllerMatrix( hand, tip ); -// return uf::matrix::translate( uf::quaternion::matrix( ext::openvr::controllerQuaternion( hand, tip ) ), controllerPosition( hand, tip ) ); + return uf::matrix::translate( uf::matrix::identity(), controllerPosition( hand, tip ) ) * uf::quaternion::matrix( controllerQuaternion( hand, tip ) ); } pod::Matrix4t<> ext::openvr::controllerMatrix( vr::Controller_Hand hand, bool tip ) { - if ( hand == vr::Controller_Hand::Hand_Left ) return tip ? ::devices.controllers.left.tip : ::devices.controllers.left.matrix; - else if ( hand == vr::Controller_Hand::Hand_Right ) return tip ? ::devices.controllers.right.tip : ::devices.controllers.right.matrix; + if ( hand == vr::Controller_Hand::Hand_Left ) return tip ? g_vr.devices.left.tip : g_vr.devices.left.matrix; + else if ( hand == vr::Controller_Hand::Hand_Right ) return tip ? g_vr.devices.right.tip : g_vr.devices.right.matrix; return uf::matrix::identity(); } bool ext::openvr::controllerActive( vr::Controller_Hand hand ) { - if ( hand == vr::Controller_Hand::Hand_Left ) return ::devices.controllers.left.active; - else if ( hand == vr::Controller_Hand::Hand_Right ) return ::devices.controllers.right.active; - return false; + return ( hand == vr::Controller_Hand::Hand_Left ) ? g_vr.devices.left.active : g_vr.devices.right.active; } -uf::Graphic& ext::openvr::getRenderModel( const uf::stl::string& name ) { - return ::renderModels[name]; +uf::Mesh& ext::openvr::getRenderModel( const uf::stl::string& name ) { + return g_vr.renderModels[name]; } -uf::Graphic& ext::openvr::controllerRenderModel( vr::Controller_Hand hand ) { - if ( hand == vr::Controller_Hand::Hand_Left ) return renderModels["{indexcontroller}valve_controller_knu_1_0_left"]; //return ::devices.controllers.left.mesh; - else if ( hand == vr::Controller_Hand::Hand_Right ) return renderModels["{indexcontroller}valve_controller_knu_1_0_right"]; //return ::devices.controllers.right.mesh; +uf::Mesh& ext::openvr::controllerRenderModel( vr::Controller_Hand hand ) { + if ( hand == vr::Controller_Hand::Hand_Left ) return g_vr.renderModels["{indexcontroller}valve_controller_knu_1_0_left"]; + else if ( hand == vr::Controller_Hand::Hand_Right ) return g_vr.renderModels["{indexcontroller}valve_controller_knu_1_0_right"]; + UF_EXCEPTION("OpenVR error: invalid hand requested"); +} +uf::Image& ext::openvr::getRenderTexture( const uf::stl::string& name ) { + return g_vr.renderTextures[name]; +} +uf::Image& ext::openvr::controllerRenderTexture( vr::Controller_Hand hand ) { + if ( hand == vr::Controller_Hand::Hand_Left ) return g_vr.renderTextures["{indexcontroller}valve_controller_knu_1_0_left"]; + else if ( hand == vr::Controller_Hand::Hand_Right ) return g_vr.renderTextures["{indexcontroller}valve_controller_knu_1_0_right"]; UF_EXCEPTION("OpenVR error: invalid hand requested"); } #endif \ No newline at end of file diff --git a/engine/src/ext/vulkan/rendermodes/deferred.cpp b/engine/src/ext/vulkan/rendermodes/deferred.cpp index f6906b9c..e66cef3c 100644 --- a/engine/src/ext/vulkan/rendermodes/deferred.cpp +++ b/engine/src/ext/vulkan/rendermodes/deferred.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #define BARYCENTRIC 1 #if BARYCENTRIC @@ -117,7 +118,7 @@ void ext::vulkan::DeferredRenderMode::initialize( Device& device ) { struct { size_t id, bary, depth, depth_resolved, uv, normal; - size_t color, scratch, motion, output; + size_t color, scratch, motion, output, outputRightEye; } attachments = {}; bool blend = true; // !ext::vulkan::settings::invariant::deferredSampling; @@ -169,14 +170,14 @@ void ext::vulkan::DeferredRenderMode::initialize( Device& device ) { attachments.color = renderTarget.attach(RenderTarget::Attachment::Descriptor{ /*.format =*/ ext::vulkan::settings::pipelines::hdr ? enums::Format::HDR : enums::Format::SDR, /*.layout = */ VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - /*.usage =*/ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + /*.usage =*/ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, /*.blend =*/ true, /*.samples =*/ 1, }); attachments.scratch = renderTarget.attach(RenderTarget::Attachment::Descriptor{ /*.format =*/ ext::vulkan::settings::pipelines::hdr ? enums::Format::HDR : enums::Format::SDR, /*.layout = */ VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - /*.usage =*/ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + /*.usage =*/ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, /*.blend =*/ false, /*.samples =*/ 1, /*.mips =*/ mips, @@ -223,6 +224,11 @@ void ext::vulkan::DeferredRenderMode::initialize( Device& device ) { metadata.attachments["output"] = attachments.color; + if ( metadata.eyes == 2 ) { + metadata.attachments["left"] = metadata.attachments["output"]; + metadata.attachments["right"] = metadata.attachments["scratch"]; + } + // First pass: fill the G-Buffer renderTarget.addPass( /*.*/ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, @@ -449,6 +455,16 @@ void ext::vulkan::DeferredRenderMode::initialize( Device& device ) { ::postprocesses::depthPyramid.atomicCounter.initialize( (const void*) nullptr, sizeof(::AtomicCounter) * 1, uf::renderer::enums::Buffer::STORAGE | VK_BUFFER_USAGE_TRANSFER_DST_BIT ); shader.aliasBuffer("atomicCounterDepth", ::postprocesses::depthPyramid.atomicCounter); } + + if ( ext::openvr::enabled ) { + uf::stl::string vertexShaderFilename = uf::io::resolveURI(uf::io::root+"/shaders/display/vr/stereo.vert.spv"); + uf::stl::string fragmentShaderFilename = uf::io::resolveURI(uf::io::root+"/shaders/display/vr/stereo.frag.spv"); + blitter.material.attachShader(vertexShaderFilename, uf::renderer::enums::Shader::VERTEX, "vr"); + blitter.material.attachShader(fragmentShaderFilename, uf::renderer::enums::Shader::FRAGMENT, "vr"); + + auto& shader = blitter.material.getShader("fragment", "vr"); + shader.aliasAttachment("output", this); + } } this->build(true); @@ -597,6 +613,12 @@ void ext::vulkan::DeferredRenderMode::build( bool resized ) { descriptor.bind.point = VK_PIPELINE_BIND_POINT_COMPUTE; blitter.update( descriptor ); } + + if ( ext::openvr::enabled ) { + auto descriptor = blitter.descriptor; + descriptor.pipeline = "vr"; + blitter.update( descriptor ); + } } } void ext::vulkan::DeferredRenderMode::tick() { @@ -1191,6 +1213,63 @@ void ext::vulkan::DeferredRenderMode::createCommandBuffers( const uf::stl::vecto } #endif */ + #if UF_USE_OPENVR + // OpenVR does not respect layered images + if ( metadata.eyes == 2 && !ext::vulkan::hasRenderMode("VR") ) { + auto& outputAttachment = this->getAttachment("left"); + auto& scratchAttachment = this->getAttachment("right"); + + VkImageSubresourceRange outRange = {}; + outRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + outRange.baseMipLevel = 0; + outRange.levelCount = 1; + outRange.baseArrayLayer = 0; + outRange.layerCount = metadata.eyes; + + VkImageSubresourceRange scratchRange = outRange; + scratchRange.layerCount = 1; + + uf::renderer::Texture::setImageLayout( + commandBuffer, outputAttachment.image, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, // Or SHADER_READ_ONLY + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + outRange + ); + uf::renderer::Texture::setImageLayout( + commandBuffer, scratchAttachment.image, + VK_IMAGE_LAYOUT_GENERAL, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + scratchRange + ); + + VkImageCopy copy = {}; + copy.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copy.srcSubresource.baseArrayLayer = 1; + copy.srcSubresource.layerCount = 1; + copy.srcSubresource.mipLevel = 0; + + copy.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copy.dstSubresource.baseArrayLayer = 0; + copy.dstSubresource.layerCount = 1; + copy.dstSubresource.mipLevel = 0; + + copy.extent = { width, height, 1 }; + + vkCmdCopyImage( + commandBuffer, + outputAttachment.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + scratchAttachment.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, © + ); + + uf::renderer::Texture::setImageLayout( + commandBuffer, scratchAttachment.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + scratchRange + ); + } + #endif device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::END, "end" ); VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer)); diff --git a/engine/src/ext/vulkan/rendermodes/rendertarget.cpp b/engine/src/ext/vulkan/rendermodes/rendertarget.cpp index 93249b33..ff48b6b8 100644 --- a/engine/src/ext/vulkan/rendermodes/rendertarget.cpp +++ b/engine/src/ext/vulkan/rendermodes/rendertarget.cpp @@ -11,6 +11,7 @@ #include #include #include +#include const uf::stl::string ext::vulkan::RenderTargetRenderMode::getType() const { return "RenderTarget"; @@ -212,6 +213,19 @@ void ext::vulkan::RenderTargetRenderMode::initialize( Device& device ) { } } } + + if ( ext::openvr::enabled && metadata.json["vr"].as() ) { + uf::stl::string vertexShaderFilename = uf::io::resolveURI(::fmt::format("{}/shaders/display/vr/{}.vert.spv", uf::io::root, metadata.json["stereo"].as(metadata.views == 2) ? "stereo" : "flat")); + uf::stl::string fragmentShaderFilename = uf::io::resolveURI(::fmt::format("{}/shaders/display/vr/{}.frag.spv", uf::io::root, metadata.json["stereo"].as(metadata.views == 2) ? "stereo" : "flat")); + + blitter.material.attachShader(vertexShaderFilename, uf::renderer::enums::Shader::VERTEX, "vr"); + blitter.material.attachShader(fragmentShaderFilename, uf::renderer::enums::Shader::FRAGMENT, "vr"); + + { + auto& shader = blitter.material.getShader("fragment", "vr"); + shader.aliasAttachment("color", this); + } + } } this->build(true); @@ -266,6 +280,12 @@ void ext::vulkan::RenderTargetRenderMode::build( bool resized ) { descriptor.bind.point = VK_PIPELINE_BIND_POINT_COMPUTE; blitter.update( descriptor ); } + + if ( ext::openvr::enabled && metadata.json["vr"].as() ) { + auto descriptor = blitter.descriptor; + descriptor.pipeline = "vr"; + blitter.update( descriptor ); + } } void ext::vulkan::RenderTargetRenderMode::tick() { diff --git a/engine/src/ext/vulkan/rendermodes/vr.cpp b/engine/src/ext/vulkan/rendermodes/vr.cpp new file mode 100644 index 00000000..c45b7673 --- /dev/null +++ b/engine/src/ext/vulkan/rendermodes/vr.cpp @@ -0,0 +1,250 @@ +#if UF_USE_VULKAN + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + uf::Camera camera; +} + +const uf::stl::string ext::vulkan::VrRenderMode::getType() const { + return "VR"; +} + +void ext::vulkan::VrRenderMode::initialize(Device& device) { + uint32_t width = this->width > 0 ? this->width : (ext::vulkan::settings::width * this->scale); + uint32_t height = this->height > 0 ? this->height : (ext::vulkan::settings::height * this->scale); + + metadata.pipeline = "vr"; + + ext::vulkan::RenderMode::initialize( device ); + renderTarget.device = &device; + + struct { + size_t left, right, depth; + } attachments = {}; + + attachments.left = renderTarget.attach(RenderTarget::Attachment::Descriptor{ + /*.format =*/ ext::vulkan::settings::pipelines::hdr ? enums::Format::HDR : enums::Format::SDR, + /*.layout = */ VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + /*.usage =*/ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + /*.blend =*/ true, + /*.samples =*/ 1, + }); + attachments.right = renderTarget.attach(RenderTarget::Attachment::Descriptor{ + /*.format =*/ ext::vulkan::settings::pipelines::hdr ? enums::Format::HDR : enums::Format::SDR, + /*.layout = */ VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + /*.usage =*/ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + /*.blend =*/ true, + /*.samples =*/ 1, + }); + attachments.depth = renderTarget.attach(RenderTarget::Attachment::Descriptor{ + /*.format = */ext::vulkan::settings::formats::depth, + /*.layout = */VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + /*.usage = */VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT, + /*.blend = */false, + /*.samples = */1, + }); + + metadata.attachments["left"] = attachments.left; + metadata.attachments["right"] = attachments.right; + metadata.attachments["output"] = attachments.left; + + renderTarget.addPass( + /*.*/ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + /*.colors =*/ { attachments.left, attachments.right }, + /*.inputs =*/ {}, + /*.resolve =*/{}, + /*.depth = */ attachments.depth, + /*.layer = */0, + /*.autoBuildPipeline =*/ true + ); + + renderTarget.initialize( device ); + + blitter.process = false; +} + +void ext::vulkan::VrRenderMode::createCommandBuffers(const uf::stl::vector& graphics) { + uint32_t width = this->width > 0 ? this->width : (ext::vulkan::settings::width * this->scale); + uint32_t height = this->height > 0 ? this->height : (ext::vulkan::settings::height * this->scale); + + VkCommandBufferBeginInfo cmdBufInfo = ext::vulkan::initializers::commandBufferBeginInfo(); + cmdBufInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; + + VkImageMemoryBarrier imageMemoryBarrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; // ext::vulkan::device.queueFamilyIndices.graphics; //VK_QUEUE_FAMILY_IGNORED + imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; // ext::vulkan::device.queueFamilyIndices.graphics; //VK_QUEUE_FAMILY_IGNORED + imageMemoryBarrier.subresourceRange.baseMipLevel = 0; + imageMemoryBarrier.subresourceRange.levelCount = 1; + imageMemoryBarrier.subresourceRange.baseArrayLayer = 0; + imageMemoryBarrier.subresourceRange.layerCount = 1; + imageMemoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + + uf::stl::vector layers = ext::vulkan::getRenderModes(uf::stl::vector{"Deferred", "Compute:RT", "RenderTarget"}, false); + + float depthClear = uf::matrix::reverseInfiniteProjection ? 0.0f : 1.0f; + uf::stl::vector clearValues; + for ( auto& attachment : renderTarget.attachments ) { + pod::Vector4f clearColor = {0, 0, 0, 0}; + auto& clearValue = clearValues.emplace_back(); + if ( attachment.descriptor.usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT ) { + clearValue.color.float32[0] = clearColor[0]; + clearValue.color.float32[1] = clearColor[1]; + clearValue.color.float32[2] = clearColor[2]; + clearValue.color.float32[3] = clearColor[3]; + } else if ( attachment.descriptor.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT ) { + clearValue.depthStencil = { depthClear, 0 }; + } + } + + auto& commands = getCommands(); + for (size_t frame = 0; frame < commands.size(); ++frame) { + auto& commandBuffer = commands[frame]; + VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &cmdBufInfo)); + device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::BEGIN, "begin" ); + { + + VkRenderPassBeginInfo renderPassBeginInfo = {}; + renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassBeginInfo.pNext = nullptr; + renderPassBeginInfo.renderArea.offset.x = 0; + renderPassBeginInfo.renderArea.offset.y = 0; + renderPassBeginInfo.renderArea.extent.width = width; + renderPassBeginInfo.renderArea.extent.height = height; + renderPassBeginInfo.clearValueCount = clearValues.size(); + renderPassBeginInfo.pClearValues = &clearValues[0]; + renderPassBeginInfo.renderPass = renderTarget.renderPass; + renderPassBeginInfo.framebuffer = renderTarget.framebuffers[frame]; + + // Update dynamic viewport state + VkViewport viewport = {}; + viewport.width = (float) width; + viewport.height = (float) height; + viewport.minDepth = (float) 0.0f; + viewport.maxDepth = (float) 1.0f; + + // Update dynamic scissor state + VkRect2D scissor = {}; + scissor.extent.width = width; + scissor.extent.height = height; + scissor.offset.x = 0; + scissor.offset.y = 0; + + size_t subpasses = renderTarget.passes.size(); + size_t currentPass = 0; + + // pre-renderpass commands + VK_COMMAND_BUFFER_CALLBACK( CALLBACK_BEGIN, commandBuffer, frame, { + device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, "callback[begin]" ); + } ); + + { + device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::BEGIN, "renderPass[begin]" ); + vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + for ( auto currentPass = 0; currentPass < 2; ++currentPass ) { + size_t currentDraw = 0; + for ( auto _ : layers ) { + RenderTargetRenderMode* layer = (RenderTargetRenderMode*) _; + auto& blitter = layer->blitter; + if ( !blitter.initialized || !blitter.process ) continue; + UF_MSG_DEBUG("currentPass={}, frame={}, renderMode name={}, type={}", currentPass, frame, layer->getName(), layer->getType()); + ext::vulkan::GraphicDescriptor descriptor = bindGraphicDescriptor(blitter.descriptor); + device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, ::fmt::format("blitter[{}: {}]", layer->getName(), layer->getType()) ); + blitter.record(commandBuffer, descriptor, currentPass, currentDraw++, frame); + } + } + device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::END, "renderPass[end]" ); + vkCmdEndRenderPass(commandBuffer); + } + + + // post-renderpass commands + VK_COMMAND_BUFFER_CALLBACK( CALLBACK_END, commandBuffer, frame, { + device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, "callback[end]" ); + } ); + } + device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::END, "end" ); + VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer)); + } +} + +void ext::vulkan::VrRenderMode::build( bool resized ) { + ext::vulkan::RenderMode::build(); + + uint32_t width = this->width > 0 ? this->width : (ext::vulkan::settings::width * this->scale); + uint32_t height = this->height > 0 ? this->height : (ext::vulkan::settings::height * this->scale); + + // (re)initialize pipelines + if ( blitter.process ) { + blitter.descriptor.bind.width = width; + blitter.descriptor.bind.height = height; + + blitter.update( blitter.descriptor ); + } +} +void ext::vulkan::VrRenderMode::tick() { + ext::vulkan::RenderMode::tick(); + + bool resized = this->width == 0 && this->height == 0 && (ext::vulkan::states::resized || this->resized); + bool rebuild = resized || ext::vulkan::states::rebuild || this->rebuild; + + uint32_t width = this->width > 0 ? this->width : (ext::vulkan::settings::width * this->scale); + uint32_t height = this->height > 0 ? this->height : (ext::vulkan::settings::height * this->scale); + + if ( resized ) { + this->resized = false; + renderTarget.initialize( *renderTarget.device ); + } + if ( rebuild && blitter.process ) { + this->build( resized ); + } + + ::camera.update(); + this->updateBuffer( (const void*) &::camera.data().viewport, sizeof(pod::Camera::Viewports), metadata.buffers["camera"] ); +} + +void ext::vulkan::VrRenderMode::destroy() { + ext::vulkan::RenderMode::destroy(); +} + +void ext::vulkan::VrRenderMode::render() { + if ( this->commands.container().empty() ) return; + + auto& commands = getCommands( this->mostRecentCommandPoolId ); + + VK_COMMAND_BUFFER_CALLBACK( EXECUTE_BEGIN, VkCommandBuffer{}, 0, {} ); + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.pWaitDstStageMask = NULL; + submitInfo.pWaitSemaphores = NULL; + submitInfo.waitSemaphoreCount = 0; + submitInfo.pSignalSemaphores = NULL; + submitInfo.signalSemaphoreCount = 0; + submitInfo.pCommandBuffers = &commands[states::currentBuffer]; + submitInfo.commandBufferCount = 1; + + VkQueue queue = device->getQueue( QueueEnum::GRAPHICS ); + VkResult res = vkQueueSubmit( queue, 1, &submitInfo, /*VK_NULL_HANDLE*/fences[states::currentBuffer]); + VK_CHECK_QUEUE_CHECKPOINT( queue, res ); + VK_COMMAND_BUFFER_CALLBACK( EXECUTE_END, VkCommandBuffer{}, 0, {} ); + + ext::openvr::submit(); + + this->executed = true; +} + +#endif \ No newline at end of file diff --git a/engine/src/ext/vulkan/rendertarget.cpp b/engine/src/ext/vulkan/rendertarget.cpp index bec95e97..e064e473 100644 --- a/engine/src/ext/vulkan/rendertarget.cpp +++ b/engine/src/ext/vulkan/rendertarget.cpp @@ -27,6 +27,7 @@ size_t ext::vulkan::RenderTarget::attach( const Attachment::Descriptor& descript size_t index = attachments.size(); uint32_t width = this->width > 0 ? this->width : (ext::vulkan::settings::width * this->scale); uint32_t height = this->height > 0 ? this->height : (ext::vulkan::settings::height * this->scale); + uint8_t layers = descriptor.layers > 0 ? descriptor.layers : this->views; if ( attachment ) { for ( auto& view : attachment->views ) { @@ -66,12 +67,13 @@ size_t ext::vulkan::RenderTarget::attach( const Attachment::Descriptor& descript attachment->descriptor.width = width; attachment->descriptor.height = height; + attachment->descriptor.layers = layers; if ( attachment->descriptor.mips <= 1 ) { attachment->descriptor.mips = 1; } else { attachment->descriptor.mips = uf::vector::mips( pod::Vector2ui{ width, height } ); } - attachment->views.resize(this->views * attachment->descriptor.mips); + attachment->views.resize(layers * attachment->descriptor.mips); bool isSwapchain = attachment->descriptor.layout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; bool isDepth = attachment->descriptor.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; @@ -86,7 +88,7 @@ size_t ext::vulkan::RenderTarget::attach( const Attachment::Descriptor& descript subresourceRange.levelCount = 1; subresourceRange.baseArrayLayer = 0; subresourceRange.aspectMask = isDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; - subresourceRange.layerCount = this->views; + subresourceRange.layerCount = layers; auto commandBuffer = device->fetchCommandBuffer(uf::renderer::QueueEnum::GRAPHICS); for ( size_t i = 0; i < ext::vulkan::swapchain.buffers; ++i ) { @@ -123,13 +125,13 @@ size_t ext::vulkan::RenderTarget::attach( const Attachment::Descriptor& descript imageCreateInfo.format = attachment->descriptor.format; imageCreateInfo.extent = { width, height, 1 }; imageCreateInfo.mipLevels = attachment->descriptor.mips; - imageCreateInfo.arrayLayers = this->views; + imageCreateInfo.arrayLayers = layers; imageCreateInfo.samples = ext::vulkan::sampleCount( attachment->descriptor.samples ); imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageCreateInfo.usage = attachment->descriptor.usage; imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - if ( this->views == 6 ) { + if ( layers == 6 ) { imageCreateInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; } @@ -152,8 +154,8 @@ size_t ext::vulkan::RenderTarget::attach( const Attachment::Descriptor& descript VkImageViewCreateInfo imageView = {}; imageView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; imageView.viewType = VK_IMAGE_VIEW_TYPE_2D; - if ( this->views > 1 ) { - imageView.viewType = this->views == 6 ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D_ARRAY; + if ( layers > 1 ) { + imageView.viewType = layers == 6 ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D_ARRAY; } imageView.format = attachment->descriptor.format; imageView.subresourceRange = {}; @@ -161,7 +163,7 @@ size_t ext::vulkan::RenderTarget::attach( const Attachment::Descriptor& descript imageView.subresourceRange.baseMipLevel = 0; imageView.subresourceRange.baseArrayLayer = 0; imageView.subresourceRange.levelCount = attachment->descriptor.mips; - imageView.subresourceRange.layerCount = this->views; + imageView.subresourceRange.layerCount = layers; imageView.image = attachment->image; VK_CHECK_RESULT(vkCreateImageView(*device, &imageView, nullptr, &attachment->view)); VK_REGISTER_HANDLE( attachment->view ); @@ -173,7 +175,7 @@ size_t ext::vulkan::RenderTarget::attach( const Attachment::Descriptor& descript } size_t viewIndex = 0; - for ( size_t layer = 0; layer < this->views; ++layer ) { + for ( size_t layer = 0; layer < layers; ++layer ) { imageView.viewType = VK_IMAGE_VIEW_TYPE_2D; imageView.subresourceRange.levelCount = 1; imageView.subresourceRange.layerCount = 1; @@ -210,7 +212,7 @@ size_t ext::vulkan::RenderTarget::attach( const Attachment::Descriptor& descript subresourceRange.baseMipLevel = 0; subresourceRange.baseArrayLayer = 0; subresourceRange.levelCount = attachment->descriptor.mips; - subresourceRange.layerCount = this->views; + subresourceRange.layerCount = layers; subresourceRange.aspectMask = isDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; uf::renderer::Texture::setImageLayout( commandBuffer, attachment->image, VK_IMAGE_LAYOUT_UNDEFINED, attachment->descriptor.layout, subresourceRange ); device->flushCommandBuffer(commandBuffer, uf::renderer::QueueEnum::GRAPHICS); @@ -493,7 +495,7 @@ void ext::vulkan::RenderTarget::destroy() { attachment.view = VK_NULL_HANDLE; } } - for ( size_t i = 0; i < this->views; ++i ) { + for ( size_t i = 0; i < attachment.views.size(); ++i ) { vkDestroyImageView(*device, attachment.views[i], nullptr); VK_UNREGISTER_HANDLE( attachment.views[i] ); } diff --git a/engine/src/utils/camera/camera.cpp b/engine/src/utils/camera/camera.cpp index a47a9ee0..e46095ce 100644 --- a/engine/src/utils/camera/camera.cpp +++ b/engine/src/utils/camera/camera.cpp @@ -13,8 +13,8 @@ pod::Vector3f uf::camera::eye( const pod::Camera& camera, int i ) { if ( i < 0 ) i = 0; pod::Vector3f position = uf::transform::flatten( camera.transform ).position; #if UF_USE_OPENVR - if ( camera.stereoscopic && ext::openvr::context ) { - position += ext::openvr::hmdPosition( eye == 0 ? vr::Eye_Left : vr::Eye_Right ); + if ( camera.stereoscopic && ext::openvr::enabled ) { + position += ext::openvr::hmdPosition( i == 0 ? vr::Eye_Left : vr::Eye_Right ); } #endif return position; @@ -39,10 +39,8 @@ void uf::camera::projection( pod::Camera& camera, const pod::Matrix4f& mat, int } void uf::camera::update( pod::Camera& camera ) { #if UF_USE_OPENVR - if ( this->stereoscopic && ext::openvr::context ) { - camera.transform.orientation = uf::quaternion::identity(); + if ( camera.stereoscopic && ext::openvr::enabled ) { auto view = uf::matrix::inverse( uf::transform::model( camera.transform ) ); - camera.transform.orientation = ext::openvr::hmdQuaternion(); uf::camera::view( camera, ext::openvr::hmdViewMatrix(vr::Eye_Left, view ), 0 ); uf::camera::view( camera, ext::openvr::hmdViewMatrix(vr::Eye_Right, view ), 1 ); diff --git a/makefiles/platforms/win64.mk b/makefiles/platforms/win64.mk index 8780c04f..fb801967 100644 --- a/makefiles/platforms/win64.mk +++ b/makefiles/platforms/win64.mk @@ -1,5 +1,5 @@ ifneq (,$(findstring -DUF_DEV_ENV,$(FLAGS))) - REQ_DEPS += meshoptimizer toml xatlas curl dc:texconv # ffx:sdk vall_e cpptrace # openvr # ncurses draco discord bullet ultralight-ux + REQ_DEPS += meshoptimizer toml xatlas curl dc:texconv ffx:sdk openvr # vall_e cpptrace # ncurses draco discord ultralight-ux FLAGS += -march=native -g # -flto # -g endif