Skip to content

Commit 060bc18

Browse files
authored
Fix VR controller ray hit-test and use-after-free in pollEvents (#3245)
1 parent 5251354 commit 060bc18

8 files changed

Lines changed: 168 additions & 5 deletions

File tree

axmol/base/EventDispatcher.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ static PointerEvent::CaptureBits makePointerCaptureBits(PointerEvent* event)
115115

116116
static bool pointerHitTest(PointerEvent* event, const Camera* camera, PointerEventListener* listener, Node* target)
117117
{
118+
event->setCamera(camera);
119+
118120
if (camera && event->getPointerType() != PointerType::Controller)
119121
event->setRay(camera->screenToRay(event->getPoint()));
120122

@@ -1877,4 +1879,47 @@ const Camera* EventDispatcher::findHitCameraForListener(PointerEvent* event,
18771879
return nullptr;
18781880
}
18791881

1882+
PointerHitResult EventDispatcher::hitTestPointerEvent(PointerEvent* event)
1883+
{
1884+
if (!event)
1885+
return {};
1886+
1887+
event->clearHitResult();
1888+
event->setCamera(nullptr);
1889+
1890+
sortEventListeners(PointerEventListener::LISTENER_ID);
1891+
1892+
auto listeners = getListeners(PointerEventListener::LISTENER_ID);
1893+
auto scene = Director::getInstance()->getRunningScene();
1894+
if (!listeners || !scene)
1895+
return {};
1896+
1897+
auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners();
1898+
if (!sceneGraphPriorityListeners)
1899+
return {};
1900+
1901+
auto cameras = scene->getCameras();
1902+
for (auto&& listener : *sceneGraphPriorityListeners)
1903+
{
1904+
if (!listener || !listener->isEnabled() || listener->isPaused() || !listener->isAttached())
1905+
continue;
1906+
1907+
auto target = listener->getAssociatedNode();
1908+
if (!target || _nodePriorityMap.find(target) == _nodePriorityMap.end())
1909+
continue;
1910+
1911+
auto pointerListener = static_cast<PointerEventListener*>(listener);
1912+
if (!findHitCameraForListener(event, pointerListener, cameras))
1913+
continue;
1914+
1915+
auto result = event->getHitResult();
1916+
event->setCamera(nullptr);
1917+
return result;
1918+
}
1919+
1920+
event->clearHitResult();
1921+
event->setCamera(nullptr);
1922+
return {};
1923+
}
1924+
18801925
} // namespace ax

axmol/base/EventDispatcher.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class CustomEvent;
5656
class CustomEventListener;
5757
class PointerEventListener;
5858
class Camera;
59+
class InputSystem;
5960

6061
/** @class EventDispatcher
6162
* @brief This class manages event listener subscriptions
@@ -217,6 +218,7 @@ class AX_DLL EventDispatcher : public Object
217218

218219
protected:
219220
friend class Node;
221+
friend class InputSystem;
220222

221223
/** Sets the dirty flag for a node. */
222224
void setDirtyForNode(Node* node);
@@ -286,6 +288,8 @@ class AX_DLL EventDispatcher : public Object
286288

287289
void dispatchPointerEvent(PointerEvent* event);
288290

291+
PointerHitResult hitTestPointerEvent(PointerEvent* event);
292+
289293
/** Associates node with event listener */
290294
void associateNodeAndEventListener(Node* node, EventListener* listener);
291295

axmol/base/InputSystem.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,19 @@ PointerHitResult InputSystem::handleVRPointerEvent(InputPhase phase,
607607
return dispatchVRPointerEvent(phase, point, ray, state);
608608
}
609609

610+
PointerHitResult InputSystem::hitTestVRPointer(Vec2 point, const Ray& ray, const PointerInputState& state)
611+
{
612+
if (!_interactive)
613+
return {};
614+
615+
// Reticle refresh needs a pure "currently under the ray" hit-test.
616+
// PointerMove hit-tests may intentionally return the previous hovered widget
617+
// to emit hover-exit events, and that path can report no fresh hit point.
618+
_isolatedMoveEvent.setPointerInfo(InputPhase::PointerScroll, nativeToScreen(point), state);
619+
_isolatedMoveEvent.setRay(ray);
620+
return _eventDispatcher->hitTestPointerEvent(&_isolatedMoveEvent);
621+
}
622+
610623
PointerHitResult InputSystem::dispatchVRPointerEvent(InputPhase phase,
611624
Vec2 point,
612625
const Ray& ray,

axmol/base/InputSystem.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ class AX_DLL InputSystem
9696

9797
// VR controller input: PointerType::Controller with a 3D ray.
9898
PointerHitResult handleVRPointerEvent(InputPhase phase, Vec2 point, const Ray& ray, const PointerInputState& state);
99+
PointerHitResult hitTestVRPointer(Vec2 point, const Ray& ray, const PointerInputState& state);
99100
PointerHitResult handleVRPointerScroll(Vec2 point,
100101
Vec2 scrollDelta,
101102
const Ray& ray,

axmol/vr/OpenXRDriver.cpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include "axmol/base/Logging.h"
4646
#include "axmol/base/InputSystem.h"
4747
#include "axmol/math/Quat.h"
48+
#include "axmol/scene/Camera.h"
4849
#include "axmol/scene/Scene.h"
4950
#include "axmol/platform/RenderView.h"
5051
#include "axmol/platform/Application.h"
@@ -264,6 +265,18 @@ Vec2 OpenXRDriver::xrToVec2(const XrVector2f& v)
264265
return Vec2(v.x, v.y);
265266
}
266267

268+
void OpenXRDriver::setPointerRayTransform(const Mat4& transform)
269+
{
270+
_pointerRayTransform = transform;
271+
_pointerRayTransformValid = true;
272+
}
273+
274+
void OpenXRDriver::clearPointerRayTransform()
275+
{
276+
_pointerRayTransform = Mat4::identity;
277+
_pointerRayTransformValid = false;
278+
}
279+
267280
// ---------------------------------------------------------------------------
268281
// OpenXR event polling (session state management)
269282
// ---------------------------------------------------------------------------
@@ -1419,7 +1432,11 @@ void OpenXRDriver::pollXrActions(XrTime predictedDisplayTime)
14191432
return;
14201433
}
14211434

1422-
Mat4 controllerToWorld = _headViewTransformValid ? _headViewTransform.getInversed() : Mat4::identity;
1435+
// Controller poses are located in the same OpenXR local space as the eye poses.
1436+
// VRSceneCompositor owns the stable pointer-ray camera and pushes its world
1437+
// transform here before polling actions. Do not use the inverse HMD pose here;
1438+
// that converts the ray to head-relative space and makes scene hit testing miss.
1439+
const Mat4& controllerToWorld = _pointerRayTransformValid ? _pointerRayTransform : Mat4::identity;
14231440

14241441
for (uint32_t hand = 0; hand < 2; ++hand)
14251442
{
@@ -1635,6 +1652,11 @@ void OpenXRDriver::pollXrActions(XrTime predictedDisplayTime)
16351652
ctrl.lastPointerEventRay = eventRay;
16361653
ctrl.lastPointerEventRayValid = true;
16371654
}
1655+
else
1656+
{
1657+
hitResult = InputSystem::getInstance()->hitTestVRPointer(centerPoint, eventRay, inputState);
1658+
hasHitResult = true;
1659+
}
16381660

16391661
constexpr float thumbstickScrollDeadzone = 0.0001f;
16401662
if (std::abs(ctrl.thumbstick.y) > thumbstickScrollDeadzone)
@@ -1660,6 +1682,12 @@ void OpenXRDriver::pollXrActions(XrTime predictedDisplayTime)
16601682
{
16611683
Vec3 visualHitPoint = hitResult.worldPoint;
16621684

1685+
if (_pointerRayTransformValid && hitResult.camera)
1686+
{
1687+
hitResult.camera->getWorldToNodeTransform().transformPoint(&visualHitPoint);
1688+
_pointerRayTransform.transformPoint(&visualHitPoint);
1689+
}
1690+
16631691
const float hitDistance =
16641692
std::max(0.0f, (visualHitPoint - ctrl.currentRay.origin).dot(ctrl.currentRay.direction));
16651693
const Vec3 closestPoint = ctrl.currentRay.origin + ctrl.currentRay.direction * hitDistance;

axmol/vr/OpenXRDriver.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ class AX_DLL OpenXRDriver
156156
const ControllerState* getControllers() const { return _controllers; }
157157
Mat4 getPointerViewTransform() const { return _headViewTransform; }
158158
bool isPointerViewTransformValid() const { return _headViewTransformValid; }
159+
void setPointerRayTransform(const Mat4& transform);
160+
void clearPointerRayTransform();
161+
void setCompositorAlive(bool alive) { _compositorAlive = alive; }
162+
bool isCompositorAlive() const { return _compositorAlive; }
163+
159164
void setXrToSceneScale(float scale) { _xrToSceneScale = scale > 0.0f ? scale : 1.0f; }
160165
float getXrToSceneScale() const { return _xrToSceneScale; }
161166

@@ -241,10 +246,13 @@ class AX_DLL OpenXRDriver
241246
std::unique_ptr<rhi::OpenXRVulkanInterop> _vulkanInterop;
242247
#endif
243248

249+
bool _compositorAlive{false};
244250
float _xrToSceneScale{1.0f};
245251

246252
Mat4 _headViewTransform{Mat4::identity};
247253
bool _headViewTransformValid{false};
254+
Mat4 _pointerRayTransform{Mat4::identity};
255+
bool _pointerRayTransformValid{false};
248256

249257
void* _graphicsBindingStorage{nullptr};
250258

axmol/vr/VRSceneCompositor.cpp

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ VRSceneCompositor::VRSceneCompositor()
9494

9595
VRSceneCompositor::~VRSceneCompositor()
9696
{
97+
if (_xrDriver)
98+
_xrDriver->setCompositorAlive(false);
9799
shutdownControllerRayResources();
98100
}
99101

@@ -114,18 +116,24 @@ XrSession VRSceneCompositor::getXrSession() const
114116

115117
void VRSceneCompositor::setXrDriver(OpenXRDriver* context)
116118
{
119+
if (_xrDriver)
120+
_xrDriver->setCompositorAlive(false);
117121
_xrDriver = context;
122+
if (_xrDriver)
123+
_xrDriver->setCompositorAlive(true);
118124
}
119125

120126
void VRSceneCompositor::pollEvents()
121127
{
128+
auto xrDriver = _xrDriver;
122129
SceneCompositor::pollEvents();
123130

124-
if (!_xrDriver)
131+
if (!xrDriver || !xrDriver->isCompositorAlive())
125132
return;
126133

127-
_xrDriver->setXrToSceneScale(_xrToSceneScale);
128-
_xrDriver->pollEvents();
134+
xrDriver->setXrToSceneScale(_xrToSceneScale);
135+
syncPointerRayCamera(_director->getRunningScene());
136+
xrDriver->pollEvents();
129137
}
130138

131139
bool VRSceneCompositor::isVRActive() const
@@ -154,6 +162,58 @@ void VRSceneCompositor::setXrToSceneScale(float scale)
154162
_xrDriver->setXrToSceneScale(_xrToSceneScale);
155163
}
156164

165+
Camera* VRSceneCompositor::selectPointerRaySourceCamera(Scene* scene) const
166+
{
167+
if (!scene)
168+
return nullptr;
169+
170+
auto defaultCamera = scene->getDefaultCamera();
171+
if (defaultCamera && defaultCamera->isVisible())
172+
return defaultCamera;
173+
174+
for (auto camera : scene->getCameras())
175+
{
176+
if (camera && camera->isVisible())
177+
return camera;
178+
}
179+
180+
return defaultCamera;
181+
}
182+
183+
Camera* VRSceneCompositor::ensurePointerRayCamera(Scene* scene)
184+
{
185+
auto sourceCamera = selectPointerRaySourceCamera(scene);
186+
if (!sourceCamera)
187+
return nullptr;
188+
189+
if (!_pointerRayCamera)
190+
_pointerRayCamera = RefPtr<Camera>(Camera::createPerspective(60.0f, 1.0f, _nearZ, _farZ));
191+
192+
const auto canvasSize = _director->getCanvasSize();
193+
const float aspect = canvasSize.height > 0.0f ? canvasSize.width / canvasSize.height : 1.0f;
194+
_pointerRayCamera->initPerspective(60.0f, aspect, _nearZ, _farZ);
195+
_pointerRayCamera->setNodeToParentTransform(sourceCamera->getNodeToWorldTransform());
196+
_pointerRayCamera->setAdditionalTransform(Mat4::identity);
197+
_pointerRayCamera->setCameraFlag(sourceCamera->getCameraFlag());
198+
199+
return _pointerRayCamera.get();
200+
}
201+
202+
void VRSceneCompositor::syncPointerRayCamera(Scene* scene)
203+
{
204+
if (!_xrDriver)
205+
return;
206+
207+
auto camera = ensurePointerRayCamera(scene);
208+
if (!camera)
209+
{
210+
_xrDriver->clearPointerRayTransform();
211+
return;
212+
}
213+
214+
_xrDriver->setPointerRayTransform(camera->getNodeToWorldTransform());
215+
}
216+
157217
void VRSceneCompositor::ensureControllerRayResources()
158218
{
159219
if (_controllerRayResourcesInitialized)
@@ -361,7 +421,7 @@ void VRSceneCompositor::renderScene(Renderer* renderer, Scene* scene)
361421
camera->setAdditionalTransform(Mat4::identity);
362422
}
363423

364-
auto rayCamera = eyeCamera;
424+
auto rayCamera = _pointerRayCamera ? _pointerRayCamera.get() : eyeCamera;
365425
if (rayCamera)
366426
{
367427
Camera::setVisitingCamera(rayCamera);

axmol/vr/VRSceneCompositor.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ class AX_DLL VRSceneCompositor : public SceneCompositor
8888
private:
8989
void ensureControllerRayResources();
9090
void shutdownControllerRayResources();
91+
Camera* ensurePointerRayCamera(Scene* scene);
92+
Camera* selectPointerRaySourceCamera(Scene* scene) const;
93+
void syncPointerRayCamera(Scene* scene);
9194
void drawControllerRays(Renderer* renderer, uint32_t eyeIdx, const XrView& view);
9295
void onBeforeControllerRayDraw();
9396
void onAfterControllerRayDraw();
@@ -111,6 +114,7 @@ class AX_DLL VRSceneCompositor : public SceneCompositor
111114
float _xrToSceneScale{1.0f};
112115

113116
RefPtr<RenderTexturePass> _rtPass;
117+
RefPtr<Camera> _pointerRayCamera;
114118

115119
ScissorRect _sourceScissorRect;
116120
LinearStack<VRScissorTransform> _scissorTransformStack;

0 commit comments

Comments
 (0)