diff --git a/examples/xrDemo.cpp b/examples/xrDemo.cpp index 648b86e5..4e6daf1a 100644 --- a/examples/xrDemo.cpp +++ b/examples/xrDemo.cpp @@ -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 - XR demo"); + auto& xr = world.addSystem("RaZ - XR demo"); + render.enableXr(xr); + // The camera parameters aren't technically used for XR rendering, but its transform may be later 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)))); + // 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()); diff --git a/include/RaZ/Render/RenderSystem.hpp b/include/RaZ/Render/RenderSystem.hpp index 74ef9edb..89236717 100644 --- a/include/RaZ/Render/RenderSystem.hpp +++ b/include/RaZ/Render/RenderSystem.hpp @@ -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 { @@ -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, @@ -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 {}; @@ -114,6 +128,10 @@ class RenderSystem final : public System { UniformBuffer m_modelUbo = UniformBuffer(sizeof(Mat4f), UniformBufferUsage::STREAM); std::optional m_cubemap {}; + +#if defined(RAZ_USE_XR) + XrSystem* m_xrSystem {}; +#endif }; } // namespace Raz diff --git a/include/RaZ/XR/XrSystem.hpp b/include/RaZ/XR/XrSystem.hpp index cf112cc7..bdb56924 100644 --- a/include/RaZ/XR/XrSystem.hpp +++ b/include/RaZ/XR/XrSystem.hpp @@ -15,6 +15,8 @@ struct XrViewConfigurationView; namespace Raz { class XrSystem final : public System { + friend class RenderSystem; + public: explicit XrSystem(const std::string& appName); @@ -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); diff --git a/src/RaZ/Render/RenderSystem.cpp b/src/RaZ/Render/RenderSystem.cpp index acbfecd7..a0191355 100644 --- a/src/RaZ/Render/RenderSystem.cpp +++ b/src/RaZ/Render/RenderSystem.cpp @@ -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 @@ -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"); @@ -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(); @@ -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(); - const std::size_t dataStride = sizeof(Vec4f) * 4 * lightIndex; - - if (light.getType() == LightType::DIRECTIONAL) { - m_lightsUbo.sendData(Vec4f(0.f), static_cast(dataStride)); - } else { - assert("Error: A non-directional light needs to have a Transform component." && entity.hasComponent()); - m_lightsUbo.sendData(Vec4f(entity.getComponent().getPosition(), 1.f), static_cast(dataStride)); - } - - m_lightsUbo.sendData(light.getDirection(), static_cast(dataStride + sizeof(Vec4f))); - m_lightsUbo.sendData(light.getColor(), static_cast(dataStride + sizeof(Vec4f) * 2)); - m_lightsUbo.sendData(light.getEnergy(), static_cast(dataStride + sizeof(Vec4f) * 3)); - m_lightsUbo.sendData(light.getAngle().value, static_cast(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()); @@ -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(); + const std::size_t dataStride = sizeof(Vec4f) * 4 * lightIndex; + + if (light.getType() == LightType::DIRECTIONAL) { + m_lightsUbo.sendData(Vec4f(0.f), static_cast(dataStride)); + } else { + assert("Error: A non-directional light needs to have a Transform component." && entity.hasComponent()); + m_lightsUbo.sendData(Vec4f(entity.getComponent().getPosition(), 1.f), static_cast(dataStride)); + } + + m_lightsUbo.sendData(light.getDirection(), static_cast(dataStride + sizeof(Vec4f))); + m_lightsUbo.sendData(light.getColor(), static_cast(dataStride + sizeof(Vec4f) * 2)); + m_lightsUbo.sendData(light.getEnergy(), static_cast(dataStride + sizeof(Vec4f) * 3)); + m_lightsUbo.sendData(light.getAngle().value, static_cast(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 diff --git a/src/RaZ/XR/XrSystem.cpp b/src/RaZ/XR/XrSystem.cpp index 36fa33ed..0d7a144e 100644 --- a/src/RaZ/XR/XrSystem.cpp +++ b/src/RaZ/XR/XrSystem.cpp @@ -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),