-
Notifications
You must be signed in to change notification settings - Fork 79
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added initial WebXR entry UI and export target.
Added thread support to handle resource queue loading. Added new StartXR scene to manage starting the OpenXR or WebXR instance. Fixed loading_screen so it doesn't produce NANs when the headset is off for a while.
- Loading branch information
1 parent
b2d0d30
commit ce0eb84
Showing
7 changed files
with
366 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,271 @@ | ||
tool | ||
class_name XRToolsStartXR | ||
extends Node | ||
|
||
|
||
## XRTools Start XR Class | ||
## | ||
## This class supports both the OpenXR and WebXR interfaces, and handles | ||
## the initialization of the interface as well as reporting when the user | ||
## starts and ends the VR session. | ||
## | ||
## For OpenXR this class also supports passthrough on compatible devices such | ||
## as the Meta Quest 1 and 2. | ||
|
||
|
||
## This signal is emitted when XR becomes active. For OpenXR this corresponds | ||
## with the 'openxr_focused_state' signal, and for WebXR this corresponds with | ||
## the 'session_started' signal. | ||
signal xr_started | ||
|
||
## This signal is emitted when XR ends. For OpenXR this corresponds with the | ||
## 'openxr_session_synchronized' state, and for WebXR this corresponds with the | ||
## 'session_ended' signal. | ||
signal xr_ended | ||
|
||
|
||
## If true, the XR interface is automatically initialized | ||
export var auto_initialize : bool = true | ||
|
||
## If true, the XR passthrough is enabled (OpenXR only) | ||
export var enable_passthrough : bool = false setget _set_enable_passthrough | ||
|
||
## Physics rate multiplier compared to HMD frame rate | ||
export var physics_rate_multiplier : float = 1 | ||
|
||
|
||
## Current XR interface | ||
var xr_interface : ARVRInterface | ||
|
||
## XR active flag | ||
var xr_active : bool = false | ||
|
||
# OpenXR configuration (of type OpenXRConfig.gdns) | ||
var _openxr_configuration | ||
|
||
# OpenXR enabled extensions | ||
var _openxr_enabled_extensions : Array | ||
|
||
|
||
# Handle auto-initialization when ready | ||
func _ready() -> void: | ||
if !Engine.editor_hint and auto_initialize: | ||
initialize() | ||
|
||
|
||
## Initialize the XR interface | ||
func initialize() -> bool: | ||
# Check for OpenXR interface | ||
xr_interface = ARVRServer.find_interface('OpenXR') | ||
if xr_interface: | ||
_setup_for_openxr() | ||
return true | ||
|
||
# Check for WebXR interface | ||
xr_interface = ARVRServer.find_interface('WebXR') | ||
if xr_interface: | ||
_setup_for_webxr() | ||
return true | ||
|
||
# No XR interface | ||
xr_interface = null | ||
print("No XR interface detected") | ||
return false | ||
|
||
|
||
# Check for configuration issues | ||
func _get_configuration_warning(): | ||
if physics_rate_multiplier < 1.0: | ||
return "Physics rate multiplier should be at least 1x the HMD rate" | ||
|
||
return "" | ||
|
||
|
||
# Perform OpenXR setup | ||
func _setup_for_openxr() -> void: | ||
print("OpenXR: Configuring interface") | ||
|
||
# Load the OpenXR configuration resource | ||
var openxr_config_res := load("res://addons/godot-openxr/config/OpenXRConfig.gdns") | ||
if not openxr_config_res: | ||
push_error("OpenXR: Unable to load OpenXRConfig.gdns") | ||
return | ||
|
||
# Create the OpenXR configuration class | ||
_openxr_configuration = openxr_config_res.new() | ||
|
||
# Initialize the OpenXR interface | ||
if not xr_interface.initialize(): | ||
push_error("OpenXR: Failed to initialize") | ||
return | ||
|
||
# Connect the OpenXR events | ||
ARVRServer.connect("openxr_session_begun", self, "_on_openxr_session_begun") | ||
ARVRServer.connect("openxr_session_synchronized", self, "_on_openxr_session_synchronized") | ||
ARVRServer.connect("openxr_focused_state", self, "_on_openxr_focused_state") | ||
|
||
# Read the OpenXR enabled extensions | ||
_openxr_enabled_extensions = _openxr_configuration.get_enabled_extensions() | ||
|
||
# Check for passthrough | ||
if enable_passthrough and _openxr_is_passthrough_supported(): | ||
enable_passthrough = _openxr_start_passthrough() | ||
|
||
# Switch the viewport to XR | ||
get_viewport().arvr = true | ||
|
||
|
||
# Handle OpenXR session ready | ||
func _on_openxr_session_begun() -> void: | ||
print("OpenXR: Session begun") | ||
|
||
# Our interface will tell us whether we should keep our render buffer in linear color space | ||
get_viewport().keep_3d_linear = _openxr_configuration.keep_3d_linear() | ||
|
||
# increase our physics engine update speed | ||
var refresh_rate : float = _openxr_configuration.get_refresh_rate() | ||
if refresh_rate > 0: | ||
# Report provided frame rare | ||
print("OpenXR: HMD refresh rate is set to ", str(refresh_rate)) | ||
else: | ||
# None provided, assume a standard rate | ||
print("OpenXR: No refresh rate given by XR runtime") | ||
refresh_rate = 144 | ||
|
||
# Pick a physics rate | ||
var physics_rate := int(refresh_rate * physics_rate_multiplier + 0.5) | ||
print("Setting physics rate to ", physics_rate) | ||
Engine.iterations_per_second = physics_rate | ||
|
||
|
||
# Handle OpenXR session synchronized | ||
func _on_openxr_session_synchronized() -> void: | ||
# Report the XR ending | ||
if xr_active: | ||
print("OpenXR: XR ended (session_synchronized)") | ||
xr_active = false | ||
emit_signal("xr_ended") | ||
|
||
|
||
# Handle OpenXR focused state | ||
func _on_openxr_focused_state() -> void: | ||
# Report the XR starting | ||
if not xr_active: | ||
print("OpenXR: XR started (focused_state)") | ||
xr_active = true | ||
emit_signal("xr_started") | ||
|
||
|
||
# Handle changes to the enable_passthrough property | ||
func _set_enable_passthrough(p_new_value : bool) -> void: | ||
# Save the new value | ||
enable_passthrough = p_new_value | ||
|
||
# Only actually start our passthrough if our interface has been instanced | ||
# if not this will be delayed until initialise is successfully called. | ||
if xr_interface and _openxr_configuration: | ||
if enable_passthrough: | ||
# unset enable_passthrough if we can't start it. | ||
enable_passthrough = _openxr_start_passthrough() | ||
else: | ||
_openxr_stop_passthrough() | ||
|
||
|
||
# Test if passthrough is supported | ||
func _openxr_is_passthrough_supported() -> bool: | ||
return _openxr_enabled_extensions.find("XR_FB_passthrough") >= 0 | ||
|
||
|
||
# Start OpenXR passthrough | ||
func _openxr_start_passthrough() -> bool: | ||
# Set viewport transparent background | ||
get_viewport().transparent_bg = true | ||
|
||
# Enable passthrough | ||
return _openxr_configuration.start_passthrough() | ||
|
||
|
||
# Stop OpenXR passthrough | ||
func _openxr_stop_passthrough() -> void: | ||
# Clear viewport transparent background | ||
get_viewport().transparent_bg = false | ||
|
||
# Disable passthrough | ||
_openxr_configuration.stop_passthrough() | ||
|
||
|
||
# Perform WebXR setup | ||
func _setup_for_webxr() -> void: | ||
print("WebXR: Configuring interface") | ||
|
||
# Connect the WebXR events | ||
xr_interface.connect("session_supported", self, "_on_webxr_session_supported") | ||
xr_interface.connect("session_started", self, "_on_webxr_session_started") | ||
xr_interface.connect("session_ended", self, "_on_webxr_session_ended") | ||
xr_interface.connect("session_failed", self, "_on_webxr_session_failed") | ||
|
||
# WebXR currently has no means of querying the refresh rate, so use | ||
# something sufficiently high | ||
Engine.iterations_per_second = 144 | ||
|
||
# This returns immediately - our _webxr_session_supported() method | ||
# (which we connected to the "session_supported" signal above) will | ||
# be called sometime later to let us know if it's supported or not. | ||
xr_interface.is_session_supported("immersive-vr") | ||
|
||
|
||
# Handle WebXR session supported check | ||
func _on_webxr_session_supported(session_mode: String, supported: bool) -> void: | ||
if session_mode == "immersive-vr": | ||
if supported: | ||
# WebXR supported - show canvas on web browser to enter WebVR | ||
$EnterWebXR.visible = true | ||
else: | ||
OS.alert("Your web browser doesn't support VR. Sorry!") | ||
|
||
|
||
# Called when the WebXR session has started successfully | ||
func _on_webxr_session_started() -> void: | ||
print("WebXR: Session started") | ||
|
||
# Hide the canvas and switch the viewport to XR | ||
$EnterWebXR.visible = false | ||
get_viewport().arvr = true | ||
|
||
# Report the XR starting | ||
xr_active = true | ||
emit_signal("xr_started") | ||
|
||
|
||
# Called when the user ends the immersive VR session | ||
func _on_webxr_session_ended() -> void: | ||
print("WebXR: Session ended") | ||
|
||
# Show the | ||
$EnterWebXR.visible = true | ||
get_viewport().arvr = false | ||
|
||
# Report the XR ending | ||
xr_active = false | ||
emit_signal("xr_ended") | ||
|
||
|
||
# Called when the immersive VR session fails to start | ||
func _on_webxr_session_failed(message: String) -> void: | ||
OS.alert("Unable to enter VR: " + message) | ||
$EnterWebXR.visible = true | ||
|
||
|
||
# Handle the Enter VR button on the WebXR browser | ||
func _on_enter_webxr_button_pressed() -> void: | ||
# Configure the WebXR interface | ||
xr_interface.session_mode = 'immersive-vr' | ||
xr_interface.requested_reference_space_types = 'bounded-floor, local-floor, local' | ||
xr_interface.required_features = 'local-floor' | ||
xr_interface.optional_features = 'bounded-floor' | ||
xr_interface.xr_standard_mapping = true | ||
|
||
# Initialize the interface. This should trigger either _on_webxr_session_started | ||
# or _on_webxr_session_failed | ||
if not xr_interface.initialize(): | ||
OS.alert("Failed to initialize WebXR") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
[gd_scene load_steps=2 format=2] | ||
|
||
[ext_resource path="res://addons/godot-xr-tools/xr/start_xr.gd" type="Script" id=1] | ||
|
||
[node name="StartXR" type="Node"] | ||
script = ExtResource( 1 ) | ||
|
||
[node name="EnterWebXR" type="CanvasLayer" parent="."] | ||
visible = false | ||
|
||
[node name="EnterVRButton" type="Button" parent="EnterWebXR"] | ||
anchor_left = 0.5 | ||
anchor_top = 0.5 | ||
anchor_right = 0.5 | ||
anchor_bottom = 0.5 | ||
margin_left = -132.0 | ||
margin_top = -52.5 | ||
margin_right = 132.0 | ||
margin_bottom = 52.5 | ||
text = "Enter VR" | ||
|
||
[connection signal="pressed" from="EnterWebXR/EnterVRButton" to="." method="_on_enter_webxr_button_pressed"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.