Skip to content

Commit

Permalink
[Render/RenderSystem] Enabled XR multi-view rendering
Browse files Browse the repository at this point in the history
- XR can be enabled in the RenderSystem; if it has been, the scene is rendered once per view each frame
  • Loading branch information
Razakhel committed Nov 12, 2024
1 parent eb1792e commit 3b73071
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 21 deletions.
12 changes: 11 additions & 1 deletion examples/xrDemo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,22 @@ int main() {
window.addKeyCallback(Raz::Keyboard::ESCAPE, [&app] (float) noexcept { app.quit(); });
window.setCloseCallback([&app] () noexcept { app.quit(); });

world.addSystem<Raz::XrSystem>("RaZ - XR demo");
auto& xr = world.addSystem<Raz::XrSystem>("RaZ - XR demo");
render.enableXr(xr);

// The camera parameters aren't technically used for XR rendering, but its transform may be later
world.addEntityWithComponent<Raz::Transform>().addComponent<Raz::Camera>(window.getWidth(), window.getHeight());
world.addEntityWithComponent<Raz::Transform>(Raz::Vec3f(0.f, 0.f, -5.f))
.addComponent<Raz::MeshRenderer>(Raz::Mesh(Raz::AABB(Raz::Vec3f(-1.f), Raz::Vec3f(1.f))));

// TODO: the textures' dimensions must be the same as the rendering viewport's
const auto colorBuffer = Raz::Texture2D::create(2468, 2584, Raz::TextureColorspace::RGBA);
const auto depthBuffer = Raz::Texture2D::create(2468, 2584, Raz::TextureColorspace::DEPTH);

Raz::RenderPass& geomPass = render.getGeometryPass();
geomPass.addWriteColorTexture(colorBuffer, 0);
geomPass.setWriteDepthTexture(depthBuffer);

app.run();
} catch (const std::exception& exception) {
Raz::Logger::error("Exception occurred: "s + exception.what());
Expand Down
18 changes: 18 additions & 0 deletions include/RaZ/Render/RenderSystem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@
#include "RaZ/Render/UniformBuffer.hpp"
#include "RaZ/Render/Window.hpp"

#if !defined(__APPLE__) && !defined(__EMSCRIPTEN__) && !defined(RAZ_NO_WINDOW)
// XR currently isn't available with macOS or Emscripten and requires windowing capabilities
#define RAZ_USE_XR
#endif

namespace Raz {

class Entity;
class MeshRenderer;
#if defined(RAZ_USE_XR)
class XrSystem;
#endif

/// RenderSystem class, handling the rendering part.
class RenderSystem final : public System {
Expand Down Expand Up @@ -57,6 +65,9 @@ class RenderSystem final : public System {
const Cubemap& getCubemap() const { assert("Error: The cubemap must be set before being accessed." && hasCubemap()); return *m_cubemap; }

void setCubemap(Cubemap&& cubemap);
#if defined(RAZ_USE_XR)
void enableXr(XrSystem& xrSystem);
#endif

#if !defined(RAZ_NO_WINDOW)
void createWindow(unsigned int width, unsigned int height,
Expand Down Expand Up @@ -98,6 +109,9 @@ class RenderSystem final : public System {
/// \param entity Light entity to be updated; if not a directional light, needs to have a Transform component.
/// \param lightIndex Index of the light to be updated.
void updateLight(const Entity& entity, unsigned int lightIndex) const;
#if defined(RAZ_USE_XR)
void renderXrFrame();
#endif

unsigned int m_sceneWidth {};
unsigned int m_sceneHeight {};
Expand All @@ -114,6 +128,10 @@ class RenderSystem final : public System {
UniformBuffer m_modelUbo = UniformBuffer(sizeof(Mat4f), UniformBufferUsage::STREAM);

std::optional<Cubemap> m_cubemap {};

#if defined(RAZ_USE_XR)
XrSystem* m_xrSystem {};
#endif
};

} // namespace Raz
Expand Down
5 changes: 5 additions & 0 deletions include/RaZ/XR/XrSystem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ struct XrViewConfigurationView;
namespace Raz {

class XrSystem final : public System {
friend class RenderSystem;

public:
explicit XrSystem(const std::string& appName);

Expand All @@ -23,6 +25,9 @@ class XrSystem final : public System {
~XrSystem() override;

private:
Vec2u recoverOptimalViewSize() const;
void renderFrame(const ViewRenderFunc& viewRenderFunc);

void recoverViewConfigurations();
void recoverEnvironmentBlendModes();
bool processSessionStateChanged(const XrEventDataSessionStateChanged& sessionStateChanged);
Expand Down
100 changes: 80 additions & 20 deletions src/RaZ/Render/RenderSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#include "RaZ/Render/MeshRenderer.hpp"
#include "RaZ/Render/Renderer.hpp"
#include "RaZ/Render/RenderSystem.hpp"
#if defined(RAZ_USE_XR)
#include "RaZ/XR/XrSystem.hpp"
#endif

#include "tracy/Tracy.hpp"
#include "GL/glew.h" // Needed by TracyOpenGL.hpp
Expand All @@ -19,6 +22,15 @@ void RenderSystem::setCubemap(Cubemap&& cubemap) {
m_cameraUbo.bindUniformBlock(m_cubemap->getProgram(), "uboCameraInfo", 0);
}

#if defined(RAZ_USE_XR)
void RenderSystem::enableXr(XrSystem& xrSystem) {
m_xrSystem = &xrSystem;

const Vec2u optimalViewSize = xrSystem.recoverOptimalViewSize();
resizeViewport(optimalViewSize.x(), optimalViewSize.y());
}
#endif

void RenderSystem::resizeViewport(unsigned int width, unsigned int height) {
ZoneScopedN("RenderSystem::resizeViewport");

Expand Down Expand Up @@ -55,9 +67,15 @@ bool RenderSystem::update(const FrameTimeInfo& timeInfo) {
m_timeUbo.sendData(timeInfo.deltaTime, 0);
m_timeUbo.sendData(timeInfo.globalTime, sizeof(float));

sendCameraInfo();

m_renderGraph.execute(*this);
#if defined(RAZ_USE_XR)
if (m_xrSystem) {
renderXrFrame();
} else
#endif
{
sendCameraInfo();
m_renderGraph.execute(*this);
}

#if defined(RAZ_CONFIG_DEBUG) && !defined(SKIP_RENDERER_ERRORS)
Renderer::printErrors();
Expand Down Expand Up @@ -226,23 +244,6 @@ void RenderSystem::initialize(unsigned int sceneWidth, unsigned int sceneHeight)
resizeViewport(sceneWidth, sceneHeight);
}

void RenderSystem::updateLight(const Entity& entity, unsigned int lightIndex) const {
const auto& light = entity.getComponent<Light>();
const std::size_t dataStride = sizeof(Vec4f) * 4 * lightIndex;

if (light.getType() == LightType::DIRECTIONAL) {
m_lightsUbo.sendData(Vec4f(0.f), static_cast<unsigned int>(dataStride));
} else {
assert("Error: A non-directional light needs to have a Transform component." && entity.hasComponent<Transform>());
m_lightsUbo.sendData(Vec4f(entity.getComponent<Transform>().getPosition(), 1.f), static_cast<unsigned int>(dataStride));
}

m_lightsUbo.sendData(light.getDirection(), static_cast<unsigned int>(dataStride + sizeof(Vec4f)));
m_lightsUbo.sendData(light.getColor(), static_cast<unsigned int>(dataStride + sizeof(Vec4f) * 2));
m_lightsUbo.sendData(light.getEnergy(), static_cast<unsigned int>(dataStride + sizeof(Vec4f) * 3));
m_lightsUbo.sendData(light.getAngle().value, static_cast<unsigned int>(dataStride + sizeof(Vec4f) * 3 + sizeof(float)));
}

void RenderSystem::sendCameraInfo() const {
assert("Error: The render system needs a camera to send its info." && (m_cameraEntity != nullptr));
assert("Error: The camera must have a transform component to send its info." && m_cameraEntity->hasComponent<Transform>());
Expand Down Expand Up @@ -274,4 +275,63 @@ void RenderSystem::sendCameraInfo() const {
sendViewProjectionMatrix(camera.getProjectionMatrix() * camera.getViewMatrix());
}

void RenderSystem::updateLight(const Entity& entity, unsigned int lightIndex) const {
const auto& light = entity.getComponent<Light>();
const std::size_t dataStride = sizeof(Vec4f) * 4 * lightIndex;

if (light.getType() == LightType::DIRECTIONAL) {
m_lightsUbo.sendData(Vec4f(0.f), static_cast<unsigned int>(dataStride));
} else {
assert("Error: A non-directional light needs to have a Transform component." && entity.hasComponent<Transform>());
m_lightsUbo.sendData(Vec4f(entity.getComponent<Transform>().getPosition(), 1.f), static_cast<unsigned int>(dataStride));
}

m_lightsUbo.sendData(light.getDirection(), static_cast<unsigned int>(dataStride + sizeof(Vec4f)));
m_lightsUbo.sendData(light.getColor(), static_cast<unsigned int>(dataStride + sizeof(Vec4f) * 2));
m_lightsUbo.sendData(light.getEnergy(), static_cast<unsigned int>(dataStride + sizeof(Vec4f) * 3));
m_lightsUbo.sendData(light.getAngle().value, static_cast<unsigned int>(dataStride + sizeof(Vec4f) * 3 + sizeof(float)));
}

#if defined(RAZ_USE_XR)
void RenderSystem::renderXrFrame() {
m_xrSystem->renderFrame([this] (const Vec3f& position, const Quaternionf& rotation, const ViewFov& viewFov) {
Mat4f invViewMat = rotation.computeMatrix();
invViewMat.getElement(3, 0) = position.x();
invViewMat.getElement(3, 1) = position.y();
invViewMat.getElement(3, 2) = position.z();
const Mat4f viewMat = invViewMat.inverse();

const float tanAngleRight = std::tan(viewFov.angleRight.value);
const float tanAngleLeft = std::tan(viewFov.angleLeft.value);
const float tanAngleUp = std::tan(viewFov.angleUp.value);
const float tanAngleDown = std::tan(viewFov.angleDown.value);
const float invAngleWidth = 1.f / (tanAngleRight - tanAngleLeft);
const float invAngleHeight = 1.f / (tanAngleUp - tanAngleDown);
const float angleWidthDiff = tanAngleRight + tanAngleLeft;
const float angleHeightDiff = tanAngleUp + tanAngleDown;
constexpr float nearZ = 0.1f;
constexpr float farZ = 1000.f;
constexpr float invDepthDiff = 1.f / (farZ - nearZ);
const Mat4f projMatrix(2.f * invAngleWidth, 0.f, angleWidthDiff * invAngleWidth, 0.f,
0.f, 2.f * invAngleHeight, angleHeightDiff * invAngleHeight, 0.f,
0.f, 0.f, -(farZ + nearZ) * invDepthDiff, -(farZ * (nearZ + nearZ)) * invDepthDiff,
0.f, 0.f, -1.f, 0.f);

m_cameraUbo.bind();
sendViewMatrix(viewMat);
sendInverseViewMatrix(invViewMat);
sendProjectionMatrix(projMatrix);
sendInverseProjectionMatrix(projMatrix.inverse());
sendViewProjectionMatrix(projMatrix * viewMat);
sendCameraPosition(position);

m_renderGraph.execute(*this);

// TODO: the returned color buffer must be the one from the render pass executed last
const Framebuffer& geomFramebuffer = m_renderGraph.m_geometryPass.getFramebuffer();
return std::make_pair(std::cref(geomFramebuffer.getColorBuffer(0)), std::cref(geomFramebuffer.getDepthBuffer()));
});
}
#endif

} // namespace Raz
19 changes: 19 additions & 0 deletions src/RaZ/XR/XrSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,25 @@ 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;
}

void XrSystem::renderFrame(const ViewRenderFunc& viewRenderFunc) {
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),
Expand Down

0 comments on commit 3b73071

Please sign in to comment.