Skyscraper 2.0
OgreOpenXRRenderWindow.cpp
Go to the documentation of this file.
1/*
2 Skyscraper 2.0 - OpenXR Render Window
3 Portions Copyright (C)2024 Ryan Thoryk
4 MIT license - see LICENSE file
5 https://www.skyscrapersim.net
6 https://sourceforge.net/projects/skyscraper/
7 Contact - ryan@skyscrapersim.net
8*/
9
10/*
11 Original work produced by Glastonbridge Software Limited. This code is provided under the MIT license.
12 https://github.com/glastonbridge/OgreOpenXRRenderWindow
13*/
14
16#include "OgreRoot.h"
17
18#include "RenderSystems/Direct3D11/OgreD3D11RenderWindow.h"
19#include "RenderSystems/Direct3D11/OgreD3D11RenderSystem.h"
20#include "RenderSystems/Direct3D11/OgreD3D11Device.h"
21
22#include "OgreOpenXRState.h"
24#include "OgreOpenXRSwapchain.h"
25
26#include "OgreOpenXRConfig.h"
27#include <openxr/openxr.h>
28#include <openxr/openxr_platform.h>
29#include <openxr/openxr_platform_defines.h>
30
31#include <memory>
32#include <winrt/base.h>
33
34std::vector<Ogre::Vector3> XRPosition;
35std::vector<Ogre::Quaternion> XROrientation;
36
37namespace Ogre {
38 class OpenXRState;
39 class OpenXRViewProjection;
40 class OpenXRSwapchain;
41
42 class _OgreOpenXRExport OpenXRRenderWindow : public D3D11RenderWindowBase {
43 public:
49 OpenXRRenderWindow(RenderSystem* rsys);
51
55 virtual void create(const String& name, unsigned int width, unsigned int height,
56 bool fullScreen, const NameValuePairList* miscParams) override;
57
58 bool requiresTextureFlipping() const { return false; }
59
60 void _beginUpdate() override;
61 void _endUpdate() override;
62
63 ID3D11Texture2D* getSurface(uint index) const override;
64
65 uint getNumberOfViews() const;
66 ID3D11RenderTargetView* getRenderTargetView(uint index) const;
67 Viewport* addViewport(Camera* cam, int zOrder = 0, float x = 0.0f, float y = 0.0f, float w = 0.0f, float h = 0.0f) override;
68
69 void setActiveEye(size_t eye);
70
71 // D3D11RenderWindowBase
72 void notifyDeviceLost(D3D11Device* device);
73 void notifyDeviceRestored(D3D11Device* device);
74
75 private:
76 Ogre::Camera* mEyeCameras[2];
77 size_t mNumberOfEyesAdded = 0;
78 size_t mActiveEye = 0; // TODO: this window should be two camera-based objects plus a headset manager
79
80 ComPtr<ID3D11Texture2D> mpBackBuffer;
81 winrt::com_ptr<ID3D11RenderTargetView> mRenderTargetViewL;
82 winrt::com_ptr<ID3D11RenderTargetView> mRenderTargetViewR;
83
84 std::unique_ptr<OpenXRState> mXrState;
85 std::unique_ptr<OpenXRSwapchain> swapchainL;
86 std::unique_ptr<OpenXRSwapchain> swapchainR;
87 std::unique_ptr<OpenXRViewProjection> mViewProjections;
88
89 XrViewState mXrViewState{ XR_TYPE_VIEW_STATE };
90
91 XrSessionState mXrSessionState;
92
93 XrFrameState mXrFrameState{ XR_TYPE_FRAME_STATE };
94 XrCompositionLayerProjection mXrLayer{ XR_TYPE_COMPOSITION_LAYER_PROJECTION };
95 std::vector<XrCompositionLayerBaseHeader*> mXrLayers;
96
97 bool sessionReady() { return mXrSessionState == XR_SESSION_STATE_READY || XR_SESSION_STATE_FOCUSED; }
98 bool shouldRender() { return sessionReady() && mXrFrameState.shouldRender; }
99
100 void ProcessOpenXREvents();
101 void _startXrFrame();
102 void _endXrFrame();
103 };
104}
105namespace {
106 // TODO: This is a nasty-ass way to draw to the correct viewport, hacking it in now
107 // but should really set up the two eyes as separate viewports
108 class EyeListener : public Ogre::Camera::Listener {
109 public:
110 EyeListener(Ogre::OpenXRRenderWindow* renderWindow, size_t eyeIndex) :
111 m_renderWindow(renderWindow),
112 m_eyeIndex(eyeIndex) {
113
114 }
115 virtual void cameraPreRenderScene(Ogre::Camera* cam) override
116 {
117 m_renderWindow->setActiveEye(m_eyeIndex);
118 }
119 private:
120 Ogre::OpenXRRenderWindow* m_renderWindow;
121 size_t m_eyeIndex;
122 };
123
124}
125
126namespace Ogre {
128 D3D11RenderWindowBase(static_cast<D3D11RenderSystem*>(rsys)->_getDevice()),
129 mXrState(new OpenXRState),
130 mViewProjections(new OpenXRViewProjection),
131 swapchainL(new OpenXRSwapchain),
132 swapchainR(new OpenXRSwapchain)
133 {
134 mIsFullScreen = true;
135 mIsExternal = true;
136 mActive = false;
137 mSizing = false;
138 mHidden = false;
139 XRPosition.resize(2);
140 XROrientation.resize(2);
141 XRPosition[0] = Ogre::Vector3::ZERO;
142 XRPosition[1] = Ogre::Vector3::ZERO;
143 XROrientation[0] = Ogre::Quaternion::ZERO;
144 XROrientation[1] = Ogre::Quaternion::ZERO;
145 LogManager::getSingleton().logMessage("\nOpenXR Interface loaded\nOriginal work produced by Glastonbridge Software Limited, MIT license");
146 LogManager::getSingleton().logMessage("OpenXR: Initializing...");
147 }
148
150
151 D3D11RenderSystem* rsys = static_cast<D3D11RenderSystem*>(Root::getSingleton().getRenderSystem());
152 rsys->fireDeviceEvent(&mDevice, "RenderWindowDestroyed", this);
153
154 mActive = false;
155 mClosed = true;
156 }
157 void OpenXRRenderWindow::create(const String& name, unsigned int width, unsigned int height,
158 bool fullScreen, const NameValuePairList* miscParams)
159 {
160 D3D11RenderSystem* rsys = static_cast<D3D11RenderSystem*>(Root::getSingleton().getRenderSystem());
161 mXrState->Initialize(name);
162 mXrState->InitializeSystem();
163 mXrState->initializeSession(mDevice);
164
165 mViewProjections->Initialize(mXrState.get());
166 mWidth = mViewProjections->getWidth();
167 mHeight = mViewProjections->getHeight();
168 swapchainL->Initialize(mXrState.get(), mViewProjections.get());
169 swapchainR->Initialize(mXrState.get(), mViewProjections.get());
170
171 mActive = true;
172 }
173
174
176 {
177 RenderTarget::_beginUpdate();
180
181 if (!shouldRender()) return;
182
183 mViewProjections->UpdateXrViewInfo(mXrViewState, mXrState.get(), mXrFrameState.predictedDisplayTime);
184 uint32_t viewCount = 2;
185
186 swapchainL->AcquireImages();
187 swapchainR->AcquireImages();
188
189 const CD3D11_RENDER_TARGET_VIEW_DESC renderTargetViewDescL(D3D11_RTV_DIMENSION_TEXTURE2DARRAY, swapchainL->ColorSwapchainPixelFormat);
190 mRenderTargetViewL = nullptr;
192 mDevice->CreateRenderTargetView(swapchainL->getColorTexture(), &renderTargetViewDescL, mRenderTargetViewL.put()));
193
194 const CD3D11_RENDER_TARGET_VIEW_DESC renderTargetViewDescR(D3D11_RTV_DIMENSION_TEXTURE2DARRAY, swapchainR->ColorSwapchainPixelFormat);
195 mRenderTargetViewR = nullptr;
197 mDevice->CreateRenderTargetView(swapchainR->getColorTexture(), &renderTargetViewDescR, mRenderTargetViewR.put()));
198
199 std::vector<xr::math::ViewProjection> vps(viewCount);
200 mViewProjections->CalculateViewProjections(vps);
201 auto imageRect = swapchainL->getImageRect();
202
203 mViewProjections->ProjectionLayerViews[0].subImage.swapchain = swapchainL->getColorSwapchain();
204 mViewProjections->ProjectionLayerViews[1].subImage.swapchain = swapchainR->getColorSwapchain();
205
206 mViewProjections->DepthInfoViews[0].subImage.swapchain = swapchainL->getDepthSwapchain();
207 mViewProjections->DepthInfoViews[1].subImage.swapchain = swapchainR->getDepthSwapchain();
208
209 for (uint32_t i = 0; i < viewCount; ++i) {
210 mViewProjections->ProjectionLayerViews[i].subImage.imageRect = imageRect;
211 mViewProjections->DepthInfoViews[i].subImage.imageRect = imageRect;
212 }
213
214 auto deviceContext = mDevice.GetImmediateContext();
215
216 const uint32_t viewInstanceCount = (uint32_t)vps.size();
217
218 CD3D11_VIEWPORT viewport(
219 (float)imageRect.offset.x, (float)imageRect.offset.y, (float)imageRect.extent.width, (float)imageRect.extent.height);
220 deviceContext->RSSetViewports(1, &viewport);
221
222 mpBackBuffer = swapchainR->getColorTexture();
223
224 const bool reversedZ = vps[0].NearFar.Near > vps[0].NearFar.Far;
225 const float depthClearValue = reversedZ ? 0.f : 1.f;
226
227 if (mNumberOfEyesAdded != 2) return;
228
229 for (uint32_t k = 0; k < 2; ++k) {
230 const DirectX::XMMATRIX spaceToView = xr::math::LoadXrPose(vps[k].Pose);
231 Ogre::Quaternion orientation(vps[k].Pose.orientation.w, vps[k].Pose.orientation.x, vps[k].Pose.orientation.y, vps[k].Pose.orientation.z);
232 Ogre::Vector3 position(vps[k].Pose.position.x, vps[k].Pose.position.y, vps[k].Pose.position.z);
233
234 Ogre::Affine3 viewMatrix;
235 for (size_t i = 0; i < 4; ++i) {
236 for (size_t j = 0; j < 4; ++j) {
237 viewMatrix[i][j] = spaceToView.r[i].m128_f32[j];
238 }
239 }
240
241 xr::math::NearFar nf = { 0.1, std::numeric_limits<float>::infinity() };
242
243 DirectX::XMMATRIX projectionMatrix = ComposeProjectionMatrix(
244 vps[k].Fov,
245 nf);
246 Ogre::Matrix4 eyeProjectionMatrix;
247
248 // Note the transpose
249 for (size_t i = 0; i < 4; ++i) {
250 for (size_t j = 0; j < 4; ++j) {
251 eyeProjectionMatrix[i][j] = projectionMatrix.r[j].m128_f32[i];
252 }
253 }
254 //mEyeCameras[k]->setCustomViewMatrix(true, viewMatrix);
255 //mEyeCameras[k]->setPosition(XRPosition[k] + position);
256 mEyeCameras[k]->setPosition(position);
257 mEyeCameras[k]->setOrientation(orientation);
258 mEyeCameras[k]->setCustomProjectionMatrix(true, eyeProjectionMatrix);
259 //mEyeCameras[k]->setFarClipDistance(3000);
260 mEyeCameras[k]->setAspectRatio(Ogre::Real(imageRect.extent.height / imageRect.extent.width));
261 }
262 }
263
265 {
266 RenderTarget::_endUpdate();
267
268 if (!shouldRender()) return;
269
270 swapchainL->ReleaseImages();
271 swapchainR->ReleaseImages();
272
273 _endXrFrame();
274 }
275
277 {
278 return 1;
279 }
280
281 ID3D11Texture2D* OpenXRRenderWindow::getSurface(uint index) const
282 {
283 return mActiveEye == 0 ? swapchainL->getSurface(0) : swapchainR->getSurface(0);
284 }
285
286 ID3D11RenderTargetView* OpenXRRenderWindow::getRenderTargetView(uint index) const
287 {
288 switch (mActiveEye) {
289 case 0: return mRenderTargetViewL.get();
290 case 1: return mRenderTargetViewR.get();
291 default: return nullptr;
292 }
293 }
294
295 Viewport* OpenXRRenderWindow::addViewport(Camera* cam, int zOrder, float x, float y, float w, float h)
296 {
297 if (mNumberOfEyesAdded < 2) {
299 cam->addListener(new EyeListener(this, mNumberOfEyesAdded++)); // TODO: Memory leak baby
300 return RenderWindow::addViewport(cam, zOrder, x, y, w, h);
301 }
302
303 OGRE_EXCEPT(Exception::ERR_INVALID_CALL,
304 "You can't have more than two cameras in OpenXR renderer.",
305 "OpenXRRenderWindow::addViewport");
306 }
307
309 {
310 if (!sessionReady()) return;
311 const auto& swapchain = eye == 0 ? swapchainL : swapchainR;
312 mpBackBuffer = swapchain->getColorTexture();
313 mActiveEye = eye;
314 }
315
316 void OpenXRRenderWindow::notifyDeviceLost(D3D11Device* device)
317 {
318 }
320 {
321 }
323 {
324 auto pollEvent = [&](XrEventDataBuffer& eventData) -> bool {
325 eventData.type = XR_TYPE_EVENT_DATA_BUFFER;
326 eventData.next = nullptr;
327 return CHECK_XRCMD(
328 xrPollEvent(mXrState->GetInstanceHandle().Get(), &eventData)
329 ) == XR_SUCCESS;
330 };
331
332 XrEventDataBuffer eventData{};
333 while (pollEvent(eventData)) {
334 switch (eventData.type) {
335 case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: {
336 //*exitRenderLoop = true;
337 //*requestRestart = false;
338 return;
339 }
340 case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
341 auto& xrSession = mXrState->GetSession();
342 const auto stateEvent = *reinterpret_cast<const XrEventDataSessionStateChanged*>(&eventData);
343 CHECK(xrSession.Get() != XR_NULL_HANDLE && xrSession.Get() == stateEvent.session);
344 mXrSessionState = stateEvent.state;
345 switch (mXrSessionState) {
346 case XR_SESSION_STATE_READY: {
347 CHECK(xrSession.Get() != XR_NULL_HANDLE);
348 XrSessionBeginInfo sessionBeginInfo{ XR_TYPE_SESSION_BEGIN_INFO };
349 sessionBeginInfo.primaryViewConfigurationType = OpenXRState::primaryViewConfigType;
350 CHECK_XRCMD(xrBeginSession(xrSession.Get(), &sessionBeginInfo));
351 break;
352 }
353 case XR_SESSION_STATE_STOPPING: {
354 CHECK_XRCMD(xrEndSession(xrSession.Get()));
355 break;
356 }
357 case XR_SESSION_STATE_EXITING: {
358 // TODO:
359 // Do not attempt to restart, because user closed this session.
360 //*exitRenderLoop = true;
361 //*requestRestart = false;
362 break;
363 }
364 case XR_SESSION_STATE_LOSS_PENDING: {
365 // TODO:
366 // Session was lost, so start over and poll for new systemId.
367 //*exitRenderLoop = true;
368 //*requestRestart = true;
369 break;
370 }
371 }
372 break;
373 }
374 case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING:
375 case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED:
376 default: {
377 DEBUG_PRINT("Ignoring event type %d", eventData.type);
378 break;
379 }
380 }
381 }
382 }
383
384
386 {
387 mXrLayers.clear();
388 if (!sessionReady()) return;
389
390 auto& session = mXrState->GetSession();
391
392 CHECK(session.Get() != XR_NULL_HANDLE);
393
394 XrFrameWaitInfo frameWaitInfo{ XR_TYPE_FRAME_WAIT_INFO };
395 CHECK_XRCMD(xrWaitFrame(session.Get(), &frameWaitInfo, &mXrFrameState));
396
397 XrFrameBeginInfo frameBeginInfo{ XR_TYPE_FRAME_BEGIN_INFO };
398 CHECK_XRCMD(xrBeginFrame(session.Get(), &frameBeginInfo));
399
400 if (!mXrFrameState.shouldRender) return;
401
402 // The projection layer consists of projection layer views.
403
404 // Inform the runtime that the app's submitted alpha channel has valid data for use during composition.
405 // The primary display on HoloLens has an additive environment blend mode. It will ignore the alpha channel.
406 // However, mixed reality capture uses the alpha channel if this bit is set to blend content with the environment.
407 mXrLayer = { XR_TYPE_COMPOSITION_LAYER_PROJECTION };
408 mXrLayer.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
409 mXrLayers.push_back(reinterpret_cast<XrCompositionLayerBaseHeader*>(&mXrLayer));
410 }
411
413 {
414 mXrLayer.space = mXrState->getAppSpace().Get();
415 mXrLayer.viewCount = (uint32_t) mViewProjections->ProjectionLayerViews.size();
416 mXrLayer.views = mViewProjections->ProjectionLayerViews.data();
417
418 XrFrameEndInfo frameEndInfo{ XR_TYPE_FRAME_END_INFO };
419 frameEndInfo.displayTime = mXrFrameState.predictedDisplayTime;
420 frameEndInfo.environmentBlendMode = *mXrState->GetEnvironmentBlendModes();
421 frameEndInfo.layerCount = mXrLayers.size();
422 frameEndInfo.layers = mXrLayers.data();
423
424 CHECK_XRCMD(xrEndFrame(mXrState->GetSession().Get(), &frameEndInfo));
425 }
426}
427
428Ogre::RenderWindow* CreateOpenXRRenderWindow(Ogre::RenderSystem* rsys)
429{
430 Ogre::OpenXRRenderWindow* xrRenderWindow = new Ogre::OpenXRRenderWindow(rsys);
431 rsys->attachRenderTarget(*xrRenderWindow);
432 return xrRenderWindow;
433}
434
435void SetOpenXRParameters(int index, const Ogre::Vector3& position, const Ogre::Quaternion& orientation)
436{
437 XRPosition[index] = position;
438 XROrientation[index] = orientation;
439}
#define _OgreOpenXRExport
Ogre::RenderWindow * CreateOpenXRRenderWindow(Ogre::RenderSystem *rsys)
std::vector< Ogre::Vector3 > XRPosition
std::vector< Ogre::Quaternion > XROrientation
void SetOpenXRParameters(int index, const Ogre::Vector3 &position, const Ogre::Quaternion &orientation)
#define CHECK(exp)
Definition XrError.h:59
#define CHECK_XRCMD(cmd)
Definition XrError.h:14
#define DEBUG_PRINT(...)
Definition XrError.h:20
#define CHECK_HRCMD(cmd)
Definition XrError.h:17
void notifyDeviceLost(D3D11Device *device)
Viewport * addViewport(Camera *cam, int zOrder=0, float x=0.0f, float y=0.0f, float w=0.0f, float h=0.0f) override
winrt::com_ptr< ID3D11RenderTargetView > mRenderTargetViewR
std::unique_ptr< OpenXRState > mXrState
ID3D11Texture2D * getSurface(uint index) const override
std::unique_ptr< OpenXRViewProjection > mViewProjections
ComPtr< ID3D11Texture2D > mpBackBuffer
void notifyDeviceRestored(D3D11Device *device)
ID3D11RenderTargetView * getRenderTargetView(uint index) const
winrt::com_ptr< ID3D11RenderTargetView > mRenderTargetViewL
std::unique_ptr< OpenXRSwapchain > swapchainR
std::vector< XrCompositionLayerBaseHeader * > mXrLayers
virtual void create(const String &name, unsigned int width, unsigned int height, bool fullScreen, const NameValuePairList *miscParams) override
std::unique_ptr< OpenXRSwapchain > swapchainL
XrCompositionLayerProjection mXrLayer
static constexpr XrViewConfigurationType primaryViewConfigType
unsigned int uint
Definition globals.h:67
DirectX::XMMATRIX XM_CALLCONV LoadXrPose(const XrPosef &rigidTransform)
Definition XrMath.h:255