Skip to content

Commit 6919faf

Browse files
authored
Composition layers (#44)
* Support for XrCompositionLayerProjection * Support for XrCompositionLayerQuad * Rework swapchain setup to allow multiple composition layers * Example for CompositionLayerQuad rendering overhead camera * Move rendering to be per-composition-layer * Allow composition layers to be positioned in any reference space * Composition layer quad example for quest * Add ReferenceSpace
1 parent 0d176df commit 6919faf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2801
-873
lines changed

examples/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ if(ANDROID)
66
else()
77
add_subdirectory( generic )
88
add_subdirectory( interaction_locomotion )
9+
add_subdirectory( composition_layer_quad )
910
endif()
1011

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
set( SOURCES
3+
main.cpp
4+
game.h
5+
game.cpp
6+
interaction.h
7+
interaction.cpp
8+
9+
interactions/interaction_teleport.h
10+
interactions/interaction_teleport.cpp
11+
)
12+
13+
add_executable( vsgvr_example_composition_quad ${SOURCES} )
14+
target_include_directories( vsgvr_example_composition_quad PRIVATE . )
15+
target_link_libraries( vsgvr_example_composition_quad vsg::vsg vsgvr )
16+
17+
install(TARGETS vsgvr_example_composition_quad)
18+
19+
set_target_properties( vsgvr_example_composition_quad PROPERTIES FOLDER "examples" )
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
2+
#include "game.h"
3+
4+
#include <vsgvr/app/CompositionLayerProjection.h>
5+
#include <vsgvr/app/CompositionLayerQuad.h>
6+
7+
#include "../../models/controller/controller.cpp"
8+
#include "../../models/controller/controller2.cpp"
9+
#include "../../models/world/world.cpp"
10+
#include "../../models/assets/teleport_marker/teleport_marker.cpp"
11+
12+
#include "interactions/interaction_teleport.h"
13+
14+
Game::Game(vsg::ref_ptr<vsgvr::Instance> xrInstance, vsg::ref_ptr<vsgvr::Viewer> vr, vsg::ref_ptr<vsg::Viewer> desktopViewer, bool displayDesktopWindow)
15+
: _xrInstance(xrInstance)
16+
, _vr(vr)
17+
, _desktopViewer(desktopViewer)
18+
, _desktopWindowEnabled(displayDesktopWindow)
19+
{
20+
loadScene();
21+
initVR();
22+
initActions();
23+
_lastFrameTime = vsg::clock::now();
24+
}
25+
26+
Game::~Game() {}
27+
28+
void Game::loadScene()
29+
{
30+
// User / OpenXR root space
31+
_sceneRoot = vsg::Group::create();
32+
_controllerLeft = controller();
33+
_sceneRoot->addChild(_controllerLeft);
34+
_controllerRight = controller2();
35+
_sceneRoot->addChild(_controllerRight);
36+
37+
_teleportMarker = vsg::Switch::create();
38+
auto teleportMatrix = vsg::MatrixTransform::create();
39+
_teleportMarker->addChild(false, teleportMatrix);
40+
teleportMatrix->addChild(teleport_marker());
41+
_sceneRoot->addChild(_teleportMarker);
42+
43+
// User origin - The regular vsg scene / world space,
44+
// which the user origin may move around in
45+
_userOrigin = vsgvr::UserOrigin::create();
46+
_sceneRoot->addChild(_userOrigin);
47+
48+
_ground = world(); // TODO: Not actually correct - buildings etc in the world should not be 'ground'
49+
_userOrigin->addChild(_ground);
50+
}
51+
52+
void Game::initVR()
53+
{
54+
// Create CommandGraphs to render the scene to the HMD
55+
// TODO: This only really exists because vsg::createCommandGraphForView requires
56+
// a Window instance. Other than some possible improvements later, it could use the same code as vsg
57+
// OpenXR rendering may use one or more command graphs, as decided by the viewer
58+
// (TODO: At the moment only a single CommandGraph will be used, even if there's multiple XR views)
59+
// Note: assignHeadlight = false -> Scene lighting is required
60+
auto headsetCompositionLayer = vsgvr::CompositionLayerProjection::create(_vr->getInstance(), _vr->getTraits(), _vr->getSession()->getSpace());
61+
auto xrCommandGraphs = headsetCompositionLayer->createCommandGraphsForView(_vr->getSession(), _sceneRoot, _xrCameras, false);
62+
// TODO: This is almost identical to Viewer::assignRecordAndSubmitTaskAndPresentation - The only difference is
63+
// that OpenXRViewer doesn't have presentation - If presentation was abstracted we could avoid awkward duplication here
64+
headsetCompositionLayer->assignRecordAndSubmitTask(xrCommandGraphs);
65+
headsetCompositionLayer->compile();
66+
_vr->compositionLayers.push_back(headsetCompositionLayer);
67+
68+
// TODO: Quick hack to render <something> to a CompositionLayerQuad - This should do something better
69+
{
70+
auto lookAt = vsg::LookAt::create(vsg::dvec3(0.0, 0.0, 10.0), vsg::dvec3(0.0, 0.0, 0.0), vsg::dvec3(0.0, -1.0, 0.0));
71+
// Camera parameters as if it's rendering to a desktop display, appropriate size for the displayed quad
72+
auto perspective = vsg::Perspective::create(30.0, 1920.0 / 1080.0, 0.1, 100.0);
73+
74+
// Configure rendering from an overhead camera, displaying the scene
75+
// - A camera is not require here, but is required if the application later wants a reference to it
76+
// - The composition layer's basic parameters (pose, scale) may be modified later at any time
77+
// - There is a runtime-specific limit to the number of composition layers. Only a few should be used, if more than one.
78+
auto overheadCamera = vsg::Camera::create(perspective, lookAt, vsg::ViewportState::create(0, 0, 1920, 1080));
79+
80+
// A quad positioned in the world (scene reference space)
81+
// auto quadLayer = vsgvr::CompositionLayerQuad::create(_vr->getInstance(), _vr->getTraits(), _vr->getSession()->getSpace(), 1920, 1080);
82+
// quadLayer->pose.position = { 0.0, 1.0, -4.0 };
83+
84+
// A quad positioned in front of the user's face
85+
auto faceLockedSpace = vsgvr::ReferenceSpace::create(_vr->getSession()->getSession(), XrReferenceSpaceType::XR_REFERENCE_SPACE_TYPE_VIEW);
86+
auto quadLayer = vsgvr::CompositionLayerQuad::create(_vr->getInstance(), _vr->getTraits(), faceLockedSpace, 1920, 1080);
87+
quadLayer->pose.position = { 0.0, 0.0, -4.0 };
88+
89+
/*auto rot = vsg::quat({0.0f, 5.0f, 2.5f}, {0.0f, 0.0f, 2.5f});
90+
quadLayer->pose.orientation = {
91+
rot.x, rot.y, rot.z, rot.w
92+
};*/
93+
// Quad size taking in to account aspect ratio
94+
quadLayer->sizeMeters = { 1.920f, 1.080f };
95+
96+
std::vector<vsg::ref_ptr<vsg::Camera>> cameras = { overheadCamera };
97+
auto overheadCommandGraphs = quadLayer->createCommandGraphsForView(_vr->getSession(), _sceneRoot, cameras, false);
98+
quadLayer->assignRecordAndSubmitTask(overheadCommandGraphs);
99+
quadLayer->compile();
100+
_vr->compositionLayers.push_back(quadLayer);
101+
}
102+
103+
if(_desktopWindowEnabled)
104+
{
105+
// Create a CommandGraph to render the desktop window
106+
auto lookAt = vsg::LookAt::create(vsg::dvec3(-4.0, -15.0, 25.0), vsg::dvec3(0.0, 0.0, 0.0), vsg::dvec3(0.0, 0.0, 1.0));
107+
auto desktopWindow = _desktopViewer->windows().front();
108+
auto perspective = vsg::Perspective::create(30.0,
109+
static_cast<double>(desktopWindow->extent2D().width) / static_cast<double>(desktopWindow->extent2D().height)
110+
, 0.1, 100.0
111+
);
112+
_desktopCamera = vsg::Camera::create(perspective, lookAt, vsg::ViewportState::create(desktopWindow->extent2D()));
113+
auto desktopCommandGraph = vsg::createCommandGraphForView(desktopWindow, _desktopCamera, _sceneRoot, VK_SUBPASS_CONTENTS_INLINE, false);
114+
_desktopViewer->assignRecordAndSubmitTaskAndPresentation({ desktopCommandGraph });
115+
_desktopViewer->compile();
116+
}
117+
}
118+
119+
void Game::initActions()
120+
{
121+
// Tracking the location of the user's headset is achieved by tracking the VIEW reference space
122+
// This may be done by the application, but vsgvr::Viewer can also track a set of SpaceBindings per-frame (within the session's reference space)
123+
_headPose = vsgvr::SpaceBinding::create(vsgvr::ReferenceSpace::create(_vr->getSession()->getSession(), XrReferenceSpaceType::XR_REFERENCE_SPACE_TYPE_VIEW));
124+
_vr->spaceBindings.push_back(_headPose);
125+
126+
// Input devices are tracked via ActionPoseBindings - Tracking elements from the OpenXR device tree in the session space,
127+
// along with binding the OpenXR input subsystem through to usable actions.
128+
_baseActionSet = vsgvr::ActionSet::create(_xrInstance, "controller_positions", "Controller Positions");
129+
// Pose bindings
130+
_leftHandPose = vsgvr::ActionPoseBinding::create(_xrInstance, _baseActionSet, "left_hand", "Left Hand");
131+
_rightHandPose = vsgvr::ActionPoseBinding::create(_xrInstance, _baseActionSet, "right_hand", "Right Hand");
132+
133+
_baseActionSet->actions = {
134+
_leftHandPose,
135+
_rightHandPose,
136+
};
137+
138+
_interactions.emplace("teleport", new Interaction_teleport(_xrInstance, _headPose, _leftHandPose, _teleportMarker, _ground));
139+
140+
// Ask OpenXR to suggest interaction bindings.
141+
// * If subpaths are used, list all paths that each action should be bound for
142+
// * Note that this may only be called once for each interaction profile (but may be across multiple overlapping action sets)
143+
// * If a particular profile is used, all interactions should be bound to this i.e. If grabbing items only specifies bindings for
144+
// an oculus controller, it will not be bound if the simple_controller is chosen by the runtime
145+
std::map<std::string, std::list<vsgvr::ActionSet::SuggestedInteractionBinding>> actionsToSuggest;
146+
actionsToSuggest["/interaction_profiles/khr/simple_controller"] = {
147+
{_leftHandPose, "/user/hand/left/input/aim/pose"},
148+
{_rightHandPose, "/user/hand/right/input/aim/pose"},
149+
};
150+
actionsToSuggest["/interaction_profiles/oculus/touch_controller"] = {
151+
{_leftHandPose, "/user/hand/left/input/aim/pose"},
152+
{_rightHandPose, "/user/hand/right/input/aim/pose"},
153+
};
154+
for(auto& interaction : _interactions )
155+
{
156+
for (auto& p : interaction.second->actionsToSuggest())
157+
{
158+
for( auto& x : p.second )
159+
{
160+
actionsToSuggest[p.first].push_back(x);
161+
}
162+
}
163+
}
164+
for (auto& p : actionsToSuggest)
165+
{
166+
if (! vsgvr::ActionSet::suggestInteractionBindings(_xrInstance, p.first, p.second))
167+
{
168+
throw vsg::Exception({ "Failed to configure interaction bindings for controllers" });
169+
}
170+
}
171+
172+
// All action sets the viewer should attach to the session
173+
_vr->actionSets.push_back(_baseActionSet);
174+
for( auto& interaction : _interactions )
175+
{
176+
_vr->actionSets.push_back(interaction.second->actionSet());
177+
}
178+
179+
// The action sets which are currently active (will be synced each frame)
180+
_vr->activeActionSets.push_back(_baseActionSet);
181+
182+
// TODO: Set up input action to switch between modes
183+
// TODO: Display active mode somewhere in the world, maybe as text panel when looking at controllers
184+
_vr->activeActionSets.push_back(_interactions["teleport"]->actionSet());
185+
186+
// add close handler to respond the close window button and pressing escape
187+
_desktopViewer->addEventHandler(vsg::CloseHandler::create(_desktopViewer));
188+
}
189+
190+
void Game::frame()
191+
{
192+
// OpenXR events must be checked first
193+
auto pol = _vr->pollEvents();
194+
if (pol == vsgvr::Viewer::PollEventsResult::Exit)
195+
{
196+
// User exited through VR overlay / XR runtime
197+
shouldExit = true;
198+
return;
199+
}
200+
201+
if (pol == vsgvr::Viewer::PollEventsResult::NotRunning)
202+
{
203+
return;
204+
}
205+
else if (pol == vsgvr::Viewer::PollEventsResult::RuntimeIdle)
206+
{
207+
// Reduce power usage, wait for XR to wake
208+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
209+
return;
210+
}
211+
212+
// Scene graph updates
213+
if (_leftHandPose->getTransformValid())
214+
{
215+
_controllerLeft->matrix = _leftHandPose->getTransform();
216+
}
217+
if (_rightHandPose->getTransformValid())
218+
{
219+
_controllerRight->matrix = _rightHandPose->getTransform();
220+
}
221+
222+
// The session is running in some form, and a frame must be processed
223+
// The OpenXR frame loop takes priority - Acquire a frame to render into
224+
auto shouldQuit = false;
225+
226+
if(_desktopWindowEnabled)
227+
{
228+
// Match the desktop camera to the HMD view
229+
_desktopCamera->viewMatrix = _xrCameras.front()->viewMatrix;
230+
_desktopCamera->projectionMatrix = _xrCameras.front()->projectionMatrix;
231+
232+
// Desktop render
233+
// * The scene graph is updated by the desktop render
234+
// * if PollEventsResult::RunningDontRender the desktop render could be skipped
235+
if (_desktopViewer->advanceToNextFrame())
236+
{
237+
_desktopViewer->handleEvents();
238+
_desktopViewer->update();
239+
_desktopViewer->recordAndSubmit();
240+
_desktopViewer->present();
241+
}
242+
else
243+
{
244+
// Desktop window was closed
245+
shouldQuit = true;
246+
return;
247+
}
248+
}
249+
250+
if (_vr->advanceToNextFrame())
251+
{
252+
if (pol == vsgvr::Viewer::PollEventsResult::RunningDontRender)
253+
{
254+
// XR Runtime requested that rendering is not performed (not visible to user)
255+
// While this happens frames must still be acquired and released however, in
256+
// order to synchronise with the OpenXR runtime
257+
}
258+
else
259+
{
260+
for (auto& interaction : _interactions)
261+
{
262+
if (std::find(_vr->activeActionSets.begin(), _vr->activeActionSets.end(),
263+
interaction.second->actionSet()) != _vr->activeActionSets.end())
264+
{
265+
auto deltaT = static_cast<double>(
266+
std::chrono::duration_cast<std::chrono::microseconds>(_vr->getFrameStamp()->time - _lastFrameTime).count()
267+
) / 1e6;
268+
_lastFrameTime = _vr->getFrameStamp()->time;
269+
interaction.second->frame(_userOrigin, *this, deltaT);
270+
}
271+
}
272+
273+
// Render to the HMD
274+
_vr->recordAndSubmit(); // Render XR frame
275+
}
276+
}
277+
278+
// End the frame, and present to user
279+
// Frames must be explicitly released, even if the previous advanceToNextFrame returned false (PollEventsResult::RunningDontRender)
280+
_vr->releaseFrame();
281+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#pragma once
2+
3+
#include <vsg/all.h>
4+
#include <vsgvr/app/Viewer.h>
5+
#include <vsgvr/app/UserOrigin.h>
6+
#include <vsgvr/actions/Action.h>
7+
#include <vsgvr/actions/ActionSet.h>
8+
#include <vsgvr/actions/ActionPoseBinding.h>
9+
#include <vsgvr/actions/SpaceBinding.h>
10+
11+
#include "interaction.h"
12+
13+
#include <memory>
14+
15+
class Game {
16+
public:
17+
Game(vsg::ref_ptr<vsgvr::Instance> xrInstance, vsg::ref_ptr<vsgvr::Viewer> vr, vsg::ref_ptr<vsg::Viewer> desktopViewer, bool displayDesktopWindow);
18+
~Game();
19+
20+
bool shouldExit = false;
21+
22+
void frame();
23+
24+
vsg::ref_ptr<vsgvr::UserOrigin> userOrigin() const { return _userOrigin; }
25+
26+
private:
27+
void loadScene();
28+
void initVR();
29+
void initActions();
30+
31+
vsg::ref_ptr<vsgvr::Instance> _xrInstance;
32+
vsg::ref_ptr<vsgvr::Viewer> _vr;
33+
vsg::ref_ptr<vsg::Viewer> _desktopViewer;
34+
bool _desktopWindowEnabled = false;
35+
36+
// The user / OpenXR root space - Contains elements such as controllers
37+
vsg::ref_ptr<vsg::Group> _sceneRoot;
38+
vsg::ref_ptr<vsg::MatrixTransform> _controllerLeft;
39+
vsg::ref_ptr<vsg::MatrixTransform> _controllerRight;
40+
vsg::ref_ptr<vsg::Switch> _teleportMarker;
41+
42+
// A transform allowing the player to move within the normal vsg scene
43+
vsg::ref_ptr<vsgvr::UserOrigin> _userOrigin;
44+
vsg::ref_ptr<vsg::Group> _ground;
45+
46+
std::vector<vsg::ref_ptr<vsg::Camera>> _xrCameras;
47+
vsg::ref_ptr<vsg::Camera> _desktopCamera;
48+
49+
// Actions and behaviours
50+
vsg::ref_ptr<vsgvr::ActionSet> _baseActionSet;
51+
vsg::ref_ptr<vsgvr::ActionPoseBinding> _leftHandPose;
52+
vsg::ref_ptr<vsgvr::ActionPoseBinding> _rightHandPose;
53+
vsg::ref_ptr<vsgvr::SpaceBinding> _headPose;
54+
55+
std::map<std::string, std::unique_ptr<Interaction>> _interactions;
56+
57+
vsg::time_point _lastFrameTime;
58+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
#include "interaction.h"
3+
4+
Interaction::Interaction() {}
5+
6+
Interaction::~Interaction() {}

0 commit comments

Comments
 (0)