I am attaching the CloudXRManager.cs code that comes with your Unity plugin. In the very beginning there is a commented area that talks about how to set it up in unity. Please guide me through it.
/*
* Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved.
*
* NVIDIA CORPORATION, its affiliates and licensors retain all intellectual property
* and proprietary rights in and to this material, related documentation
* and any modifications thereto. Any use, reproduction, disclosure or
* distribution of this material and related documentation without an express
* license agreement from NVIDIA CORPORATION or its affiliates is strictly prohibited.
*/
using System.IO;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
// Included for access to CloudXRCommon.h etc. enums
using CloudXrClientWrapper;
namespace CloudXR {
// Just before the tracked pose driver, if enabled.
[DefaultExecutionOrder(-30001)]
[AddComponentMenu("NVIDIA/CloudXR Manager")]
//[Serializable]
[RequireComponent(typeof(AudioListener))]
public class CloudXRManager : MonoBehaviour {
/*
This class is the primary interface into the CloudXR Client for Unity plugin.
Lifecycle:
0. Add to project
a. Add this script to the main camera (typically under XRRig or similar)
b. Retrieve this script in your own script by:
CloudXRManager cxrManager = Camera.main.GetComponent<CloudXRManager>();
1. Configure
a. config: use LoadConfigFromFile=True to use defaults or load from file, or set up manually:
cxrManager.config = new CxruConfig();
cxrManager.config = GetDefaultConfig();
// Then set up individual settings
2. Register for events
a. implement callback c_OnStatusChanged(object sender, StatusEventArgs e)
b. cxrManager.statusChanged += OnCxrStatusChange;
3. Connect to a server
a. Set server_ip:
cxrManager.server_ip = serverAddress; // IP string like "127.0.0.1"
b. cxrManager.Connect();
note that this will fire a "configuring" event
4. Handle the CloudXRManager.S state changes in c_OnStatusChanged(sender,e):
e.state ==
S.connecting: actively trying to set up a connectiong, no frames streaming, lobby visuals
S.running: streaming has started, transition to streaming visuals
S.disconnecting, S.error, S.dormant: streaming has stopped or failed, or is not requested; lobby visuals
S.error: check e.result for more information
*/
// ============================================================================
// Configuration
// The primary configuration; publicly settable; set up before calling Connect()
private CxruConfig _config;
public CxruConfig config {
get {return _config;}
set {_config=value;}
}
private string _server_ip;
public string server_ip {
get {return _config.serverAddress;}
// CXRUNITY-223
set {_config.serverAddress=value;}
}
public static CxruConfig GetDefaultConfig() {
// Initialize with non-default defaults.
// bool defaults to false; int/uint/float to 0.
CxruConfig newConfig = new CxruConfig();
newConfig.cxrLibraryConfig.cxrRecieverConfig.logMaxSizeKB = -1;
newConfig.cxrLibraryConfig.cxrRecieverConfig.logMaxAgeDays = -1;
// config.cxrLibraryConfig.cxrDebugConfig.enableAImageReaderDecoder = true;
newConfig.cxrLibraryConfig.cxrDebugConfig.outputLinearRGBColor = false;
newConfig.cxrLibraryConfig.cxrDeviceConfig.receiveAudio = true;
newConfig.cxrLibraryConfig.cxrDeviceConfig.sendAudio = false;
newConfig.cxrLibraryConfig.cxrDeviceConfig.maxResFactor = 1f;
newConfig.cxrLibraryConfig.cxrDeviceConfig.foveatedScaleFactor = 50;
newConfig.cxrLibraryConfig.cxrDeviceConfig.maxClientQueueSize = 0f; // Arjun said on call 2022-08-02 that this is correct to invoke the default
newConfig.cxrLibraryConfig.cxrDeviceConfig.angularVelocityInDeviceSpace = true;
newConfig.cxrLibraryConfig.cxrDeviceConfig.maxVideoBitrateKbps = 0; // Use library default
newConfig.cxrLibraryConfig.cxrConnectionConfig.clientNetwork = cxrNetworkInterface.cxrNetworkInterface_WiFi5Ghz;
newConfig.cxrLibraryConfig.cxrConnectionConfig.topology = cxrNetworkTopology.cxrNetworkTopology_LAN;
newConfig.debugLevel = DebugLevel.Info;
newConfig.doLDAT = true;
newConfig.serverAddress = "0.0.0.0";//comments: take from ip field
return newConfig;
}
public bool LoadConfigFromFile = true;
public string ConfigFileName = "cxrUnityConfig.json";
private CxruConfig LoadConfig() {
// This is a reasonable default
CxruConfig newConfig;
string fname = $"{Application.persistentDataPath}/{ConfigFileName}";
if (File.Exists(fname))
{
string jsondata = File.ReadAllText(fname);
newConfig = JsonUtility.FromJson<CxruConfig>(jsondata);
}
else
{
newConfig = GetDefaultConfig();
}
string jsonString = JsonUtility.ToJson(newConfig,true);
string outName = $"{Application.persistentDataPath}/used_{ConfigFileName}";
Log.V("json config");
Log.V(jsonString);
File.WriteAllText(outName, jsonString);
return newConfig;
}
// ============================================================================
// Startup
CxrUnityXRManager m_unityXrManager = null;
void Start() {
m_unityXrManager = new CxrUnityXRManager();
}
// ============================================================================
// Interfaces during streaming
private StereoPosedFrame _latestValidFrame = null;
public StereoPosedFrame latestValidFrame {
get {return _latestValidFrame;}
}
public void SignalLatency(bool state) {
client?.SignalLatency(state);
}
// ============================================================================
// Input bindings
private CxruDeviceBinding[] m_bindings = null;
public void SetBindings(CxruDeviceBinding[] bindings) {
m_bindings = bindings;
}
// ============================================================================
// State machine public interfaces
public enum S {
dormant, configuring, connecting, running, disconnecting, disconnected,
error
}
// Subscribe to this event to get updates whenever the state machine status changes
public event System.EventHandler<StatusChangeArgs> statusChanged;
public class StatusChangeArgs : System.EventArgs
{
public S previous_status, new_status;
public CxruResult result;
}
public bool Disconnect() {
doStatusChange(S.disconnecting,null);
return true;
}
public bool Connect() {
if (m_bindings == null) {
Log.E("Tried to call Connect before setting up bindings.");
return false;
}
if ( (state == S.dormant) || (state == S.disconnected) ) {
doStatusChange(S.configuring,null);
return true;
} else {
doStatusChange(S.error,null);
return false;
}
}
// ============================================================================
// State machine internal code
private S state = S.dormant;
public S currentState {
get {return state;}
}
protected void doStatusChange(S new_status, CxruResult result) {
state = new_status;
if (new_status == S.error) {
client.Disconnect();
Teardown();
}
StatusChangeArgs args = new StatusChangeArgs();
args.previous_status = state;
args.new_status = state;
args.result = result;
System.EventHandler<StatusChangeArgs> handler = statusChanged;
if (handler != null) {
handler(this,args);
}
}
private CxruResult DoState_Configuring() {
if (state != S.configuring) Log.E($"Call to DoState_Configuring in other state: {state}");
if (m_bindings == null) Log.E($"Should not be possible to get to 'configuring' with null bindings");
client = new CloudXRClient(config ,m_unityXrManager, m_bindings);
Log.I("CloudXR Client library version: " + client.Version());
CloudXRConnection cxrConn;
CxruResult result = client.CreateReceiver(out cxrConn);
if (result.succeeded) {
Log.I("Finished creating reciever!");
} else {
if (Time.frameCount%100==0) Log.I($"Not quite done; message {result.message}");
}
return result;
}
private CxruResult DoState_Connecting() {
if (state != S.configuring) Log.E($"Call to DoState_Connecting in other state: {state}");
CxruResult result = client.StartConnect(server_ip);
Log.D($"Attempted to start connection, result: {result.succeeded}, {result.api_cxrErrorString}");
return result;
}
private uint framesLatched = 0;
private CxruResult DoState_Running() {
client.FireControllerEvents();
StereoPosedFrame frame = null;
uint timeoutMs = 5;
CxruResult result = client.LatchFrame(out frame, timeoutMs);
if (result.succeeded) {
framesLatched += 1;
if (framesLatched==1) {
Log.D("First frame latched, texture copied!");
} else if (framesLatched % 1000 == 0) {
Log.D($"Latched {framesLatched} frames");
}
_latestValidFrame = frame;
} else {
Log.I("LatchFrame did not succeed, msg:" + result.message + "\n" + " " + result.api_cxrErrorString);
}
// This condition is a hack meaning "wait until we are so connected that
// frames are consistently flowing"
if (framesLatched % 12 == 6) {
result = client.UpdateControllers();
if (result.succeeded) {
// Log.I("Controller add/remove update");
}
else {
Log.W($"Failed controller update! {result.api_cxrError}, '{result.message}' ");
}
}
return result;
}
private CxruResult DoState_Disconnecting() {
_latestValidFrame = null;
CxruResult result = client.Disconnect();
Teardown();
if (result.succeeded) {
}
else {
Log.W("Disconnect() did not succeed, msg:" + result.message + "\n" + " " + result.api_cxrErrorString);
}
return result;
}
private void Teardown() {
client = null;
quittingThreads = true;
if (poseUpdateThread!=null) {
poseUpdateThread.Join();
}
}
/// <inheritdoc />
protected virtual void Update()
{
if (!enabled)
return;
CxruResult result;
switch(state) {
case S.dormant:
return;
case S.configuring:
result = DoState_Configuring();
if (result.succeeded) {
result = DoState_Connecting();
if (result.succeeded) {
doStatusChange(S.connecting,null);
}
else {
doStatusChange(S.error,result);
Log.E($"Error while initiating connection! {result.message}");
}
}
break;
case S.connecting:
switch (client.m_status) {
case CloudXRStatus.connecting:
return;
case CloudXRStatus.connected:
doStatusChange(S.running,null);
if (poseUpdateThread==null) {
poseUpdateThread = new System.Threading.Thread(new System.Threading.ThreadStart(PoseSendThread));
}
quittingThreads = false;
poseUpdateThread.Start();
Log.I("Connected!");
break;
case CloudXRStatus.error:
case CloudXRStatus.connectionFailed:
doStatusChange(S.error,client.lastCallbackResult);
string reason = client.lastCallbackResult.api_cxrErrorString;
uint code = (uint)client.lastCallbackResult.api_cxrError;
Log.E($"Unhandled error connecting: code {code}, '{reason}'");
break;
case CloudXRStatus.disconnected:
case CloudXRStatus.offline:
result = new CxruResult();
result.message = $"CloudXR client: invalid state {state} while connecting";
doStatusChange(S.error,result);
Log.E(result.message);
break;
}
break;
case S.running:
if (!poseUpdateThread.IsAlive) {
doStatusChange(S.error,new CxruResult{succeeded=false,message="Pose thread quit early"});
Log.E("Pose thread quit early!!");
break;
}
switch (client.m_status) {
case CloudXRStatus.connected:
result = DoState_Running();
break;
case CloudXRStatus.error:
doStatusChange(S.error,client.lastCallbackResult);
string reason = client.lastCallbackResult.api_cxrErrorString;
uint code = (uint)client.lastCallbackResult.api_cxrError;
Log.E($"Unhandled error while streaming: code {code}, '{reason}'");
break;
case CloudXRStatus.disconnected:
case CloudXRStatus.connectionFailed:
case CloudXRStatus.offline:
result = new CxruResult();
result.message = $"CloudXR client: invalid state {client.m_status} while streaming";
doStatusChange(S.error,result);
Log.E(result.message);
break;
}
break;
case S.disconnecting:
switch (client.m_status) {
case CloudXRStatus.connected:
case CloudXRStatus.offline:
case CloudXRStatus.error:
case CloudXRStatus.connectionFailed:
case CloudXRStatus.disconnected:
result = DoState_Disconnecting();
if (result.succeeded)
doStatusChange(S.dormant,result);
else {
doStatusChange(S.error,result);
}
break;
}
break;
case S.disconnected:
break;
}
}
// ============================================================================
// MonoBehaviour life cycle
private CloudXRClient client;
// private AudioClip m_streamingAudioClip;
// private AudioSource m_streamingAudioSource;
/// <inheritdoc />
protected virtual void OnEnable()//onBtnclick()
{
_SetupAudio();
if (config==null) {
config = LoadConfig();
}
config.cxrLibraryConfig.logRootDirectory = Application.persistentDataPath;
config.cxrLibraryConfig.dllRootDirectory = Path.GetFullPath("Packages/com.nvidia.cloudxr.client.unity-plugin/Runtime/Windows/");
Log.level = config.debugLevel;
}
/// <inheritdoc />
protected virtual void OnDisable()
{
}
// ============================================================================
// Audio
private float[] m_audioBuffer;
private int m_n_samplesInBuffer = 0;
private void _SetupAudio() {
int length, count;
AudioSettings.GetDSPBufferSize(out length,out count);
Log.D($"Audio DSP buffer: block length {length}, block count {count}");
int buffer_size =
length*count // samples in buffer
*2 // number of channels
*2 // have a double-size buffer.
;
m_audioBuffer = new float[buffer_size];
}
private System.UInt64 m_filter_callcount = 0;
private long latest = 0;
void OnAudioFilterRead(float[] data, int channels) {
long now = System.DateTime.Now.Ticks;
// Log.W($"Entering filter read: {(int)((now-latest)/10000)} ms since last call");
latest = now;
if (client==null) return;
// Step 1: copy the current buffers.
client.DumpAudioQueue(ref m_audioBuffer, ref m_n_samplesInBuffer);
// Log.W($" ---------a- {m_filter_callcount}: {m_audioBuffer[0]} {m_n_samplesInBuffer} input samples, {channels} channels, {data.Length} output samples!");
if (m_n_samplesInBuffer >= data.Length) {
// Play the oldest audio as early as possible
System.Array.Copy(m_audioBuffer,0,data,0,data.Length);
// Copy the leftover forwards.
int n_leftover = m_n_samplesInBuffer - data.Length;
System.Array.Copy(m_audioBuffer,data.Length,m_audioBuffer,0,n_leftover);
m_n_samplesInBuffer -= data.Length;
}
else {
System.Array.Copy(m_audioBuffer,0,data,0,m_n_samplesInBuffer);
m_n_samplesInBuffer = 0;
}
// Log.W($" ---------b- {m_filter_callcount}: {m_n_samplesInBuffer} input samples, {channels} channels, {data.Length} output samples!");
m_filter_callcount += 1;
// if ( (m_filter_callcount%100) == 0)
// Log.W($"I'm playing audio!! {m_n_samplesInBuffer} input samples, {channels} channels, {data.Length} output samples!");
}
// ============================================================================
// Thread for sending poses
private System.Threading.Thread poseUpdateThread;
private bool quittingThreads = false;
private double pollFrequencyHz = 250.0;
public void PoseSendThread()
{
System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew();
long pollPeriodTicks = (long)(
(1.0/pollFrequencyHz) // Cycles/second > seconds/cycle
* System.Diagnostics.Stopwatch.Frequency // Ticks / second
);
while (!quittingThreads)
{
long nextTick = (stopwatch.ElapsedTicks + pollPeriodTicks);
nextTick -= (nextTick % pollPeriodTicks);
// The work of this thread
// The guards to make sure we don't call this when we shouldn't.
if ( (state==S.running) && (client!=null) && (client.m_status == CloudXRStatus.connected) )
{
// This call has the client "reach down" into the CxrUnityXRManager to get the pose,
// then "reach down" into the client library to immediately send it.
client.SendPoses();
}
long afterWork = stopwatch.ElapsedTicks;
if (afterWork > nextTick)
continue;
// Determine how long until the next tick
long sleepTimeTicks = nextTick - afterWork;
// Convert sleep time from ticks to milliseconds (rounded down)
int sleepTimeMillis = (int)(sleepTimeTicks / (System.Diagnostics.Stopwatch.Frequency / 1e3));
// Sleep until the next tick
System.Threading.Thread.Sleep(sleepTimeMillis);
}
}
// ============================================================================
// Platform detection
public XrPlatform? GetXrPlatform() {
return m_unityXrManager.GetXrPlatform();
}
public uint GetUnityResolutionWidth() {
return m_unityXrManager.getEyeTextureWidth();
}
public void SetUnityResolutionScaling(float scaleFactor) {
m_unityXrManager.SetUnityResolutionScaling(scaleFactor);
}
}
}