Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ See the [Ecosystem](https://nvidia.github.io/IsaacTeleop/main/overview/ecosystem

## Quick Start

### WebXR client settings for world-locked content

The CloudXR WebXR client ships two defaults that do not agree with each other, and the combination makes world-locked content sit at a fixed offset from your head instead of staying anchored to the room. Before running anything that draws content at a known height above the floor, set these in the client's config panel:

| Setting | Default | Use |
|---|---|---|
| Reference Space | `auto` | `local-floor` |
| Y Offset (centimeters): Vertical | `-155` | `0` |

`auto` walks `local-floor → local → viewer → unbounded` and the −155 cm offset is applied whichever one it lands on. That offset is a hand-rolled floor estimate for a head-height origin, so it double-corrects a `local-floor` space that is already floor-referenced — and if the chain reaches `viewer`, which is head-locked by definition, the scene attaches to your face. Selecting `local-floor` with a zero offset makes the origin a measured floor and applies no second correction. Tracked as [NVIDIA/IsaacTeleop#871](https://github.com/NVIDIA/IsaacTeleop/issues/871).

### Documentation

Our [documentation page](https://nvidia.github.io/IsaacTeleop) provides everything you need to get started, including detailed tutorials and step-by-step guides. Follow these links to learn more:
Expand Down
2 changes: 2 additions & 0 deletions src/python/isaacteleop/viz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
EquirectLayerConfig,
EquirectLayerPlacement,
DisplayMode,
XrReferenceSpace,
Fov,
FrameInfo,
FrameTimingStats,
Expand Down Expand Up @@ -69,6 +70,7 @@
"EquirectLayerConfig",
"EquirectLayerPlacement",
"DisplayMode",
"XrReferenceSpace",
"Fov",
"FrameInfo",
"FrameTimingStats",
Expand Down
8 changes: 8 additions & 0 deletions src/viz/python/core_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <viz/core/viz_types.hpp>
#include <viz/session/display_mode.hpp>
#include <viz/session/viz_session.hpp> // SessionState
#include <viz/session/xr_reference_space.hpp>

#include <cstdint>
#include <memory>
Expand All @@ -33,6 +34,13 @@ void bind_core(py::module_& m)
.value("kWindow", viz::DisplayMode::kWindow)
.value("kXr", viz::DisplayMode::kXr);

py::enum_<viz::XrReferenceSpace>(m, "XrReferenceSpace")
.value("kLocal", viz::XrReferenceSpace::kLocal, "Origin at the headset's start pose. y=0 is HEAD HEIGHT.")
.value("kLocalFloor", viz::XrReferenceSpace::kLocalFloor,
"Origin on the floor below the headset's start pose. y=0 is the FLOOR.")
.value("kStage", viz::XrReferenceSpace::kStage,
"Origin at the runtime's stage centre, on the floor. Does not follow the operator.");

py::enum_<viz::PixelFormat>(m, "PixelFormat")
.value("kRGBA8", viz::PixelFormat::kRGBA8)
.value("kD32F", viz::PixelFormat::kD32F);
Expand Down
1 change: 1 addition & 0 deletions src/viz/python/session_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ void bind_session(py::module_& m)
.def_readwrite("xr_system_wait_seconds", &viz::VizSession::Config::xr_system_wait_seconds)
.def_readwrite("xr_near_z", &viz::VizSession::Config::xr_near_z)
.def_readwrite("xr_far_z", &viz::VizSession::Config::xr_far_z)
.def_readwrite("xr_reference_space", &viz::VizSession::Config::xr_reference_space)
.def_readwrite("required_extensions", &viz::VizSession::Config::required_extensions)
.def_readwrite("gpu_timing", &viz::VizSession::Config::gpu_timing)
.def_property(
Expand Down
1 change: 1 addition & 0 deletions src/viz/session/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ add_library(viz_session STATIC
inc/viz/session/viz_session.hpp
inc/viz/session/window_backend.hpp
inc/viz/session/xr_backend.hpp
inc/viz/session/xr_reference_space.hpp
)

target_include_directories(viz_session
Expand Down
7 changes: 7 additions & 0 deletions src/viz/session/cpp/inc/viz/session/viz_session.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "frame_info.hpp"
#include "layer_base.hpp"
#include "viz_compositor.hpp"
#include "xr_reference_space.hpp"

#include <oxr_utils/oxr_session_handles.hpp>
#include <viz/core/host_image.hpp>
Expand Down Expand Up @@ -87,6 +88,12 @@ class VizSession
float xr_near_z = 0.05f;
float xr_far_z = 100.0f;

// kXr-only: where the session's origin sits. kLocal (the default and
// the previous hard-wired behaviour) puts y=0 at head height. An
// unavailable space throws at session creation naming the space rather
// than silently degrading to a different origin.
XrReferenceSpace xr_reference_space = XrReferenceSpace::kLocal;

// Opt-in GPU timestamp queries (render-pass, post-pass, total).
// Off by default — production builds shouldn't pay. Read via
// get_gpu_timing().
Expand Down
33 changes: 33 additions & 0 deletions src/viz/session/cpp/inc/viz/session/xr_reference_space.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

#pragma once

namespace viz
{

// Which OpenXR reference space a kXr session's world is expressed in -- i.e.
// where the origin of every pose the app sends and receives sits. A viz-level
// enum rather than XrReferenceSpaceType so the public header and the Python
// binding stay free of OpenXR types, matching DisplayMode.
//
// The choice is about the origin's HEIGHT, and it is not cosmetic: an app
// drawing world-locked geometry above the floor gets that height wrong by a
// whole person if it guesses.
enum class XrReferenceSpace
{
// Origin at the headset's start pose, gravity-aligned. y=0 is HEAD HEIGHT
// and the floor cannot be recovered from it. The default, because it is
// the one space every runtime has.
kLocal,
// Origin on the floor below the headset's start position; core in
// OpenXR 1.1. Keeps kLocal's position and facing and moves y=0 to the
// floor, which is what anything world-locked wants.
kLocalFloor,
// Origin at the runtime's stage centre, on the floor. Floor-referenced but
// does NOT follow the operator: position and facing come from the guardian
// setup.
kStage,
};

} // namespace viz
17 changes: 17 additions & 0 deletions src/viz/session/cpp/viz_session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ namespace viz
namespace
{

// A switch with no default, so a value added to the enum fails to compile here
// rather than silently mapping to LOCAL.
XrReferenceSpaceType to_xr_reference_space_type(XrReferenceSpace space)
{
switch (space)
{
case XrReferenceSpace::kLocal:
return XR_REFERENCE_SPACE_TYPE_LOCAL;
case XrReferenceSpace::kLocalFloor:
return XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR;
case XrReferenceSpace::kStage:
return XR_REFERENCE_SPACE_TYPE_STAGE;
}
throw std::runtime_error("VizSession: unknown XrReferenceSpace");
}

std::unique_ptr<DisplayBackend> make_backend(const VizSession::Config& cfg)
{
switch (cfg.mode)
Expand All @@ -42,6 +58,7 @@ std::unique_ptr<DisplayBackend> make_backend(const VizSession::Config& cfg)
// Env blend mode chosen at runtime from what the system advertises.
xc.session_config.near_z = cfg.xr_near_z;
xc.session_config.far_z = cfg.xr_far_z;
xc.session_config.reference_space_type = to_xr_reference_space_type(cfg.xr_reference_space);
return std::make_unique<XrBackend>(std::move(xc));
}
}
Expand Down
44 changes: 44 additions & 0 deletions src/viz/xr/cpp/openxr_session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
#include <cstdio>
#include <cstring>
#include <stdexcept>
#include <string>
#include <thread>
#include <unordered_set>
#include <vector>

namespace viz
{
Expand All @@ -29,6 +31,27 @@ void check_xr(XrResult r, const char* what)
}
}

// Names for the log line in create_reference_space. Only the four this
// codebase can ask for or be offered; anything else is a runtime extension
// nothing here interprets, so it prints as "other" rather than a raw number
// that would still need looking up.
const char* reference_space_name(XrReferenceSpaceType type)
{
switch (type)
{
case XR_REFERENCE_SPACE_TYPE_VIEW:
return "VIEW";
case XR_REFERENCE_SPACE_TYPE_LOCAL:
return "LOCAL";
case XR_REFERENCE_SPACE_TYPE_STAGE:
return "STAGE";
case XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR:
return "LOCAL_FLOOR";
default:
return "other";
}
}

} // namespace

OpenXrSession::OpenXrSession(const std::string& app_name,
Expand Down Expand Up @@ -305,6 +328,27 @@ void OpenXrSession::create_session(const VkContext& vk)

void OpenXrSession::create_reference_space(XrReferenceSpaceType type)
{
// What the runtime offers, logged beside what was asked for. There is no
// fallback on purpose: check_xr throws and names the space, where quietly
// substituting a different origin would render every world-locked thing at
// the wrong height with nothing in the log.
uint32_t space_count = 0;
if (XR_SUCCEEDED(xrEnumerateReferenceSpaces(session_.get(), 0, &space_count, nullptr)) && space_count > 0)
{
std::vector<XrReferenceSpaceType> offered(space_count);
if (XR_SUCCEEDED(xrEnumerateReferenceSpaces(session_.get(), space_count, &space_count, offered.data())))
{
std::string list;
for (const XrReferenceSpaceType t : offered)
{
list += (list.empty() ? "" : " ");
list += reference_space_name(t);
}
std::fprintf(stderr, "OpenXrSession: reference space = %s (runtime offers: %s)\n",
reference_space_name(type), list.c_str());
}
}

XrReferenceSpaceCreateInfo info{ XR_TYPE_REFERENCE_SPACE_CREATE_INFO };
info.referenceSpaceType = type;
info.poseInReferenceSpace.orientation = XrQuaternionf{ 0.0f, 0.0f, 0.0f, 1.0f };
Expand Down
Loading