diff --git a/examples/xrDemo.cpp b/examples/xrDemo.cpp index 8c4bfafa..648b86e5 100644 --- a/examples/xrDemo.cpp +++ b/examples/xrDemo.cpp @@ -6,10 +6,22 @@ int main() { try { Raz::Logger::setLoggingLevel(Raz::LoggingLevel::ALL); - Raz::XrContext xrContext("RaZ - XR demo"); + Raz::Application app; + Raz::World& world = app.addWorld(); - Raz::RenderSystem render(1280, 720, "RaZ"); - Raz::XrSession xrSession(xrContext); + auto& render = world.addSystem(1280, 720, "RaZ"); + Raz::Window& window = render.getWindow(); + + window.addKeyCallback(Raz::Keyboard::ESCAPE, [&app] (float) noexcept { app.quit(); }); + window.setCloseCallback([&app] () noexcept { app.quit(); }); + + world.addSystem("RaZ - XR demo"); + + world.addEntityWithComponent().addComponent(window.getWidth(), window.getHeight()); + world.addEntityWithComponent(Raz::Vec3f(0.f, 0.f, -5.f)) + .addComponent(Raz::Mesh(Raz::AABB(Raz::Vec3f(-1.f), Raz::Vec3f(1.f)))); + + app.run(); } catch (const std::exception& exception) { Raz::Logger::error("Exception occurred: "s + exception.what()); } diff --git a/include/RaZ/RaZ.hpp b/include/RaZ/RaZ.hpp index 4c8f73d2..a8e549cf 100644 --- a/include/RaZ/RaZ.hpp +++ b/include/RaZ/RaZ.hpp @@ -95,6 +95,7 @@ #include "Utils/TypeUtils.hpp" #include "XR/XrContext.hpp" #include "XR/XrSession.hpp" +#include "XR/XrSystem.hpp" using namespace Raz::Literals; diff --git a/include/RaZ/XR/XrContext.hpp b/include/RaZ/XR/XrContext.hpp index 5f4c1703..1fdbb630 100644 --- a/include/RaZ/XR/XrContext.hpp +++ b/include/RaZ/XR/XrContext.hpp @@ -14,6 +14,7 @@ namespace Raz { class XrContext { friend class XrSession; + friend class XrSystem; public: explicit XrContext(const std::string& appName); diff --git a/include/RaZ/XR/XrSession.hpp b/include/RaZ/XR/XrSession.hpp index dd4b836c..b0a74cc1 100644 --- a/include/RaZ/XR/XrSession.hpp +++ b/include/RaZ/XR/XrSession.hpp @@ -15,6 +15,8 @@ namespace Raz { class XrContext; class XrSession { + friend class XrSystem; + public: explicit XrSession(const XrContext& context); diff --git a/include/RaZ/XR/XrSystem.hpp b/include/RaZ/XR/XrSystem.hpp new file mode 100644 index 00000000..cf112cc7 --- /dev/null +++ b/include/RaZ/XR/XrSystem.hpp @@ -0,0 +1,43 @@ +#pragma once + +#ifndef RAZ_XRSYSTEM_HPP +#define RAZ_XRSYSTEM_HPP + +#include "RaZ/System.hpp" +#include "RaZ/XR/XrContext.hpp" +#include "RaZ/XR/XrSession.hpp" + +#include + +struct XrEventDataSessionStateChanged; +struct XrViewConfigurationView; + +namespace Raz { + +class XrSystem final : public System { +public: + explicit XrSystem(const std::string& appName); + + bool update(const FrameTimeInfo&) override; + + ~XrSystem() override; + +private: + void recoverViewConfigurations(); + void recoverEnvironmentBlendModes(); + bool processSessionStateChanged(const XrEventDataSessionStateChanged& sessionStateChanged); + + XrContext m_context; + XrSession m_session; + + std::vector m_viewConfigTypes; + unsigned int m_viewConfigType {}; + std::vector m_viewConfigViews; + + std::vector m_environmentBlendModes; + unsigned int m_environmentBlendMode {}; +}; + +} // namespace Raz + +#endif // RAZ_XRSYSTEM_HPP diff --git a/src/RaZ/XR/XrSystem.cpp b/src/RaZ/XR/XrSystem.cpp new file mode 100644 index 00000000..36fa33ed --- /dev/null +++ b/src/RaZ/XR/XrSystem.cpp @@ -0,0 +1,220 @@ +#include "RaZ/Math/Vector.hpp" +#include "RaZ/Utils/Logger.hpp" +#include "RaZ/XR/XrContext.hpp" +#include "RaZ/XR/XrSystem.hpp" + +#include "openxr/openxr.h" + +#include +#include + +namespace Raz { + +namespace { + +const char* getResultStr(XrInstance instance, XrResult result) { + static std::array errorStr {}; + xrResultToString(instance, result, errorStr.data()); + return errorStr.data(); +} + +std::string getErrorStr(const std::string& errorMsg, XrResult result, XrInstance instance) { + return "[XrSystem] " + errorMsg + ": " + getResultStr(instance, result) + " (" + std::to_string(result) + ')'; +} + +void checkLog(XrResult result, const std::string& errorMsg, XrInstance instance) { + if (XR_SUCCEEDED(result)) + return; + + Logger::error(getErrorStr(errorMsg, result, instance)); +} + +bool pollNextEvent(XrInstance instance, XrEventDataBuffer& eventData) { + eventData = {}; + eventData.type = XR_TYPE_EVENT_DATA_BUFFER; + + return (xrPollEvent(instance, &eventData) == XR_SUCCESS); +} + +void processEventData(const XrEventDataEventsLost& eventsLost) { + Logger::info("[XrSystem] " + std::to_string(eventsLost.lostEventCount) + " events lost"); +} + +void processEventData(const XrEventDataInstanceLossPending& instanceLossPending) { + // After the period of time specified by lossTime, the application can try recreating an instance again + // See: https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrEventDataInstanceLossPending.html#_description + Logger::info("[XrSystem] Instance loss pending at: " + std::to_string(instanceLossPending.lossTime)); +} + +void processEventData(const XrEventDataInteractionProfileChanged& interactionProfileChanged, ::XrSession session) { + Logger::info("[XrSystem] Interaction profile changed for " + + std::string(interactionProfileChanged.session != session ? "unknown" : "current") + + " session"); +} + +void processEventData(const XrEventDataReferenceSpaceChangePending& referenceSpaceChangePending, ::XrSession session) { + Logger::info("[XrSystem] Reference space changed pending for " + + std::string(referenceSpaceChangePending.session != session ? "unknown" : "current") + + " session"); +} + +} // namespace + +XrSystem::XrSystem(const std::string& appName) : m_context(appName), m_session(m_context) { + recoverViewConfigurations(); + m_session.createSwapchains(m_viewConfigViews); + recoverEnvironmentBlendModes(); +} + +bool XrSystem::update(const FrameTimeInfo&) { + XrEventDataBuffer eventData {}; + + while (pollNextEvent(m_context.m_instance, eventData)) { + switch (eventData.type) { + case XR_TYPE_EVENT_DATA_EVENTS_LOST: + processEventData(*reinterpret_cast(&eventData)); + break; + + case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: + processEventData(*reinterpret_cast(&eventData)); + m_session.m_isRunning = false; + return false; + + case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: + processEventData(*reinterpret_cast(&eventData), m_session.m_handle); + break; + + case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: + processEventData(*reinterpret_cast(&eventData), m_session.m_handle); + break; + + case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: + processSessionStateChanged(*reinterpret_cast(&eventData)); + break; + + default: + break; + } + } + + return true; +} + +XrSystem::~XrSystem() = default; + +void XrSystem::recoverViewConfigurations() { + uint32_t viewConfigCount {}; + checkLog(xrEnumerateViewConfigurations(m_context.m_instance, m_context.m_systemId, 0, &viewConfigCount, nullptr), + "Failed to get view configuration count", + m_context.m_instance); + m_viewConfigTypes.resize(viewConfigCount); + checkLog(xrEnumerateViewConfigurations(m_context.m_instance, m_context.m_systemId, viewConfigCount, &viewConfigCount, + reinterpret_cast(m_viewConfigTypes.data())), + "Failed to enumerate view configurations", + m_context.m_instance); + + for (const XrViewConfigurationType viewConfigType : { XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO }) { + if (std::find(m_viewConfigTypes.cbegin(), m_viewConfigTypes.cend(), viewConfigType) == m_viewConfigTypes.cend()) + continue; + + m_viewConfigType = viewConfigType; + break; + } + + if (m_viewConfigType == 0) { + Logger::warn("[XrSystem] Failed to find a view configuration type; defaulting to XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO"); + m_viewConfigType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + } + + uint32_t viewConfigViewCount {}; + checkLog(xrEnumerateViewConfigurationViews(m_context.m_instance, m_context.m_systemId, static_cast(m_viewConfigType), + 0, &viewConfigViewCount, nullptr), + "Failed to get view configuration view count", + m_context.m_instance); + m_viewConfigViews.resize(viewConfigViewCount, { XR_TYPE_VIEW_CONFIGURATION_VIEW }); + checkLog(xrEnumerateViewConfigurationViews(m_context.m_instance, m_context.m_systemId, static_cast(m_viewConfigType), + viewConfigViewCount, &viewConfigViewCount, + m_viewConfigViews.data()), + "Failed to enumerate view configuration views", + m_context.m_instance); + +#if !defined(NDEBUG) || defined(RAZ_FORCE_DEBUG_LOG) + { + std::string viewConfigViewsMsg = "[XrSystem] View configuration views:"; + for (const XrViewConfigurationView& viewConfigView : m_viewConfigViews) { + viewConfigViewsMsg += "\n View:" + "\n Recom. image rect width: " + std::to_string(viewConfigView.recommendedImageRectWidth) + + "\n Max. image rect width: " + std::to_string(viewConfigView.maxImageRectWidth) + + "\n Recom. image rect height: " + std::to_string(viewConfigView.recommendedImageRectHeight) + + "\n Max. image rect height: " + std::to_string(viewConfigView.maxImageRectHeight) + + "\n Recom. swapchain sample count: " + std::to_string(viewConfigView.recommendedSwapchainSampleCount) + + "\n Max. swapchain sample count: " + std::to_string(viewConfigView.maxSwapchainSampleCount); + } + Logger::debug(viewConfigViewsMsg); + } +#endif +} + +void XrSystem::recoverEnvironmentBlendModes() { + uint32_t environmentBlendModeCount {}; + checkLog(xrEnumerateEnvironmentBlendModes(m_context.m_instance, m_context.m_systemId, static_cast(m_viewConfigType), + 0, &environmentBlendModeCount, nullptr), + "Failed to get environment blend mode count", + m_context.m_instance); + m_environmentBlendModes.resize(environmentBlendModeCount); + checkLog(xrEnumerateEnvironmentBlendModes(m_context.m_instance, m_context.m_systemId, static_cast(m_viewConfigType), + environmentBlendModeCount, &environmentBlendModeCount, + reinterpret_cast(m_environmentBlendModes.data())), + "Failed to enumerate environment blend modes", + m_context.m_instance); + + for (const XrEnvironmentBlendMode environmentBlendMode : { XR_ENVIRONMENT_BLEND_MODE_OPAQUE, XR_ENVIRONMENT_BLEND_MODE_ADDITIVE }) { + if (std::find(m_environmentBlendModes.cbegin(), m_environmentBlendModes.cend(), environmentBlendMode) == m_environmentBlendModes.cend()) + continue; + + m_environmentBlendMode = environmentBlendMode; + break; + } + + if (m_environmentBlendMode == 0) { + Logger::warn("Failed to find a compatible blend mode; defaulting to XR_ENVIRONMENT_BLEND_MODE_OPAQUE"); + m_environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; + } +} + +bool XrSystem::processSessionStateChanged(const XrEventDataSessionStateChanged& sessionStateChanged) { + if (sessionStateChanged.session != m_session.m_handle) { + Logger::info("[XrSystem] Data session state changed for unknown session"); + return true; + } + + switch (sessionStateChanged.state) { + case XR_SESSION_STATE_READY: + m_session.begin(m_viewConfigType); + m_session.m_isRunning = true; + break; + + case XR_SESSION_STATE_STOPPING: + m_session.end(); + m_session.m_isRunning = false; + break; + + case XR_SESSION_STATE_LOSS_PENDING: + // TODO: "It's possible to try to reestablish an XrInstance and XrSession" + m_session.m_isRunning = false; + return false; + + case XR_SESSION_STATE_EXITING: + m_session.m_isRunning = false; + return false; + + default: + break; + } + + m_session.m_state = sessionStateChanged.state; + + return true; +} + +} // namespace Raz