diff --git a/examples/xrDemo.cpp b/examples/xrDemo.cpp index 2765e667..7582b600 100644 --- a/examples/xrDemo.cpp +++ b/examples/xrDemo.cpp @@ -11,8 +11,15 @@ int main() { Raz::Application app; Raz::World& world = app.addWorld(); - // Dimensions of [2468, 2584] (definition of each view of the Meta Quest 2) divided by 3 - auto& render = world.addSystem(823, 861, "RaZ", Raz::WindowSetting::NON_RESIZABLE); + auto& xr = world.addSystem("RaZ - XR demo"); + + // The dimensions given to the window are of one third of the XR device's views. This works well for e.g. the Meta Quest 2, yielding dimensions + // of [822; 861]. Ideally, this should be more flexible and make a window of a good enough size according to what the monitor allows + // In an XR application, the window shouldn't be resizable: + // - At the time of writing, resizing it will also resize the rendering viewport, breaking XR rendering (which requires fixed dimensions) + // - As the visualization depends on what's rendered from the XR device, which has its own definition for each view, the aspect ratio should be + // maintained to preserve a clean, undistorted image + auto& render = world.addSystem(xr.getOptimalViewWidth() / 3, xr.getOptimalViewHeight() / 3, "RaZ", Raz::WindowSetting::NON_RESIZABLE); Raz::Window& window = render.getWindow(); @@ -20,7 +27,6 @@ int main() { window.setCloseCallback([&app] () noexcept { app.quit(); }); // Enabling XR in the render system changes the render viewport's size according to what the detected XR device expects - auto& xr = world.addSystem("RaZ - XR demo"); render.enableXr(xr); // In an XR workflow, the camera is optional; its parameters aren't technically used, but its transform is diff --git a/include/RaZ/XR/XrSession.hpp b/include/RaZ/XR/XrSession.hpp index 87a41118..c79a2074 100644 --- a/include/RaZ/XR/XrSession.hpp +++ b/include/RaZ/XR/XrSession.hpp @@ -4,7 +4,6 @@ #define RAZ_XRSESSION_HPP #include "RaZ/Math/Angle.hpp" -#include "RaZ/Render/RenderPass.hpp" using XrSession = struct XrSession_T*; struct XrInstance_T; @@ -15,13 +14,8 @@ struct XrViewConfigurationView; namespace Raz { -template -class Quaternion; using Quaternionf = Quaternion; - -template -class Vector; -using Vec3f = Vector; +using Vec3f = Vector; class Texture2D; class XrContext; @@ -56,11 +50,12 @@ class XrSession { struct RenderLayerInfo; enum class SwapchainType : uint8_t; + void initialize(uint64_t systemId); + void createReferenceSpace(); + void destroyReferenceSpace(); void createSwapchains(const std::vector& viewConfigViews); void destroySwapchains(); void createSwapchainImages(XrSwapchain swapchain, SwapchainType swapchainType); - void createReferenceSpace(); - void destroyReferenceSpace(); bool renderLayer(RenderLayerInfo& layerInfo, const std::vector& viewConfigViews, unsigned int viewConfigType, @@ -72,13 +67,11 @@ class XrSession { int m_state {}; bool m_isRunning = false; + XrSpace m_localSpace {}; + std::vector m_colorSwapchains; std::vector m_depthSwapchains; std::unordered_map> m_swapchainImages; - - XrSpace m_localSpace {}; - - RenderPass m_swapchainCopyPass {}; }; } // namespace Raz diff --git a/include/RaZ/XR/XrSystem.hpp b/include/RaZ/XR/XrSystem.hpp index 684322de..d18d5e3d 100644 --- a/include/RaZ/XR/XrSystem.hpp +++ b/include/RaZ/XR/XrSystem.hpp @@ -20,16 +20,18 @@ class XrSystem final : public System { public: explicit XrSystem(const std::string& appName); + unsigned int getOptimalViewWidth() const { return m_optimalViewWidth; } + unsigned int getOptimalViewHeight() const { return m_optimalViewHeight; } + bool update(const FrameTimeInfo&) override; ~XrSystem() override; private: - Vec2u recoverOptimalViewSize() const; - bool renderFrame(const ViewRenderFunc& viewRenderFunc); - void recoverViewConfigurations(); void recoverEnvironmentBlendModes(); + void initializeSession(); + bool renderFrame(const ViewRenderFunc& viewRenderFunc); bool processSessionStateChanged(const XrEventDataSessionStateChanged& sessionStateChanged); XrContext m_context; @@ -38,6 +40,8 @@ class XrSystem final : public System { std::vector m_viewConfigTypes; unsigned int m_viewConfigType {}; std::vector m_viewConfigViews; + unsigned int m_optimalViewWidth {}; + unsigned int m_optimalViewHeight {}; std::vector m_environmentBlendModes; unsigned int m_environmentBlendMode {}; diff --git a/src/RaZ/Render/RenderSystem.cpp b/src/RaZ/Render/RenderSystem.cpp index 48115469..87e0a993 100644 --- a/src/RaZ/Render/RenderSystem.cpp +++ b/src/RaZ/Render/RenderSystem.cpp @@ -26,8 +26,8 @@ void RenderSystem::setCubemap(Cubemap&& cubemap) { void RenderSystem::enableXr(XrSystem& xrSystem) { m_xrSystem = &xrSystem; - const Vec2u optimalViewSize = xrSystem.recoverOptimalViewSize(); - resizeViewport(optimalViewSize.x(), optimalViewSize.y()); + xrSystem.initializeSession(); + resizeViewport(xrSystem.getOptimalViewWidth(), xrSystem.getOptimalViewHeight()); } #endif diff --git a/src/RaZ/XR/XrContext.cpp b/src/RaZ/XR/XrContext.cpp index a94566b0..5bc0986b 100644 --- a/src/RaZ/XR/XrContext.cpp +++ b/src/RaZ/XR/XrContext.cpp @@ -11,6 +11,7 @@ #include "openxr/openxr.h" #include "openxr/openxr_platform.h" + #include "tracy/Tracy.hpp" #include diff --git a/src/RaZ/XR/XrSession.cpp b/src/RaZ/XR/XrSession.cpp index 61117654..b11c0629 100644 --- a/src/RaZ/XR/XrSession.cpp +++ b/src/RaZ/XR/XrSession.cpp @@ -1,6 +1,6 @@ -#include "RaZ/Data/Color.hpp" #include "RaZ/Math/Quaternion.hpp" #include "RaZ/Render/Renderer.hpp" +#include "RaZ/Render/RenderPass.hpp" #include "RaZ/Utils/Logger.hpp" #include "RaZ/XR/XrContext.hpp" #include "RaZ/XR/XrSession.hpp" @@ -158,61 +158,9 @@ enum class XrSession::SwapchainType : uint8_t { DEPTH }; -XrSession::XrSession(const XrContext& context) : m_instance{ context.m_instance }, - m_swapchainCopyPass(FragmentShader::loadFromSource(swapchainCopySource), "Swapchain copy pass") { - ZoneScopedN("XrSession::XrSession"); - - Logger::debug("[XrSession] Creating session..."); - +XrSession::XrSession(const XrContext& context) : m_instance{ context.m_instance } { if (m_instance == XR_NULL_HANDLE) throw std::runtime_error("[XrSession] The XR instance must be valid"); - - if (!Renderer::isInitialized()) - throw std::runtime_error("[XrSession] The renderer must be initialized"); - - PFN_xrGetOpenGLGraphicsRequirementsKHR xrGetOpenGLGraphicsRequirementsKHR {}; - checkLog(xrGetInstanceProcAddr(m_instance, - "xrGetOpenGLGraphicsRequirementsKHR", - reinterpret_cast(&xrGetOpenGLGraphicsRequirementsKHR)), - "Failed to get OpenGL graphics requirements get function", - m_instance); - XrGraphicsRequirementsOpenGLKHR graphicsRequirements {}; - graphicsRequirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR; - checkLog(xrGetOpenGLGraphicsRequirementsKHR(m_instance, context.m_systemId, &graphicsRequirements), - "Failed to get graphics requirements for OpenGL", m_instance); - - const XrVersion graphicsApiVersion = XR_MAKE_VERSION(Renderer::getMajorVersion(), Renderer::getMinorVersion(), 0); - if (graphicsRequirements.minApiVersionSupported > graphicsApiVersion) { - const uint16_t requiredMajorVersion = XR_VERSION_MAJOR(graphicsRequirements.minApiVersionSupported); - const uint16_t requiredMinorVersion = XR_VERSION_MINOR(graphicsRequirements.minApiVersionSupported); - throw std::runtime_error("[XrSession] The current OpenGL version " - + std::to_string(Renderer::getMajorVersion()) + '.' + std::to_string(Renderer::getMinorVersion()) - + " does not meet the minimum required version " - + std::to_string(requiredMajorVersion) + '.' + std::to_string(requiredMinorVersion) - + " for OpenXR"); - } - - const GraphicsBinding graphicsBinding = getGraphicsBinding(); - XrSessionCreateInfo sessionCreateInfo {}; - sessionCreateInfo.type = XR_TYPE_SESSION_CREATE_INFO; - sessionCreateInfo.next = &graphicsBinding; - sessionCreateInfo.createFlags = 0; - sessionCreateInfo.systemId = context.m_systemId; - checkThrow(xrCreateSession(m_instance, &sessionCreateInfo, &m_handle), "Failed to create session", m_instance); - - createReferenceSpace(); - - RenderShaderProgram& swapchainCopyProgram = m_swapchainCopyPass.getProgram(); - swapchainCopyProgram.setAttribute(0, "uniFinalColorBuffer"); - swapchainCopyProgram.setAttribute(1, "uniFinalDepthBuffer"); - swapchainCopyProgram.sendAttributes(); - - constexpr DrawBuffer drawBuffer = DrawBuffer::COLOR_ATTACHMENT0; - Renderer::bindFramebuffer(m_swapchainCopyPass.getFramebuffer().getIndex(), FramebufferType::DRAW_FRAMEBUFFER); - Renderer::setDrawBuffers(1, &drawBuffer); - Renderer::bindFramebuffer(0); - - Logger::debug("[XrSession] Created session"); } void XrSession::begin(unsigned int viewConfigType) { @@ -255,6 +203,7 @@ bool XrSession::renderFrame(const std::vector& viewConf // TODO: either the application should use this display time, or the application's global & delta times should be used here somehow // See: // - https://registry.khronos.org/OpenXR/specs/1.0/man/html/xrWaitFrame.html#_description + // - https://registry.khronos.org/OpenXR/specs/1.0/man/html/xrEndFrame.html#_description // - https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrTime.html // - https://registry.khronos.org/OpenXR/specs/1.0/man/html/XR_KHR_convert_timespec_time.html renderLayerInfo.predictedDisplayTime = frameState.predictedDisplayTime; @@ -292,11 +241,77 @@ XrSession::~XrSession() { Logger::debug("[XrSession] Destroyed session"); } +void XrSession::initialize(uint64_t systemId) { + ZoneScopedN("XrSession::initialize"); + + Logger::debug("[XrSession] Initializing..."); + + if (!Renderer::isInitialized()) + throw std::runtime_error("[XrSession] The renderer must be initialized"); + + PFN_xrGetOpenGLGraphicsRequirementsKHR xrGetOpenGLGraphicsRequirementsKHR {}; + checkLog(xrGetInstanceProcAddr(m_instance, + "xrGetOpenGLGraphicsRequirementsKHR", + reinterpret_cast(&xrGetOpenGLGraphicsRequirementsKHR)), + "Failed to get OpenGL graphics requirements get function", + m_instance); + XrGraphicsRequirementsOpenGLKHR graphicsRequirements {}; + graphicsRequirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR; + checkLog(xrGetOpenGLGraphicsRequirementsKHR(m_instance, systemId, &graphicsRequirements), + "Failed to get graphics requirements for OpenGL", m_instance); + + const XrVersion graphicsApiVersion = XR_MAKE_VERSION(Renderer::getMajorVersion(), Renderer::getMinorVersion(), 0); + if (graphicsRequirements.minApiVersionSupported > graphicsApiVersion) { + const uint16_t requiredMajorVersion = XR_VERSION_MAJOR(graphicsRequirements.minApiVersionSupported); + const uint16_t requiredMinorVersion = XR_VERSION_MINOR(graphicsRequirements.minApiVersionSupported); + throw std::runtime_error("[XrSession] The current OpenGL version " + + std::to_string(Renderer::getMajorVersion()) + '.' + std::to_string(Renderer::getMinorVersion()) + + " does not meet the minimum required version " + + std::to_string(requiredMajorVersion) + '.' + std::to_string(requiredMinorVersion) + + " for OpenXR"); + } + + const GraphicsBinding graphicsBinding = getGraphicsBinding(); + XrSessionCreateInfo sessionCreateInfo {}; + sessionCreateInfo.type = XR_TYPE_SESSION_CREATE_INFO; + sessionCreateInfo.next = &graphicsBinding; + sessionCreateInfo.createFlags = 0; + sessionCreateInfo.systemId = systemId; + checkThrow(xrCreateSession(m_instance, &sessionCreateInfo, &m_handle), "Failed to create session", m_instance); + + createReferenceSpace(); + + Logger::debug("[XrSession] Initialized"); +} + +void XrSession::createReferenceSpace() { + ZoneScopedN("XrSession::createReferenceSpace"); + + Logger::debug("[XrSession] Creating reference space..."); + + XrReferenceSpaceCreateInfo referenceSpaceCreateInfo {}; + referenceSpaceCreateInfo.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO; + referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL; + referenceSpaceCreateInfo.poseInReferenceSpace = { XrQuaternionf{ 0.f, 0.f, 0.f, 1.f }, XrVector3f{ 0.f, 0.f, 0.f }}; + checkLog(xrCreateReferenceSpace(m_handle, &referenceSpaceCreateInfo, &m_localSpace), "Failed to create reference space", m_instance); + + Logger::debug("[XrSession] Created reference space"); +} + +void XrSession::destroyReferenceSpace() { + Logger::debug("[XrSession] Destroying reference space..."); + checkLog(xrDestroySpace(m_localSpace), "Failed to destroy space", m_instance); + Logger::debug("[XrSession] Destroyed reference space"); +} + void XrSession::createSwapchains(const std::vector& viewConfigViews) { ZoneScopedN("XrSession::createSwapchains"); Logger::debug("[XrSession] Creating swapchains..."); + if (m_handle == nullptr) + throw std::runtime_error("[XrSession] The session has not been initialized"); + uint32_t formatCount {}; checkLog(xrEnumerateSwapchainFormats(m_handle, 0, &formatCount, nullptr), "Failed to get swapchain format count", m_instance); std::vector formats(formatCount); @@ -314,7 +329,8 @@ void XrSession::createSwapchains(const std::vector& vie XrSwapchain& depthSwapchain = m_depthSwapchains[viewIndex]; XrSwapchainCreateInfo swapchainCreateInfo {}; - swapchainCreateInfo.type = XR_TYPE_SWAPCHAIN_CREATE_INFO; + swapchainCreateInfo.type = XR_TYPE_SWAPCHAIN_CREATE_INFO; + swapchainCreateInfo.createFlags = 0; swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT; // Technically ignored with OpenGL swapchainCreateInfo.format = selectColorSwapchainFormat(formats); @@ -381,26 +397,6 @@ void XrSession::createSwapchainImages(XrSwapchain swapchain, SwapchainType swapc Logger::debug("[XrSession] Created " + typeStr + " swapchain images"); } -void XrSession::createReferenceSpace() { - ZoneScopedN("XrSession::createReferenceSpace"); - - Logger::debug("[XrSession] Creating reference space..."); - - XrReferenceSpaceCreateInfo referenceSpaceCreateInfo {}; - referenceSpaceCreateInfo.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO; - referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL; - referenceSpaceCreateInfo.poseInReferenceSpace = { XrQuaternionf{ 0.f, 0.f, 0.f, 1.f }, XrVector3f{ 0.f, 0.f, 0.f }}; - checkLog(xrCreateReferenceSpace(m_handle, &referenceSpaceCreateInfo, &m_localSpace), "Failed to create reference space", m_instance); - - Logger::debug("[XrSession] Created reference space"); -} - -void XrSession::destroyReferenceSpace() { - Logger::debug("[XrSession] Destroying reference space..."); - checkLog(xrDestroySpace(m_localSpace), "Failed to destroy space", m_instance); - Logger::debug("[XrSession] Destroyed reference space"); -} - bool XrSession::renderLayer(RenderLayerInfo& layerInfo, const std::vector& viewConfigViews, unsigned int viewConfigType, @@ -505,22 +501,40 @@ bool XrSession::renderLayer(RenderLayerInfo& layerInfo, } void XrSession::copyToSwapchains(const Texture2D& colorBuffer, const Texture2D& depthBuffer, uint32_t colorSwapchainImage, uint32_t depthSwapchainImage) { + // https://docs.gl/gl4/glCopyImageSubData *could* be a viable and more direct solution, but expects both textures to have compatible internal formats + // (https://registry.khronos.org/OpenGL/specs/gl/glspec46.core.pdf#page=295), which we simply cannot have any guarantee of + ZoneScopedN("XrSession::copyToSwapchains"); TracyGpuZone("XrSession::copyToSwapchains") - m_swapchainCopyPass.getProgram().use(); + static RenderPass swapchainCopyPass = [] () { + RenderPass copyPass(FragmentShader::loadFromSource(swapchainCopySource), "Swapchain copy pass"); + + RenderShaderProgram& copyProgram = copyPass.getProgram(); + copyProgram.setAttribute(0, "uniFinalColorBuffer"); + copyProgram.setAttribute(1, "uniFinalDepthBuffer"); + + constexpr DrawBuffer drawBuffer = DrawBuffer::COLOR_ATTACHMENT0; + Renderer::bindFramebuffer(copyPass.getFramebuffer().getIndex(), FramebufferType::DRAW_FRAMEBUFFER); + Renderer::setDrawBuffers(1, &drawBuffer); + Renderer::bindFramebuffer(0); + + return copyPass; + }(); + + swapchainCopyPass.getProgram().use(); Renderer::activateTexture(0); colorBuffer.bind(); Renderer::activateTexture(1); depthBuffer.bind(); - Renderer::bindFramebuffer(m_swapchainCopyPass.getFramebuffer().getIndex(), FramebufferType::DRAW_FRAMEBUFFER); + Renderer::bindFramebuffer(swapchainCopyPass.getFramebuffer().getIndex(), FramebufferType::DRAW_FRAMEBUFFER); Renderer::setFramebufferTexture2D(FramebufferAttachment::COLOR0, colorSwapchainImage, 0); Renderer::setFramebufferTexture2D(FramebufferAttachment::DEPTH, depthSwapchainImage, 0); Renderer::clear(MaskType::COLOR | MaskType::DEPTH | MaskType::STENCIL); Renderer::setDepthFunction(DepthStencilFunction::ALWAYS); - m_swapchainCopyPass.execute(); + swapchainCopyPass.execute(); Renderer::setDepthFunction(DepthStencilFunction::LESS); } diff --git a/src/RaZ/XR/XrSystem.cpp b/src/RaZ/XR/XrSystem.cpp index 1d177b7f..e394fd4a 100644 --- a/src/RaZ/XR/XrSystem.cpp +++ b/src/RaZ/XR/XrSystem.cpp @@ -1,12 +1,9 @@ -#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 { @@ -62,7 +59,17 @@ void processEventData(const XrEventDataReferenceSpaceChangePending& referenceSpa XrSystem::XrSystem(const std::string& appName) : m_context(appName), m_session(m_context) { recoverViewConfigurations(); - m_session.createSwapchains(m_viewConfigViews); + + m_optimalViewWidth = m_viewConfigViews.front().recommendedImageRectWidth; + m_optimalViewHeight = m_viewConfigViews.front().recommendedImageRectHeight; + + if (m_viewConfigViews.size() > 1) { + for (const XrViewConfigurationView& viewConfigView : m_viewConfigViews) { + if (viewConfigView.recommendedImageRectWidth != m_optimalViewWidth || viewConfigView.recommendedImageRectHeight != m_optimalViewHeight) + Logger::warn("[XrSystem] The optimal configuration view size is not the same for all views; rendering may be altered"); + } + } + recoverEnvironmentBlendModes(); } @@ -102,25 +109,6 @@ bool XrSystem::update(const FrameTimeInfo&) { XrSystem::~XrSystem() = default; -Vec2u XrSystem::recoverOptimalViewSize() const { - assert("[XrSystem] View configuration views must be recovered before getting the optimal view size" && !m_viewConfigViews.empty()); - - Vec2u optimalViewSize(m_viewConfigViews.front().recommendedImageRectWidth, m_viewConfigViews.front().recommendedImageRectHeight); - - if (m_viewConfigViews.size() > 1) { - for (const XrViewConfigurationView& viewConfigView : m_viewConfigViews) { - if (viewConfigView.recommendedImageRectWidth != optimalViewSize.x() || viewConfigView.recommendedImageRectHeight != optimalViewSize.y()) - Logger::warn("[XrSystem] The optimal configuration view size is not the same for all views; rendering may be altered"); - } - } - - return optimalViewSize; -} - -bool XrSystem::renderFrame(const ViewRenderFunc& viewRenderFunc) { - return m_session.renderFrame(m_viewConfigViews, m_viewConfigType, m_environmentBlendMode, viewRenderFunc); -} - void XrSystem::recoverViewConfigurations() { uint32_t viewConfigCount {}; checkLog(xrEnumerateViewConfigurations(m_context.m_instance, m_context.m_systemId, 0, &viewConfigCount, nullptr), @@ -201,6 +189,15 @@ void XrSystem::recoverEnvironmentBlendModes() { } } +void XrSystem::initializeSession() { + m_session.initialize(m_context.m_systemId); + m_session.createSwapchains(m_viewConfigViews); +} + +bool XrSystem::renderFrame(const ViewRenderFunc& viewRenderFunc) { + return m_session.renderFrame(m_viewConfigViews, m_viewConfigType, m_environmentBlendMode, viewRenderFunc); +} + bool XrSystem::processSessionStateChanged(const XrEventDataSessionStateChanged& sessionStateChanged) { if (sessionStateChanged.session != m_session.m_handle) { Logger::info("[XrSystem] Data session state changed for unknown session");