Skip to content

Commit

Permalink
[XR/XrSession] Delayed the session's actual creation
Browse files Browse the repository at this point in the history
- This allows creating an XR system & context, therefore allowing getting the XR device's info like its views dimensions, before initializing anything related to rendering
  - The optimal view width & height (the recommended image rect. size for the device's views) can be recovered from the XrSystem
  - These dimensions are used to create the window in the XR demo

- Due to the necessary delayed rendering initialization, the swapchain copy pass is now a static local variable in the corresponding function, just like the window copy pass
  • Loading branch information
Razakhel committed Nov 13, 2024
1 parent 59ced50 commit bf23058
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 122 deletions.
12 changes: 9 additions & 3 deletions examples/xrDemo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,22 @@ 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<Raz::RenderSystem>(823, 861, "RaZ", Raz::WindowSetting::NON_RESIZABLE);
auto& xr = world.addSystem<Raz::XrSystem>("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<Raz::RenderSystem>(xr.getOptimalViewWidth() / 3, xr.getOptimalViewHeight() / 3, "RaZ", Raz::WindowSetting::NON_RESIZABLE);

Raz::Window& window = render.getWindow();

window.addKeyCallback(Raz::Keyboard::ESCAPE, [&app] (float) noexcept { app.quit(); });
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::XrSystem>("RaZ - XR demo");
render.enableXr(xr);

// In an XR workflow, the camera is optional; its parameters aren't technically used, but its transform is
Expand Down
19 changes: 6 additions & 13 deletions include/RaZ/XR/XrSession.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,13 +14,8 @@ struct XrViewConfigurationView;

namespace Raz {

template <typename T>
class Quaternion;
using Quaternionf = Quaternion<float>;

template <typename T, std::size_t Size>
class Vector;
using Vec3f = Vector<float, 3>;
using Vec3f = Vector<float, 3>;

class Texture2D;
class XrContext;
Expand Down Expand Up @@ -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<XrViewConfigurationView>& viewConfigViews);
void destroySwapchains();
void createSwapchainImages(XrSwapchain swapchain, SwapchainType swapchainType);
void createReferenceSpace();
void destroyReferenceSpace();
bool renderLayer(RenderLayerInfo& layerInfo,
const std::vector<XrViewConfigurationView>& viewConfigViews,
unsigned int viewConfigType,
Expand All @@ -72,13 +67,11 @@ class XrSession {
int m_state {};
bool m_isRunning = false;

XrSpace m_localSpace {};

std::vector<XrSwapchain> m_colorSwapchains;
std::vector<XrSwapchain> m_depthSwapchains;
std::unordered_map<XrSwapchain, std::vector<XrSwapchainImageOpenGLKHR>> m_swapchainImages;

XrSpace m_localSpace {};

RenderPass m_swapchainCopyPass {};
};

} // namespace Raz
Expand Down
10 changes: 7 additions & 3 deletions include/RaZ/XR/XrSystem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -38,6 +40,8 @@ class XrSystem final : public System {
std::vector<unsigned int> m_viewConfigTypes;
unsigned int m_viewConfigType {};
std::vector<XrViewConfigurationView> m_viewConfigViews;
unsigned int m_optimalViewWidth {};
unsigned int m_optimalViewHeight {};

std::vector<unsigned int> m_environmentBlendModes;
unsigned int m_environmentBlendMode {};
Expand Down
4 changes: 2 additions & 2 deletions src/RaZ/Render/RenderSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions src/RaZ/XR/XrContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include "openxr/openxr.h"
#include "openxr/openxr_platform.h"

#include "tracy/Tracy.hpp"

#include <algorithm>
Expand Down
170 changes: 92 additions & 78 deletions src/RaZ/XR/XrSession.cpp
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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<PFN_xrVoidFunction*>(&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) {
Expand Down Expand Up @@ -255,6 +203,7 @@ bool XrSession::renderFrame(const std::vector<XrViewConfigurationView>& 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;
Expand Down Expand Up @@ -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<PFN_xrVoidFunction*>(&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<XrViewConfigurationView>& 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<int64_t> formats(formatCount);
Expand All @@ -314,7 +329,8 @@ void XrSession::createSwapchains(const std::vector<XrViewConfigurationView>& 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);
Expand Down Expand Up @@ -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<XrViewConfigurationView>& viewConfigViews,
unsigned int viewConfigType,
Expand Down Expand Up @@ -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);
}

Expand Down
Loading

0 comments on commit bf23058

Please sign in to comment.