diff --git a/.gitignore b/.gitignore index c6bb293..e69de29 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +0,0 @@ -*.meta diff --git a/DisguiseUnityRenderStream/DisguiseCameraCapture.cs b/DisguiseUnityRenderStream/DisguiseCameraCapture.cs deleted file mode 100644 index 8e54aa5..0000000 --- a/DisguiseUnityRenderStream/DisguiseCameraCapture.cs +++ /dev/null @@ -1,2066 +0,0 @@ -#if UNITY_STANDALONE_WIN -#define PLUGIN_AVAILABLE -#endif - -using System; -using System.Net; -using System.Net.Sockets; -using System.IO; -using System.Collections; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Reflection; - -using Microsoft.Win32; - -using Unity.Collections.LowLevel.Unsafe; -using UnityEngine; -using UnityEngine.Rendering; -using UnityEngine.Serialization; -using UnityEngine.SceneManagement; -using System.Threading; -using System.Runtime.Remoting; -using Disguise.RenderStream; -using System.Linq; - -#if UNITY_EDITOR -class DisguiseRenderStream : UnityEditor.Build.IPreprocessBuildWithReport -#else -class DisguiseRenderStream -#endif -{ -#if UNITY_EDITOR - public int callbackOrder { get { return 0; } } - public void OnPreprocessBuild(UnityEditor.Build.Reporting.BuildReport report) - { - DisguiseRenderStreamSettings settings = DisguiseRenderStreamSettings.GetOrCreateSettings(); - schema = new ManagedSchema(); - schema.channels = new string[0]; - switch (settings.sceneControl) - { - case DisguiseRenderStreamSettings.SceneControl.Selection: - Debug.Log("Generating scene-selection schema for: " + SceneManager.sceneCountInBuildSettings + " scenes"); - schema.scenes = new ManagedRemoteParameters[SceneManager.sceneCountInBuildSettings]; - if (SceneManager.sceneCountInBuildSettings == 0) - Debug.LogWarning("No scenes in build settings. Schema will be empty."); - break; - case DisguiseRenderStreamSettings.SceneControl.Manual: - default: - Debug.Log("Generating manual schema"); - schema.scenes = new ManagedRemoteParameters[1]; - break; - } - } - - [UnityEditor.Callbacks.PostProcessSceneAttribute(0)] - static void OnPostProcessScene() - { - if (!UnityEditor.BuildPipeline.isBuildingPlayer) - return; - - Scene activeScene = SceneManager.GetActiveScene(); - DisguiseRenderStreamSettings settings = DisguiseRenderStreamSettings.GetOrCreateSettings(); - switch (settings.sceneControl) - { - case DisguiseRenderStreamSettings.SceneControl.Selection: - { - if (activeScene.buildIndex < 0 || activeScene.buildIndex >= SceneManager.sceneCountInBuildSettings) - { - Debug.Log("Ignoring scene: " + activeScene.name + " (not in build's scene list)"); - return; - } - - Debug.Log("Processing scene: " + activeScene.name + " (" + activeScene.buildIndex + '/' + SceneManager.sceneCountInBuildSettings + ')'); - - HashSet channels = new HashSet(schema.channels); - channels.UnionWith(getTemplateCameras().Select(camera => camera.name)); - schema.channels = channels.ToArray(); - - List parameters = new List(); - foreach (var parameter in UnityEngine.Object.FindObjectsOfType(typeof(DisguiseRemoteParameters)) as DisguiseRemoteParameters[]) - parameters.AddRange(parameter.exposedParameters()); - schema.scenes[activeScene.buildIndex] = new ManagedRemoteParameters(); - ManagedRemoteParameters scene = schema.scenes[activeScene.buildIndex]; - scene.name = activeScene.name; - scene.parameters = parameters.ToArray(); - break; - } - case DisguiseRenderStreamSettings.SceneControl.Manual: - default: - { - Debug.Log("Processing scene: " + activeScene.name); - - HashSet channels = new HashSet(schema.channels); - channels.UnionWith(getTemplateCameras().Select(camera => camera.name)); - schema.channels = channels.ToArray(); - - if (schema.scenes[0] == null) - { - schema.scenes[0] = new ManagedRemoteParameters(); - schema.scenes[0].parameters = new ManagedRemoteParameter[0]; - } - ManagedRemoteParameters scene = schema.scenes[0]; - scene.name = "Default"; - List parameters = new List(scene.parameters); - foreach (var parameter in UnityEngine.Object.FindObjectsOfType(typeof(DisguiseRemoteParameters)) as DisguiseRemoteParameters[]) - parameters.AddRange(parameter.exposedParameters()); - scene.parameters = parameters.ToArray(); - break; - } - } - } - - [UnityEditor.Callbacks.PostProcessBuildAttribute(0)] - static void OnPostProcessBuild(UnityEditor.BuildTarget target, string pathToBuiltProject) - { - if (target != UnityEditor.BuildTarget.StandaloneWindows64) - { - Debug.LogError("DisguiseRenderStream: RenderStream is only available for 64-bit Windows (x86_64)."); - return; - } - - if (PluginEntry.instance.IsAvailable == false) - { - Debug.LogError("DisguiseRenderStream: RenderStream DLL not available, could not save schema"); - return; - } - - RS_ERROR error = PluginEntry.instance.saveSchema(pathToBuiltProject, ref schema); - if (error != RS_ERROR.RS_ERROR_SUCCESS) - { - Debug.LogError(string.Format("DisguiseRenderStream: Failed to save schema {0}", error)); - } - } -#endif - - [RuntimeInitializeOnLoadMethod] - static void OnLoad() - { - if (Application.isEditor) - { - // No play in editor support currently - return; - } - - if (PluginEntry.instance.IsAvailable == false) - { - Debug.LogError("DisguiseRenderStream: RenderStream DLL not available"); - return; - } - - string pathToBuiltProject = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName; - RS_ERROR error = PluginEntry.instance.loadSchema(pathToBuiltProject, ref schema); - if (error == RS_ERROR.RS_ERROR_SUCCESS) - { - sceneFields = new SceneFields[schema.scenes.Length]; - Scene activeScene = SceneManager.GetActiveScene(); - if (activeScene.isLoaded) - OnSceneLoaded(activeScene, LoadSceneMode.Single); - SceneManager.sceneLoaded += OnSceneLoaded; - } - else - { - Debug.LogError(string.Format("DisguiseRenderStream: Failed to load schema {0}", error)); - schema = new ManagedSchema(); - schema.channels = new string[0]; - schema.scenes = new ManagedRemoteParameters[1]; - schema.scenes[0] = new ManagedRemoteParameters(); - schema.scenes[0].name = "Default"; - schema.scenes[0].parameters = new ManagedRemoteParameter[0]; - sceneFields = new SceneFields[schema.scenes.Length]; - CreateStreams(); - } - } - - static void OnSceneLoaded(Scene loadedScene, LoadSceneMode mode) - { - CreateStreams(); - int sceneIndex = 0; - DisguiseRenderStreamSettings settings = DisguiseRenderStreamSettings.GetOrCreateSettings(); - switch (settings.sceneControl) - { - case DisguiseRenderStreamSettings.SceneControl.Selection: - sceneIndex = loadedScene.buildIndex; - break; - } - DisguiseRemoteParameters[] remoteParameters = UnityEngine.Object.FindObjectsOfType(typeof(DisguiseRemoteParameters)) as DisguiseRemoteParameters[]; - ManagedRemoteParameters scene = schema.scenes[sceneIndex]; - sceneFields[sceneIndex] = new SceneFields{ numerical = new List(), images = new List(), texts = new List() }; - SceneFields fields = sceneFields[sceneIndex]; - for (int j = 0; j < scene.parameters.Length;) - { - string key = scene.parameters[j].key; - DisguiseRemoteParameters remoteParams = Array.Find(remoteParameters, rp => key.StartsWith(rp.prefix)); - ObjectField field = new ObjectField(); - field.target = remoteParams.exposedObject; - field.info = null; - if (field.info == null && key.EndsWith("_x")) - { - string baseKey = key.Substring(0, key.Length - 2); - field.info = remoteParams.GetMemberInfoFromPropertyPath(baseKey.Substring(remoteParams.prefix.Length + 1)); - Type fieldType = field.FieldType; - if ((fieldType == typeof(Vector2) || fieldType == typeof(Vector2Int)) && - j + 1 < scene.parameters.Length && scene.parameters[j + 1].key == baseKey + "_y") - { - j += 2; - } - else if ((fieldType == typeof(Vector3) || fieldType == typeof(Vector3Int)) && - j + 2 < scene.parameters.Length && scene.parameters[j + 1].key == baseKey + "_y" && scene.parameters[j + 2].key == baseKey + "_z") - { - j += 3; - } - else if (fieldType == typeof(Vector4) && - j + 3 < scene.parameters.Length && scene.parameters[j + 1].key == baseKey + "_y" && scene.parameters[j + 2].key == baseKey + "_z" && scene.parameters[j + 3].key == baseKey + "_w") - { - j += 4; - } - else - { - field.info = null; - } - } - if (field.info == null && key.EndsWith("_r")) - { - string baseKey = key.Substring(0, key.Length - 2); - field.info = remoteParams.GetMemberInfoFromPropertyPath(baseKey.Substring(remoteParams.prefix.Length + 1)); - Type fieldType = field.FieldType; - if (fieldType == typeof(Color) && - j + 3 < scene.parameters.Length && scene.parameters[j + 1].key == baseKey + "_g" && scene.parameters[j + 2].key == baseKey + "_b" && scene.parameters[j + 3].key == baseKey + "_a") - { - j += 4; - } - else - { - field.info = null; - } - } - if (field.info == null) - { - field.info = remoteParams.GetMemberInfoFromPropertyPath(key.Substring(remoteParams.prefix.Length + 1)); - ++j; - } - if (field.info == null) - { - Debug.LogError("Unhandled remote parameter: " + key); - } - - if (field.FieldType == typeof(Texture)) - fields.images.Add(field); - else if (field.FieldType == typeof(String) || field.FieldType == typeof(String[])) - fields.texts.Add(field); - else - fields.numerical.Add(field); - } - } - - static void CreateStreams() - { - if (PluginEntry.instance.IsAvailable == false) - { - Debug.LogError("DisguiseRenderStream: RenderStream DLL not available"); - return; - } - - do - { - RS_ERROR error = PluginEntry.instance.getStreams(ref streams); - if (error != RS_ERROR.RS_ERROR_SUCCESS) - { - Debug.LogError(string.Format("DisguiseRenderStream: Failed to get streams {0}", error)); - return; - } - - if (streams.Length == 0) - { - Debug.Log("Waiting for streams..."); - Thread.Sleep(1000); - } - } while (streams.Length == 0); - - Debug.Log(string.Format("Found {0} streams", streams.Length)); - foreach (var camera in cameras) - UnityEngine.Object.Destroy(camera); - cameras = new GameObject[streams.Length]; - - // cache the template cameras prior to instantiating our instance cameras - Camera[] templateCameras = getTemplateCameras(); - const int cullUIOnly = ~(1 << 5); - - for (int i = 0; i < streams.Length; ++i) - { - StreamDescription stream = streams[i]; - Camera channelCamera = DisguiseRenderStream.GetChannelCamera(stream.channel); - if (channelCamera) - { - cameras[i] = UnityEngine.Object.Instantiate(channelCamera.gameObject, channelCamera.gameObject.transform.parent); - cameras[i].name = stream.name; - } - else if (Camera.main) - { - cameras[i] = UnityEngine.Object.Instantiate(Camera.main.gameObject, Camera.main.gameObject.transform.parent); - cameras[i].name = stream.name; - } - else - { - cameras[i] = new GameObject(stream.name); - cameras[i].AddComponent(); - } - - GameObject cameraObject = cameras[i]; - Camera camera = cameraObject.GetComponent(); - camera.enabled = true; // ensure the camera component is enable - camera.cullingMask &= cullUIOnly; // cull the UI so RenderStream and other error messages don't render to RenderStream outputs - DisguiseCameraCapture capture = cameraObject.GetComponent(typeof(DisguiseCameraCapture)) as DisguiseCameraCapture; - if (capture == null) - capture = cameraObject.AddComponent(typeof(DisguiseCameraCapture)) as DisguiseCameraCapture; -// Blocks HDRP streams in r18.2 -// #if UNITY_PIPELINE_HDRP -// Volume volume = cameraObject.GetComponent(); -// if (volume == null) -// volume = cameraObject.AddComponent(); -// volume.profile = ScriptableObject.CreateInstance(); -// var captureAfterPostProcess = volume.profile.Add(true); -// captureAfterPostProcess.width.value = (Int32)stream.width; -// captureAfterPostProcess.height.value = (Int32)stream.height; -// #endif - camera.enabled = true; - } - - // stop template cameras impacting performance - foreach (var templateCam in templateCameras) - { - templateCam.enabled = false; // disable the camera component on the template camera so these cameras won't render and impact performance - // we don't want to disable the game object otherwise we won't be able to find the object again to instantiate instance cameras if we get a streams changed event - } - - frameData = new FrameData(); - awaiting = false; - } - - static public IEnumerator AwaitFrame() - { - if (awaiting) - yield break; - awaiting = true; - DisguiseRenderStreamSettings settings = DisguiseRenderStreamSettings.GetOrCreateSettings(); - List scratchTextures = new List(); - while (true) - { - yield return new WaitForEndOfFrame(); - RS_ERROR error = PluginEntry.instance.awaitFrameData(500, ref frameData); - if (error == RS_ERROR.RS_ERROR_QUIT) - Application.Quit(); - if (error == RS_ERROR.RS_ERROR_STREAMS_CHANGED) - CreateStreams(); - switch (settings.sceneControl) - { - case DisguiseRenderStreamSettings.SceneControl.Selection: - if (SceneManager.GetActiveScene().buildIndex != frameData.scene) - { - newFrameData = false; - SceneManager.LoadScene((int)frameData.scene); - yield break; - } - break; - } - newFrameData = (error == RS_ERROR.RS_ERROR_SUCCESS); - if (newFrameData && frameData.scene < schema.scenes.Length) - { - ManagedRemoteParameters spec = schema.scenes[frameData.scene]; - SceneFields fields = sceneFields[frameData.scene]; - int nNumericalParameters = 0; - int nImageParameters = 0; - int nTextParameters = 0; - for (int i = 0; i < spec.parameters.Length; ++i) - { - if (spec.parameters[i].type == RemoteParameterType.RS_PARAMETER_NUMBER) - ++nNumericalParameters; - else if (spec.parameters[i].type == RemoteParameterType.RS_PARAMETER_IMAGE) - ++nImageParameters; - else if (spec.parameters[i].type == RemoteParameterType.RS_PARAMETER_POSE || spec.parameters[i].type == RemoteParameterType.RS_PARAMETER_TRANSFORM) - nNumericalParameters += 16; - else if (spec.parameters[i].type == RemoteParameterType.RS_PARAMETER_TEXT) - ++nTextParameters; - } - float[] parameters = new float[nNumericalParameters]; - ImageFrameData[] imageData = new ImageFrameData[nImageParameters]; - if (PluginEntry.instance.getFrameParameters(spec.hash, ref parameters) == RS_ERROR.RS_ERROR_SUCCESS && PluginEntry.instance.getFrameImageData(spec.hash, ref imageData) == RS_ERROR.RS_ERROR_SUCCESS) - { - if (fields.numerical != null) - { - int i = 0; - foreach (var field in fields.numerical) - { - Type fieldType = field.FieldType; - if (fieldType.IsEnum) - { - field.SetValue(Enum.ToObject(fieldType, Convert.ToUInt64(parameters[i]))); - ++i; - } - else if (fieldType == typeof(Vector2)) - { - Vector2 v = new Vector2(parameters[i + 0], parameters[i + 1]); - field.SetValue(v); - i += 2; - } - else if (fieldType == typeof(Vector2Int)) - { - Vector2Int v = new Vector2Int((int)parameters[i + 0], (int)parameters[i + 1]); - field.SetValue(v); - i += 2; - } - else if (fieldType == typeof(Vector3)) - { - Vector3 v = new Vector3(parameters[i + 0], parameters[i + 1], parameters[i + 2]); - field.SetValue(v); - i += 3; - } - else if (fieldType == typeof(Vector3Int)) - { - Vector3Int v = new Vector3Int((int)parameters[i + 0], (int)parameters[i + 1], (int)parameters[i + 2]); - field.SetValue(v); - i += 3; - } - else if (fieldType == typeof(Vector4)) - { - Vector4 v = new Vector4(parameters[i + 0], parameters[i + 1], parameters[i + 2], parameters[i + 3]); - field.SetValue(v); - i += 4; - } - else if (fieldType == typeof(Color)) - { - Color v = new Color(parameters[i + 0], parameters[i + 1], parameters[i + 2], parameters[i + 3]); - field.SetValue(v); - i += 4; - } - else if (fieldType == typeof(Transform)) - { - Matrix4x4 m = new Matrix4x4(); - m.SetColumn(0, new Vector4(parameters[i + 0], parameters[i + 1], parameters[i + 2], parameters[i + 3])); - m.SetColumn(1, new Vector4(parameters[i + 4], parameters[i + 5], parameters[i + 6], parameters[i + 7])); - m.SetColumn(2, new Vector4(parameters[i + 8], parameters[i + 9], parameters[i + 10], parameters[i + 11])); - m.SetColumn(3, new Vector4(parameters[i + 12], parameters[i + 13], parameters[i + 14], parameters[i + 15])); - Transform transform = field.GetValue() as Transform; - transform.localPosition = new Vector3(m[0, 3], m[1, 3], m[2, 3]); - transform.localScale = m.lossyScale; - transform.localRotation = m.rotation; - i += 16; - } - else - { - if (field.info != null) - field.SetValue(Convert.ChangeType(parameters[i], fieldType)); - ++i; - } - } - } - if (fields.images != null) - { - while (scratchTextures.Count < imageData.Length) - { - int index = scratchTextures.Count; - scratchTextures.Add(new Texture2D((int)imageData[index].width, (int)imageData[index].height, PluginEntry.ToTextureFormat(imageData[index].format), false, true)); - } - uint i = 0; - foreach (var field in fields.images) - { - if (field.GetValue() is RenderTexture renderTexture) - { - Texture2D texture = scratchTextures[(int)i]; - if (texture.width != imageData[i].width || texture.height != imageData[i].height || texture.format != PluginEntry.ToTextureFormat(imageData[i].format)) - { - scratchTextures[(int)i] = new Texture2D((int)imageData[i].width, (int)imageData[i].height, PluginEntry.ToTextureFormat(imageData[i].format), false, true); - texture = scratchTextures[(int)i]; - } - if (PluginEntry.instance.getFrameImage(imageData[i].imageId, ref texture) == RS_ERROR.RS_ERROR_SUCCESS) - { - texture.IncrementUpdateCount(); - Graphics.Blit(texture, renderTexture, new Vector2(1.0f, -1.0f), new Vector2(0.0f, 1.0f)); - renderTexture.IncrementUpdateCount(); - } - } - ++i; - } - } - if (fields.texts != null) - { - uint i = 0; - foreach (var field in fields.texts) - { - string text = ""; - if (PluginEntry.instance.getFrameText(spec.hash, i, ref text) == RS_ERROR.RS_ERROR_SUCCESS) - { - if (field.FieldType == typeof(String[])) - field.SetValue(text.Split(' ')); - else - field.SetValue(text); - } - } - ++i; - } - } - } - } - } - - static Camera[] getTemplateCameras() - { - return Camera.allCameras; - } - - static Camera GetChannelCamera(string channel) - { - try - { - return Array.Find(getTemplateCameras(), camera => camera.name == channel); - } - catch (ArgumentNullException) - { - return Camera.main; - } - } - - static public StreamDescription[] streams = { }; - static public bool awaiting = false; - static public FrameData frameData; - static public bool newFrameData = false; - - static private GameObject[] cameras = { }; - static private ManagedSchema schema = new ManagedSchema(); - public class ObjectField - { - public object target; - public MemberInfo info; - public Type FieldType { - get { - if (info is FieldInfo fieldInfo) - return fieldInfo.FieldType; - else if (info is PropertyInfo propertyInfo) - return propertyInfo.PropertyType; - return typeof(void); - } - } - public void SetValue(object value) - { - if (info is FieldInfo fieldInfo) - fieldInfo.SetValue(target, value); - else if (info is PropertyInfo propertyInfo) - propertyInfo.SetValue(target, value); - } - public object GetValue() - { - if (info is FieldInfo fieldInfo) - return fieldInfo.GetValue(target); - else if (info is PropertyInfo propertyInfo) - return propertyInfo.GetValue(target); - return null; - } - } - public struct SceneFields - { - public List numerical; - public List images; - public List texts; - } - static private SceneFields[] sceneFields = new SceneFields[0]; -} - -[AddComponentMenu("")] -[RequireComponent(typeof(Camera))] -public class DisguiseCameraCapture : MonoBehaviour -{ - // Start is called before the first frame update - public IEnumerator Start() - { - if (PluginEntry.instance.IsAvailable == false) - { - Debug.LogError("DisguiseCameraCapture: RenderStream DLL not available, capture cannot start."); - enabled = false; - yield break; - } - - m_cameraData = new CameraData(); - - m_camera = GetComponent(); - m_frameSender = new Disguise.RenderStream.FrameSender(gameObject.name, m_camera); - RenderPipelineManager.endFrameRendering += RenderPipelineManager_endFrameRendering; - - if (Application.isPlaying == false) - yield break; - - if (!DisguiseRenderStream.awaiting) - yield return StartCoroutine(DisguiseRenderStream.AwaitFrame()); - } - - // Update is called once per frame - public void Update() - { - // set tracking - m_newFrameData = DisguiseRenderStream.newFrameData && m_frameSender != null && m_frameSender.GetCameraData(ref m_cameraData); - float cameraAspect = m_camera.aspect; - Vector2 lensShift = new Vector2(0.0f, 0.0f); - if (m_newFrameData) - { - cameraAspect = m_cameraData.sensorX / m_cameraData.sensorY; - if (m_cameraData.cameraHandle != 0) // If no camera, only set aspect - { - transform.localPosition = new Vector3(m_cameraData.x, m_cameraData.y, m_cameraData.z); - transform.localRotation = Quaternion.Euler(new Vector3(-m_cameraData.rx, m_cameraData.ry, -m_cameraData.rz)); - m_camera.nearClipPlane = m_cameraData.nearZ; - m_camera.farClipPlane = m_cameraData.farZ; - - if (m_cameraData.orthoWidth > 0.0f) // Use an orthographic camera - { - m_camera.orthographic = true; - m_camera.orthographicSize = 0.5f * m_cameraData.orthoWidth / cameraAspect; - transform.localPosition = new Vector3(m_cameraData.x, m_cameraData.y, m_cameraData.z); - transform.localRotation = Quaternion.Euler(new Vector3(-m_cameraData.rx, m_cameraData.ry, -m_cameraData.rz)); - } - else // Perspective projection, use camera lens properties - { - m_camera.usePhysicalProperties = true; - m_camera.sensorSize = new Vector2(m_cameraData.sensorX, m_cameraData.sensorY); - m_camera.focalLength = m_cameraData.focalLength; - lensShift = new Vector2(-m_cameraData.cx, m_cameraData.cy); - } - } - } - else if (m_frameSender != null) - { - // By default aspect is resolution aspect. We need to undo the effect of the subregion on this to get the whole image aspect. - cameraAspect = m_camera.aspect * (m_frameSender.subRegion.height / m_frameSender.subRegion.width); - } - - // Clip to correct subregion and calculate projection matrix - if (m_frameSender != null) - { - Rect subRegion = m_frameSender.subRegion; - - float imageHeight, imageWidth; - if (m_camera.orthographic) - { - imageHeight = 2.0f * m_camera.orthographicSize; - imageWidth = cameraAspect * imageHeight; - } - else - { - float fovV = m_camera.fieldOfView * Mathf.Deg2Rad; - float fovH = Camera.VerticalToHorizontalFieldOfView(m_camera.fieldOfView, cameraAspect) * Mathf.Deg2Rad; - imageWidth = 2.0f * (float)Math.Tan(0.5f * fovH); - imageHeight = 2.0f * (float)Math.Tan(0.5f * fovV); - } - - float l = (-0.5f + subRegion.xMin) * imageWidth; - float r = (-0.5f + subRegion.xMax) * imageWidth; - float t = (-0.5f + 1.0f - subRegion.yMin) * imageHeight; - float b = (-0.5f + 1.0f - subRegion.yMax) * imageHeight; - - Matrix4x4 projectionMatrix; - if (m_camera.orthographic) - projectionMatrix = Matrix4x4.Ortho(l, r, b, t, m_camera.nearClipPlane, m_camera.farClipPlane); - else - projectionMatrix = PerspectiveOffCenter(l * m_camera.nearClipPlane, r * m_camera.nearClipPlane, b * m_camera.nearClipPlane, t * m_camera.nearClipPlane, m_camera.nearClipPlane, m_camera.farClipPlane); - - Matrix4x4 clippingTransform = Matrix4x4.Translate(new Vector3(-lensShift.x / subRegion.width, lensShift.y / subRegion.height, 0.0f)); - m_camera.projectionMatrix = clippingTransform * projectionMatrix; - } - } - - // From http://docs.unity3d.com/ScriptReference/Camera-projectionMatrix.html - static Matrix4x4 PerspectiveOffCenter(float left, float right, float bottom, float top, float near, float far) - { - float x = 2.0F * near / (right - left); - float y = 2.0F * near / (top - bottom); - float a = (right + left) / (right - left); - float b = (top + bottom) / (top - bottom); - float c = -(far + near) / (far - near); - float d = -(2.0F * far * near) / (far - near); - float e = -1.0F; - Matrix4x4 m = new Matrix4x4(); - m[0, 0] = x; - m[0, 1] = 0; - m[0, 2] = a; - m[0, 3] = 0; - m[1, 0] = 0; - m[1, 1] = y; - m[1, 2] = b; - m[1, 3] = 0; - m[2, 0] = 0; - m[2, 1] = 0; - m[2, 2] = c; - m[2, 3] = d; - m[3, 0] = 0; - m[3, 1] = 0; - m[3, 2] = e; - m[3, 3] = 0; - return m; - } - - public void OnRenderImage(RenderTexture source, RenderTexture destination) - { - CheckAndSendFrame(); - } - - private void CheckAndSendFrame() - { - if (m_newFrameData) - { - if (m_frameSender != null) - m_frameSender.SendFrame(DisguiseRenderStream.frameData, m_cameraData); - m_newFrameData = false; - } - } - - private void RenderPipelineManager_endFrameRendering(ScriptableRenderContext context, Camera[] cameras) - { - foreach (var cam in cameras) - { - if (cam == m_camera) - CheckAndSendFrame(); - } - } - - public void OnDestroy() - { - } - - public void OnDisable() - { - if (m_frameSender != null) - { - m_frameSender.DestroyStream(); - } - RenderPipelineManager.endFrameRendering -= RenderPipelineManager_endFrameRendering; - } - - Camera m_camera; - public Disguise.RenderStream.FrameSender m_frameSender; - - CameraData m_cameraData; - bool m_newFrameData = false; -} - -namespace Disguise.RenderStream -{ - // d3renderstream/d3renderstream.h - using StreamHandle = UInt64; - using CameraHandle = UInt64; - delegate void logger_t(string message); - - public enum RSPixelFormat : UInt32 - { - RS_FMT_INVALID, - - RS_FMT_BGRA8, - RS_FMT_BGRX8, - - RS_FMT_RGBA32F, - - RS_FMT_RGBA16, - - RS_FMT_RGBA8, - RS_FMT_RGBX8, - } - - public enum SenderFrameType : UInt32 - { - RS_FRAMETYPE_HOST_MEMORY = 0x00000000, - RS_FRAMETYPE_DX11_TEXTURE, - RS_FRAMETYPE_DX12_TEXTURE, - RS_FRAMETYPE_OPENGL_TEXTURE, - RS_FRAMETYPE_UNKNOWN - } - - public enum UseDX12SharedHeapFlag : UInt32 - { - RS_DX12_USE_SHARED_HEAP_FLAG, - RS_DX12_DO_NOT_USE_SHARED_HEAP_FLAG - } - - public enum RS_ERROR : UInt32 - { - RS_ERROR_SUCCESS = 0, - - // Core is not initialised - RS_NOT_INITIALISED, - - // Core is already initialised - RS_ERROR_ALREADYINITIALISED, - - // Given handle is invalid - RS_ERROR_INVALIDHANDLE, - - // Maximum number of frame senders have been created - RS_MAXSENDERSREACHED, - - RS_ERROR_BADSTREAMTYPE, - - RS_ERROR_NOTFOUND, - - RS_ERROR_INCORRECTSCHEMA, - - RS_ERROR_INVALID_PARAMETERS, - - RS_ERROR_BUFFER_OVERFLOW, - - RS_ERROR_TIMEOUT, - - RS_ERROR_STREAMS_CHANGED, - - RS_ERROR_INCOMPATIBLE_VERSION, - - RS_ERROR_FAILED_TO_GET_DXDEVICE_FROM_RESOURCE, - - RS_ERROR_FAILED_TO_INITIALISE_GPGPU, - - RS_ERROR_QUIT, - - RS_ERROR_UNSPECIFIED - } - - // Bitmask flags - public enum FRAMEDATA_FLAGS : UInt32 - { - FRAMEDATA_NO_FLAGS = 0, - FRAMEDATA_RESET = 1 - } - - public enum REMOTEPARAMETER_FLAGS : UInt32 - { - REMOTEPARAMETER_NO_FLAGS = 0, - REMOTEPARAMETER_NO_SEQUENCE = 1 - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct D3TrackingData - { - public float virtualZoomScale; - public byte virtualReprojectionRequired; - public float xRealCamera, yRealCamera, zRealCamera; - public float rxRealCamera, ryRealCamera, rzRealCamera; - } // Tracking data required by d3 but not used to render content - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct CameraData - { - public StreamHandle streamHandle; - public CameraHandle cameraHandle; - public float x, y, z; - public float rx, ry, rz; - public float focalLength; - public float sensorX, sensorY; - public float cx, cy; - public float nearZ, farZ; - public float orthoWidth; // If > 0, an orthographic camera should be used - public D3TrackingData d3Tracking; - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct FrameData - { - public double tTracked; - public double localTime; - public double localTimeDelta; - public UInt32 frameRateNumerator; - public UInt32 frameRateDenominator; - public UInt32 flags; // FRAMEDATA_FLAGS - public UInt32 scene; - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct CameraResponseData - { - public double tTracked; - public CameraData camera; - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct FrameResponseData - { - public /*CameraResponseData**/ IntPtr cameraData; - public UInt64 schemaHash; - public UInt64 parameterDataSize; - public IntPtr parameterData; - public UInt32 textDataCount; - public /*const char***/ IntPtr textData; - } - - [StructLayout(LayoutKind.Explicit)] - public /*union*/ struct SenderFrameTypeData - { - // struct HostMemoryData - [FieldOffset(0)] - public /*uint8_t**/ IntPtr host_data; - [FieldOffset(8)] - public UInt32 host_stride; - // struct Dx11Data - [FieldOffset(0)] - public /*struct ID3D11Resource**/ IntPtr dx11_resource; - // struct Dx12Data - [FieldOffset(0)] - public /*struct ID3D12Resource**/ IntPtr dx12_resource; - // struct OpenGlData - [FieldOffset(0)] - public /*GLuint**/ UInt32 gl_texture; - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct FrameRegion - { - public UInt32 xOffset; - public UInt32 yOffset; - public UInt32 width; - public UInt32 height; - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct ProjectionClipping - { - public float left; - public float right; - public float top; - public float bottom; - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct StreamDescription - { - public StreamHandle handle; - [MarshalAs(UnmanagedType.LPStr)] - public string channel; - public UInt64 mappingId; - public Int32 iViewpoint; - [MarshalAs(UnmanagedType.LPStr)] - public string name; - public UInt32 width; - public UInt32 height; - public RSPixelFormat format; - public ProjectionClipping clipping; - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct StreamDescriptions - { - public UInt32 nStreams; - public /*StreamDescription**/ IntPtr streams; - } - - public enum RemoteParameterType : UInt32 - { - RS_PARAMETER_NUMBER, - RS_PARAMETER_IMAGE, - RS_PARAMETER_POSE, // 4x4 TR matrix - RS_PARAMETER_TRANSFORM, // 4x4 TRS matrix - RS_PARAMETER_TEXT, - } - - public enum RemoteParameterDmxType : UInt32 - { - RS_DMX_DEFAULT, - RS_DMX_8, - RS_DMX_16_BE, - } - - [StructLayout(LayoutKind.Explicit)] - public /*union*/ struct RemoteParameterTypeDefaults - { - [FieldOffset(0)] - public float numerical_min; - [FieldOffset(4)] - public float numerical_max; - [FieldOffset(8)] - public float numerical_step; - [FieldOffset(12)] - public float numerical_defaultValue; - [FieldOffset(0)] - public /*const char**/ IntPtr text_defaultValue; - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct ImageFrameData - { - public UInt32 width; - public UInt32 height; - public RSPixelFormat format; - public Int64 imageId; - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct RemoteParameter - { - [MarshalAs(UnmanagedType.LPStr)] - public string group; - [MarshalAs(UnmanagedType.LPStr)] - public string displayName; - [MarshalAs(UnmanagedType.LPStr)] - public string key; - public RemoteParameterType type; - public RemoteParameterTypeDefaults defaults; - public UInt32 nOptions; - public /*const char***/ IntPtr options; - - public Int32 dmxOffset; // DMX channel offset or auto (-1) - public RemoteParameterDmxType dmxType; - public UInt32 flags; // REMOTEPARAMETER_FLAGS - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct RemoteParameters - { - [MarshalAs(UnmanagedType.LPStr)] - public string name; - public UInt32 nParameters; - public /*RemoteParameter**/ IntPtr parameters; - public UInt64 hash; - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct Scenes - { - public UInt32 nScenes; - public /*RemoteParameters**/ IntPtr scenes; - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct Channels - { - public UInt32 nChannels; - public /*const char***/ IntPtr channels; - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct Schema - { - [MarshalAs(UnmanagedType.LPStr)] - public string engineName; - [MarshalAs(UnmanagedType.LPStr)] - public string engineVersion; - [MarshalAs(UnmanagedType.LPStr)] - public string info; - public Channels channels; - public Scenes scenes; - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct ProfilingEntry - { - public string name; - public float value; - } - - - public class ManagedRemoteParameter - { - public string group; - public string displayName; - public string key; - public RemoteParameterType type; - public float min; - public float max; - public float step; - public object defaultValue; - public string[] options = { }; - - public Int32 dmxOffset; - public RemoteParameterDmxType dmxType; - } - - public class ManagedRemoteParameters - { - public string name; - public ManagedRemoteParameter[] parameters = { }; - public UInt64 hash; - } - - public class ManagedSchema - { - public string[] channels = { }; - public ManagedRemoteParameters[] scenes = { }; - } - - [Serializable] - public sealed class PluginEntry - { - private class Nested - { - // Explicit static constructor to tell C# compiler - // not to mark type as beforefieldinit - static Nested() { } - - internal static readonly PluginEntry instance = new PluginEntry(); - } - - public static PluginEntry instance { get { return Nested.instance; } } - - public static TextureFormat ToTextureFormat(RSPixelFormat fmt) - { - switch (fmt) - { - case RSPixelFormat.RS_FMT_BGRA8: return TextureFormat.BGRA32; - case RSPixelFormat.RS_FMT_BGRX8: return TextureFormat.BGRA32; - case RSPixelFormat.RS_FMT_RGBA32F: return TextureFormat.RGBAFloat; - case RSPixelFormat.RS_FMT_RGBA16: return TextureFormat.RGBAFloat; - case RSPixelFormat.RS_FMT_RGBA8: return TextureFormat.RGBA32; - case RSPixelFormat.RS_FMT_RGBX8: return TextureFormat.RGBA32; - default: return TextureFormat.BGRA32; - } - } - - public static RenderTextureFormat ToRenderTextureFormat(RSPixelFormat fmt) - { - switch (fmt) - { - case RSPixelFormat.RS_FMT_BGRA8: return RenderTextureFormat.ARGBFloat; - case RSPixelFormat.RS_FMT_BGRX8: return RenderTextureFormat.ARGBFloat; - case RSPixelFormat.RS_FMT_RGBA32F: return RenderTextureFormat.ARGBFloat; - case RSPixelFormat.RS_FMT_RGBA16: return RenderTextureFormat.ARGBFloat; - case RSPixelFormat.RS_FMT_RGBA8: return RenderTextureFormat.ARGBFloat; - case RSPixelFormat.RS_FMT_RGBX8: return RenderTextureFormat.ARGBFloat; - default: return RenderTextureFormat.ARGBFloat; - } - } - - // isolated functions, do not require init prior to use - unsafe delegate void pRegisterLogFunc(logger_t logger); - unsafe delegate void pUnregisterLogFunc(); - - unsafe delegate RS_ERROR pInitialise(int expectedVersionMajor, int expectedVersionMinor); - unsafe delegate RS_ERROR pInitialiseGpGpuWithoutInterop(/*ID3D11Device**/ IntPtr device); - unsafe delegate RS_ERROR pInitialiseGpGpuWithDX11Device(/*ID3D11Device**/ IntPtr device); - unsafe delegate RS_ERROR pInitialiseGpGpuWithDX11Resource(/*ID3D11Resource**/ IntPtr resource); - unsafe delegate RS_ERROR pInitialiseGpGpuWithDX12DeviceAndQueue(/*ID3D12Device**/ IntPtr device, /*ID3D12CommandQueue**/ IntPtr queue); - unsafe delegate RS_ERROR pInitialiseGpGpuWithOpenGlContexts(/*HGLRC**/ IntPtr glContext, /*HDC**/ IntPtr deviceContext); - unsafe delegate RS_ERROR pInitialiseGpGpuWithVulkanDevice(/*VkDevice**/ IntPtr device); - unsafe delegate RS_ERROR pShutdown(); - - // non-isolated functions, these require init prior to use - - unsafe delegate RS_ERROR pUseDX12SharedHeapFlag(ref UseDX12SharedHeapFlag flag); - unsafe delegate RS_ERROR pSaveSchema(string assetPath, /*Schema**/ IntPtr schema); // Save schema for project file/custom executable at (assetPath) - unsafe delegate RS_ERROR pLoadSchema(string assetPath, /*Out*/ /*Schema**/ IntPtr schema, /*InOut*/ ref UInt32 nBytes); // Load schema for project file/custom executable at (assetPath) into a buffer of size (nBytes) starting at (schema) - - // workload functions, these require the process to be running inside d3's asset launcher environment - - unsafe delegate RS_ERROR pSetSchema(/*InOut*/ /*Schema**/ IntPtr schema); // Set schema and fill in per-scene hash for use with rs_getFrameParameters - - unsafe delegate RS_ERROR pGetStreams(/*Out*/ /*StreamDescriptions**/ IntPtr streams, /*InOut*/ ref UInt32 nBytes); // Populate streams into a buffer of size (nBytes) starting at (streams) - - unsafe delegate RS_ERROR pAwaitFrameData(int timeoutMs, /*Out*/ /*FrameData**/ IntPtr data); // waits for any asset, any stream to request a frame, provides the parameters for that frame. - unsafe delegate RS_ERROR pSetFollower(int isFollower); // Used to mark this node as relying on alternative mechanisms to distribute FrameData. Users must provide correct CameraResponseData to sendFrame, and call rs_beginFollowerFrame at the start of the frame, where awaitFrame would normally be called. - unsafe delegate RS_ERROR pBeginFollowerFrame(double tTracked); // Pass the engine-distributed tTracked value in, if you have called rs_setFollower(1) otherwise do not call this function. - - unsafe delegate RS_ERROR pGetFrameParameters(UInt64 schemaHash, /*Out*/ /*void**/ IntPtr outParameterData, UInt64 outParameterDataSize); // returns the remote parameters for this frame. - unsafe delegate RS_ERROR pGetFrameImageData(UInt64 schemaHash, /*Out*/ /*ImageFrameData**/ IntPtr outParameterData, UInt64 outParameterDataCount); // returns the remote image data for this frame. - unsafe delegate RS_ERROR pGetFrameImage(Int64 imageId, SenderFrameType frameType, SenderFrameTypeData data); // fills in (data) with the remote image - unsafe delegate RS_ERROR pGetFrameText(UInt64 schemaHash, UInt32 textParamIndex, /*Out*/ /*const char***/ ref IntPtr outTextPtr); // // returns the remote text data (pointer only valid until next rs_awaitFrameData) - - unsafe delegate RS_ERROR pGetFrameCamera(StreamHandle streamHandle, /*Out*/ /*CameraData**/ IntPtr outCameraData); // returns the CameraData for this stream, or RS_ERROR_NOTFOUND if no camera data is available for this stream on this frame - unsafe delegate RS_ERROR pSendFrame(StreamHandle streamHandle, SenderFrameType frameType, SenderFrameTypeData data, /*const CameraResponseData**/ IntPtr sendData); // publish a frame buffer which was generated from the associated tracking and timing information. - - unsafe delegate RS_ERROR pReleaseImage(SenderFrameType frameType, SenderFrameTypeData data); - - unsafe delegate RS_ERROR pLogToD3(string str); - unsafe delegate RS_ERROR pSendProfilingData(/*ProfilingEntry**/ IntPtr entries, int count); - unsafe delegate RS_ERROR pSetNewStatusMessage(string msg); - - pRegisterLogFunc m_registerLoggingFunc = null; - pRegisterLogFunc m_registerErrorLoggingFunc = null; - pRegisterLogFunc m_registerVerboseLoggingFunc = null; - - pUnregisterLogFunc m_unregisterLoggingFunc = null; - pUnregisterLogFunc m_unregisterErrorLoggingFunc = null; - pUnregisterLogFunc m_unregisterVerboseLoggingFunc = null; - - pInitialise m_initialise = null; - pInitialiseGpGpuWithoutInterop m_initialiseGpGpuWithoutInterop = null; - pInitialiseGpGpuWithDX11Device m_initialiseGpGpuWithDX11Device = null; - pInitialiseGpGpuWithDX11Resource m_initialiseGpGpuWithDX11Resource = null; - pInitialiseGpGpuWithDX12DeviceAndQueue m_initialiseGpGpuWithDX12DeviceAndQueue = null; - pInitialiseGpGpuWithOpenGlContexts m_initialiseGpGpuWithOpenGlContexts = null; - pInitialiseGpGpuWithVulkanDevice m_initialiseGpGpuWithVulkanDevice = null; - - pShutdown m_shutdown = null; - - pUseDX12SharedHeapFlag m_useDX12SharedHeapFlag = null; - pSaveSchema m_saveSchema = null; - pLoadSchema m_loadSchema = null; - - pSetSchema m_setSchema = null; - pGetStreams m_getStreams = null; - - pAwaitFrameData m_awaitFrameData = null; - pSetFollower m_setFollower = null; - pBeginFollowerFrame m_beginFollowerFrame = null; - - pGetFrameParameters m_getFrameParameters = null; - pGetFrameImageData m_getFrameImageData = null; - pGetFrameImage m_getFrameImage = null; - pGetFrameText m_getFrameText = null; - - pGetFrameCamera m_getFrameCamera = null; - pSendFrame m_sendFrame = null; - - pReleaseImage m_releaseImage = null; - - pLogToD3 m_logToD3 = null; - pSendProfilingData m_sendProfilingData = null; - pSetNewStatusMessage m_setNewStatusMessage = null; - - logger_t m_logInfo; - logger_t m_logError; - - void logInfo(string message) - { - Debug.Log(message); - } - - void logError(string message) - { - Debug.LogError(message); - } - - void logToD3(string logString, string stackTrace, LogType type) - { - if (m_logToD3 == null) - return; - - string prefix = ""; - switch(type) - { - case LogType.Error: - prefix = "!!!!! "; - break; - case LogType.Assert: - prefix = "!!!!! ASSERT: "; - break; - case LogType.Warning: - prefix = "!!! "; - break; - case LogType.Exception: - prefix = "!!!!! Exception: "; - break; - } - - string trace = String.IsNullOrEmpty(stackTrace) ? "" : "\nTrace: " + stackTrace; - - m_logToD3(prefix + logString + trace); - } - - void setNewStatusMessage(string message) - { - m_setNewStatusMessage?.Invoke(message); - } - - ManagedSchema schemaToManagedSchema(Schema cSchema) - { - ManagedSchema schema = new ManagedSchema(); - schema.channels = new string[cSchema.channels.nChannels]; - for (int i = 0; i < cSchema.channels.nChannels; ++i) - { - IntPtr channelPtr = Marshal.ReadIntPtr(cSchema.channels.channels, i * Marshal.SizeOf(typeof(IntPtr))); - schema.channels[i] = Marshal.PtrToStringAnsi(channelPtr); - } - schema.scenes = new ManagedRemoteParameters[cSchema.scenes.nScenes]; - for (int i = 0; i < cSchema.scenes.nScenes; ++i) - { - schema.scenes[i] = new ManagedRemoteParameters(); - ManagedRemoteParameters managedParameters = schema.scenes[i]; - RemoteParameters parameters = (RemoteParameters)Marshal.PtrToStructure(cSchema.scenes.scenes + i * Marshal.SizeOf(typeof(RemoteParameters)), typeof(RemoteParameters)); - managedParameters.name = parameters.name; - managedParameters.parameters = new ManagedRemoteParameter[parameters.nParameters]; - for (int j = 0; j < parameters.nParameters; ++j) - { - managedParameters.parameters[j] = new ManagedRemoteParameter(); - ManagedRemoteParameter managedParameter = managedParameters.parameters[j]; - RemoteParameter parameter = (RemoteParameter)Marshal.PtrToStructure(parameters.parameters + j * Marshal.SizeOf(typeof(RemoteParameter)), typeof(RemoteParameter)); - managedParameter.group = parameter.group; - managedParameter.displayName = parameter.displayName; - managedParameter.key = parameter.key; - managedParameter.type = parameter.type; - if (parameter.type == RemoteParameterType.RS_PARAMETER_NUMBER) - { - managedParameter.min = parameter.defaults.numerical_min; - managedParameter.max = parameter.defaults.numerical_max; - managedParameter.step = parameter.defaults.numerical_step; - managedParameter.defaultValue = parameter.defaults.numerical_defaultValue; - } - else if (parameter.type == RemoteParameterType.RS_PARAMETER_TEXT) - { - managedParameter.defaultValue = Marshal.PtrToStringAnsi(parameter.defaults.text_defaultValue); - } - managedParameter.options = new string[parameter.nOptions]; - for (int k = 0; k < parameter.nOptions; ++k) - { - IntPtr optionPtr = Marshal.ReadIntPtr(parameter.options, k * Marshal.SizeOf(typeof(IntPtr))); - managedParameter.options[i] = Marshal.PtrToStringAnsi(optionPtr); - } - managedParameter.dmxOffset = parameter.dmxOffset; - managedParameter.dmxType = parameter.dmxType; - } - managedParameters.hash = parameters.hash; - } - return schema; - } - - public RS_ERROR saveSchema(string assetPath, ref ManagedSchema schema) - { - if (m_saveSchema == null) - return RS_ERROR.RS_NOT_INITIALISED; - - List allocations = new List(); - try - { - Schema cSchema = new Schema(); - cSchema.engineName = "Unity Engine"; - cSchema.engineVersion = Application.unityVersion; - cSchema.info = Application.productName; - cSchema.channels.nChannels = (UInt32)schema.channels.Length; - cSchema.channels.channels = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)) * (int)cSchema.channels.nChannels); - allocations.Add(cSchema.channels.channels); - for (int i = 0; i < cSchema.channels.nChannels; ++i) - { - IntPtr channelPtr = Marshal.StringToHGlobalAnsi(schema.channels[i]); - allocations.Add(channelPtr); - Marshal.WriteIntPtr(cSchema.channels.channels, i * Marshal.SizeOf(typeof(IntPtr)), channelPtr); - } - - cSchema.scenes.nScenes = (UInt32)schema.scenes.Length; - cSchema.scenes.scenes = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(RemoteParameters)) * (int)cSchema.scenes.nScenes); - allocations.Add(cSchema.scenes.scenes); - for (int i = 0; i < cSchema.scenes.nScenes; ++i) - { - ManagedRemoteParameters managedParameters = schema.scenes[i]; - RemoteParameters parameters = new RemoteParameters(); - parameters.name = managedParameters.name; - parameters.nParameters = (UInt32)managedParameters.parameters.Length; - parameters.parameters = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(RemoteParameter)) * (int)parameters.nParameters); - allocations.Add(parameters.parameters); - for (int j = 0; j < parameters.nParameters; ++j) - { - ManagedRemoteParameter managedParameter = managedParameters.parameters[j]; - RemoteParameter parameter = new RemoteParameter(); - parameter.group = managedParameter.group; - parameter.displayName = managedParameter.displayName; - parameter.key = managedParameter.key; - parameter.type = managedParameter.type; - if (parameter.type == RemoteParameterType.RS_PARAMETER_NUMBER) - { - parameter.defaults.numerical_min = managedParameter.min; - parameter.defaults.numerical_max = managedParameter.max; - parameter.defaults.numerical_step = managedParameter.step; - parameter.defaults.numerical_defaultValue = Convert.ToSingle(managedParameter.defaultValue); - } - else if (parameter.type == RemoteParameterType.RS_PARAMETER_TEXT) - { - IntPtr textPtr = Marshal.StringToHGlobalAnsi(Convert.ToString(managedParameter.defaultValue)); - allocations.Add(textPtr); - parameter.defaults.text_defaultValue = textPtr; - } - parameter.nOptions = (UInt32)managedParameter.options.Length; - parameter.options = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)) * (int)parameter.nOptions); - allocations.Add(parameter.options); - for (int k = 0; k < parameter.nOptions; ++k) - { - IntPtr optionPtr = Marshal.StringToHGlobalAnsi(managedParameter.options[k]); - allocations.Add(optionPtr); - Marshal.WriteIntPtr(parameter.options, k * Marshal.SizeOf(typeof(IntPtr)), optionPtr); - } - parameter.dmxOffset = managedParameter.dmxOffset; - parameter.dmxType = managedParameter.dmxType; - Marshal.StructureToPtr(parameter, parameters.parameters + j * Marshal.SizeOf(typeof(RemoteParameter)), false); - } - Marshal.StructureToPtr(parameters, cSchema.scenes.scenes + i * Marshal.SizeOf(typeof(RemoteParameters)), false); - } - - IntPtr schemaPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Schema))); - allocations.Add(schemaPtr); - Marshal.StructureToPtr(cSchema, schemaPtr, false); - RS_ERROR error = m_saveSchema(assetPath, schemaPtr); - if (error == RS_ERROR.RS_ERROR_SUCCESS) - { - cSchema = (Schema)Marshal.PtrToStructure(schemaPtr, typeof(Schema)); - schema = schemaToManagedSchema(cSchema); - } - return error; - } - finally - { - foreach (IntPtr ptr in allocations) - Marshal.FreeHGlobal(ptr); - } - //return RS_ERROR.RS_ERROR_UNSPECIFIED; - } - - public RS_ERROR loadSchema(string assetPath, ref ManagedSchema schema) - { - if (m_loadSchema == null) - return RS_ERROR.RS_NOT_INITIALISED; - - IntPtr descMem = IntPtr.Zero; - UInt32 nBytes = 0; - m_loadSchema(assetPath, descMem, ref nBytes); - - const int MAX_TRIES = 3; - int iterations = 0; - - RS_ERROR res = RS_ERROR.RS_ERROR_BUFFER_OVERFLOW; - try - { - do - { - Marshal.FreeHGlobal(descMem); - descMem = Marshal.AllocHGlobal((int)nBytes); - res = m_loadSchema(assetPath, descMem, ref nBytes); - if (res == RS_ERROR.RS_ERROR_SUCCESS) - { - Schema cSchema = (Schema)Marshal.PtrToStructure(descMem, typeof(Schema)); - schema = schemaToManagedSchema(cSchema); - } - - ++iterations; - } while (res == RS_ERROR.RS_ERROR_BUFFER_OVERFLOW && iterations < MAX_TRIES); - } - finally - { - Marshal.FreeHGlobal(descMem); - } - return res; - } - - public RS_ERROR getStreams(ref StreamDescription[] streams) - { - if (m_getStreams == null) - return RS_ERROR.RS_NOT_INITIALISED; - - IntPtr descMem = IntPtr.Zero; - UInt32 nBytes = 0; - m_getStreams(descMem, ref nBytes); - - const int MAX_TRIES = 3; - int iterations = 0; - - RS_ERROR res = RS_ERROR.RS_ERROR_BUFFER_OVERFLOW; - try - { - do - { - Marshal.FreeHGlobal(descMem); - descMem = Marshal.AllocHGlobal((int)nBytes); - res = m_getStreams(descMem, ref nBytes); - if (res == RS_ERROR.RS_ERROR_SUCCESS) - { - StreamDescriptions desc = (StreamDescriptions)Marshal.PtrToStructure(descMem, typeof(StreamDescriptions)); - streams = new StreamDescription[desc.nStreams]; - for (int i = 0; i < desc.nStreams; ++i) - { - IntPtr current = desc.streams + i * Marshal.SizeOf(typeof(StreamDescription)); - streams[i] = (StreamDescription)Marshal.PtrToStructure(current, typeof(StreamDescription)); - } - } - - ++iterations; - } while (res == RS_ERROR.RS_ERROR_BUFFER_OVERFLOW && iterations < MAX_TRIES); - } - finally - { - Marshal.FreeHGlobal(descMem); - } - return res; - } - - public RS_ERROR sendFrame(StreamHandle streamHandle, SenderFrameType frameType, SenderFrameTypeData data, FrameResponseData sendData) - { - if (m_sendFrame == null) - return RS_ERROR.RS_NOT_INITIALISED; - - if (handleReference.IsAllocated) - handleReference.Free(); - handleReference = GCHandle.Alloc(sendData, GCHandleType.Pinned); - - try - { - RS_ERROR error = m_sendFrame(streamHandle, frameType, data, handleReference.AddrOfPinnedObject()); - return error; - } - finally - { - if (handleReference.IsAllocated) - handleReference.Free(); - } - //return RS_ERROR.RS_ERROR_UNSPECIFIED; - } - - public RS_ERROR awaitFrameData(int timeoutMs, ref FrameData data) - { - if (m_awaitFrameData == null) - return RS_ERROR.RS_NOT_INITIALISED; - - if (handleReference.IsAllocated) - handleReference.Free(); - handleReference = GCHandle.Alloc(data, GCHandleType.Pinned); - try - { - RS_ERROR error = m_awaitFrameData(timeoutMs, handleReference.AddrOfPinnedObject()); - if (error == RS_ERROR.RS_ERROR_SUCCESS) - { - data = (FrameData)Marshal.PtrToStructure(handleReference.AddrOfPinnedObject(), typeof(FrameData)); - } - return error; - } - finally - { - if (handleReference.IsAllocated) - handleReference.Free(); - } - //return RS_ERROR.RS_ERROR_UNSPECIFIED; - } - - public RS_ERROR setFollower(int isFollower) - { - if (m_setFollower == null) - return RS_ERROR.RS_NOT_INITIALISED; - - try - { - RS_ERROR error = m_setFollower(isFollower); - return error; - } - finally - { - } - } - - public RS_ERROR beginFollowerFrame(double tTracked) - { - if (m_beginFollowerFrame == null) - return RS_ERROR.RS_NOT_INITIALISED; - - try - { - RS_ERROR error = beginFollowerFrame(tTracked); - return error; - } - finally - { - } - } - - public RS_ERROR getFrameParameters(UInt64 schemaHash, ref float[] outParameterData) - { - if (m_getFrameParameters == null) - return RS_ERROR.RS_NOT_INITIALISED; - - if (handleReference.IsAllocated) - handleReference.Free(); - handleReference = GCHandle.Alloc(outParameterData, GCHandleType.Pinned); - try - { - RS_ERROR error = m_getFrameParameters(schemaHash, handleReference.AddrOfPinnedObject(), (UInt64)outParameterData.Length * sizeof(float)); - if (error == RS_ERROR.RS_ERROR_SUCCESS) - { - Marshal.Copy(handleReference.AddrOfPinnedObject(), outParameterData, 0, outParameterData.Length); - } - return error; - } - finally - { - if (handleReference.IsAllocated) - handleReference.Free(); - } - //return RS_ERROR.RS_ERROR_UNSPECIFIED; - } - - public RS_ERROR getFrameImageData(UInt64 schemaHash, ref ImageFrameData[] outParameterData) - { - if (m_getFrameImageData == null) - return RS_ERROR.RS_NOT_INITIALISED; - - if (handleReference.IsAllocated) - handleReference.Free(); - handleReference = GCHandle.Alloc(outParameterData, GCHandleType.Pinned); - try - { - var size = Marshal.SizeOf(typeof(ImageFrameData)); - RS_ERROR error = m_getFrameImageData(schemaHash, handleReference.AddrOfPinnedObject(), (UInt64)outParameterData.Length); - if (error == RS_ERROR.RS_ERROR_SUCCESS) - { - for (int i = 0; i < outParameterData.Length; ++i) - { - IntPtr ptr = new IntPtr(handleReference.AddrOfPinnedObject().ToInt64() + i * size); - outParameterData[i] = Marshal.PtrToStructure(ptr); - } - } - return error; - } - finally - { - if (handleReference.IsAllocated) - handleReference.Free(); - } - //return RS_ERROR.RS_ERROR_UNSPECIFIED; - } - - public RS_ERROR getFrameImage(Int64 imageId, ref Texture2D texture) - { - if (m_getFrameImage == null) - return RS_ERROR.RS_NOT_INITIALISED; - - try - { - SenderFrameTypeData data = new SenderFrameTypeData(); - data.dx11_resource = texture.GetNativeTexturePtr(); - RS_ERROR error = m_getFrameImage(imageId, SenderFrameType.RS_FRAMETYPE_DX11_TEXTURE, data); - return error; - } - finally - { - } - //return RS_ERROR.RS_ERROR_UNSPECIFIED; - } - - public RS_ERROR getFrameText(UInt64 schemaHash, UInt32 textParamIndex, ref string text) - { - if (m_getFrameText == null) - return RS_ERROR.RS_NOT_INITIALISED; - - try - { - IntPtr textPtr = IntPtr.Zero; - RS_ERROR error = m_getFrameText(schemaHash, textParamIndex, ref textPtr); - if (error == RS_ERROR.RS_ERROR_SUCCESS) - text = Marshal.PtrToStringAnsi(textPtr); - return error; - } - finally - { - } - //return RS_ERROR.RS_ERROR_UNSPECIFIED; - } - - public RS_ERROR getFrameCamera(StreamHandle streamHandle, ref CameraData outCameraData) - { - if (m_getFrameCamera == null) - return RS_ERROR.RS_NOT_INITIALISED; - - if (handleReference.IsAllocated) - handleReference.Free(); - handleReference = GCHandle.Alloc(outCameraData, GCHandleType.Pinned); - try - { - RS_ERROR error = m_getFrameCamera(streamHandle, handleReference.AddrOfPinnedObject()); - if (error == RS_ERROR.RS_ERROR_SUCCESS) - { - outCameraData = (CameraData)Marshal.PtrToStructure(handleReference.AddrOfPinnedObject(), typeof(CameraData)); - } - return error; - } - finally - { - if (handleReference.IsAllocated) - handleReference.Free(); - } - //return RS_ERROR.RS_ERROR_UNSPECIFIED; - } - -#if PLUGIN_AVAILABLE - - [DllImport("kernel32.dll")] - static extern IntPtr LoadLibraryEx(string lpLibFileName, IntPtr fileHandle, int flags); - - [DllImport("kernel32.dll")] - static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); - - [DllImport("kernel32.dll", CharSet = CharSet.Ansi)] - static extern bool FreeLibrary(IntPtr hModule); - - private void free() - { -#if UNITY_EDITOR - UnityEditor.EditorApplication.quitting -= free; - UnityEditor.AssemblyReloadEvents.beforeAssemblyReload -= free; -#else - Application.quitting -= free; -#endif - - if (functionsLoaded) - { - if (m_logToD3 != null) - Application.logMessageReceivedThreaded -= logToD3; - - if (m_unregisterErrorLoggingFunc != null) - m_unregisterErrorLoggingFunc(); - if (m_unregisterLoggingFunc != null) - m_unregisterLoggingFunc(); - - RS_ERROR error = m_shutdown(); - if (error != RS_ERROR.RS_ERROR_SUCCESS) - Debug.LogError(string.Format("Failed to shutdown: {0}", error)); - functionsLoaded = false; - Debug.Log("Shut down RenderStream"); - } - - if (d3RenderStreamDLL != IntPtr.Zero) - { - FreeLibrary(d3RenderStreamDLL); - d3RenderStreamDLL = IntPtr.Zero; - Debug.Log("Unloaded RenderStream"); - } - - if (handleReference.IsAllocated) - handleReference.Free(); - } - - public bool IsAvailable - { - get - { - UnityEngine.Rendering.GraphicsDeviceType gapi = UnityEngine.SystemInfo.graphicsDeviceType; - return functionsLoaded && (gapi == UnityEngine.Rendering.GraphicsDeviceType.Direct3D11); - } - } -#else - private void free() {} - public bool IsAvailable { get { return false; } } -#endif - - const int LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010; - const int LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100; - const int LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200; - const int LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400; - const int LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800; - - const string _dllName = "d3renderstream"; - - const int RENDER_STREAM_VERSION_MAJOR = 1; - const int RENDER_STREAM_VERSION_MINOR = 30; - - bool functionsLoaded = false; - IntPtr d3RenderStreamDLL = IntPtr.Zero; - GCHandle handleReference; // Everything is run under coroutines with odd lifetimes, so store a reference to GCHandle - - string name; - - // https://answers.unity.com/questions/16804/retrieving-project-name.html?childToView=478633#answer-478633 - public string GetProjectName() - { - string[] s = Application.dataPath.Split('/'); - if (s.Length >= 2) - { - string projectName = s[s.Length - 2]; - return projectName; - } - return "UNKNOWN UNITY PROJECT"; - } - - private bool LoadFn(ref T fn, string fnName) where T : Delegate - { - fn = DelegateBuilder(d3RenderStreamDLL, fnName); - if (fn == null) - { - Debug.LogError(string.Format("Failed load function \"{0}\" from {1}.dll", fnName, _dllName)); - return false; - } - return true; - } - - private PluginEntry() - { -#if PLUGIN_AVAILABLE - RegistryKey d3Key = Registry.CurrentUser.OpenSubKey("Software"); - if (d3Key != null) - { - d3Key = d3Key.OpenSubKey("d3 Technologies"); - if (d3Key != null) - { - d3Key = d3Key.OpenSubKey("d3 Production Suite"); - } - } - - if (d3Key == null) - { - Debug.LogError(string.Format("Failed to find path to {0}.dll. d3 Not installed?", _dllName)); - return; - } - - string d3ExePath = d3Key.GetValue("exe path").ToString(); - d3ExePath = d3ExePath.Replace(@"\\", @"\"); - int endSeparator = d3ExePath.LastIndexOf(Path.DirectorySeparatorChar); - if (endSeparator != d3ExePath.Length - 1) - d3ExePath = d3ExePath.Substring(0, endSeparator + 1); - - string libPath = d3ExePath + _dllName + ".dll"; - d3RenderStreamDLL = LoadWin32Library(libPath); - if (d3RenderStreamDLL == IntPtr.Zero) - { - Debug.LogError(string.Format("Failed to load {0}.dll from {1}", _dllName, d3ExePath)); - return; - } - - functionsLoaded = true; - - functionsLoaded &= LoadFn(ref m_registerLoggingFunc, "rs_registerLoggingFunc"); - functionsLoaded &= LoadFn(ref m_registerErrorLoggingFunc, "rs_registerErrorLoggingFunc"); - functionsLoaded &= LoadFn(ref m_registerVerboseLoggingFunc, "rs_registerVerboseLoggingFunc"); - - functionsLoaded &= LoadFn(ref m_unregisterLoggingFunc, "rs_unregisterLoggingFunc"); - functionsLoaded &= LoadFn(ref m_unregisterErrorLoggingFunc, "rs_unregisterErrorLoggingFunc"); - functionsLoaded &= LoadFn(ref m_unregisterVerboseLoggingFunc, "rs_unregisterVerboseLoggingFunc"); - - functionsLoaded &= LoadFn(ref m_initialise, "rs_initialise"); - functionsLoaded &= LoadFn(ref m_initialiseGpGpuWithoutInterop, "rs_initialiseGpGpuWithoutInterop"); - functionsLoaded &= LoadFn(ref m_initialiseGpGpuWithDX11Device, "rs_initialiseGpGpuWithDX11Device"); - functionsLoaded &= LoadFn(ref m_initialiseGpGpuWithDX11Resource, "rs_initialiseGpGpuWithDX11Resource"); - functionsLoaded &= LoadFn(ref m_initialiseGpGpuWithDX12DeviceAndQueue, "rs_initialiseGpGpuWithDX12DeviceAndQueue"); - functionsLoaded &= LoadFn(ref m_initialiseGpGpuWithOpenGlContexts, "rs_initialiseGpGpuWithOpenGlContexts"); - functionsLoaded &= LoadFn(ref m_initialiseGpGpuWithVulkanDevice, "rs_initialiseGpGpuWithVulkanDevice"); - functionsLoaded &= LoadFn(ref m_shutdown, "rs_shutdown"); - - functionsLoaded &= LoadFn(ref m_useDX12SharedHeapFlag, "rs_useDX12SharedHeapFlag"); - functionsLoaded &= LoadFn(ref m_saveSchema, "rs_saveSchema"); - functionsLoaded &= LoadFn(ref m_loadSchema, "rs_loadSchema"); - - functionsLoaded &= LoadFn(ref m_setSchema, "rs_setSchema"); - - functionsLoaded &= LoadFn(ref m_getStreams, "rs_getStreams"); - - functionsLoaded &= LoadFn(ref m_awaitFrameData, "rs_awaitFrameData"); - functionsLoaded &= LoadFn(ref m_setFollower, "rs_setFollower"); - functionsLoaded &= LoadFn(ref m_beginFollowerFrame, "rs_beginFollowerFrame"); - - functionsLoaded &= LoadFn(ref m_getFrameParameters, "rs_getFrameParameters"); - functionsLoaded &= LoadFn(ref m_getFrameImageData, "rs_getFrameImageData"); - functionsLoaded &= LoadFn(ref m_getFrameImage, "rs_getFrameImage"); - functionsLoaded &= LoadFn(ref m_getFrameText, "rs_getFrameText"); - - functionsLoaded &= LoadFn(ref m_getFrameCamera, "rs_getFrameCamera"); - functionsLoaded &= LoadFn(ref m_sendFrame, "rs_sendFrame"); - - functionsLoaded &= LoadFn(ref m_releaseImage, "rs_releaseImage"); - - functionsLoaded &= LoadFn(ref m_logToD3, "rs_logToD3"); - functionsLoaded &= LoadFn(ref m_sendProfilingData, "rs_sendProfilingData"); - functionsLoaded &= LoadFn(ref m_setNewStatusMessage, "rs_setNewStatusMessage"); - - if (!functionsLoaded) - { - Debug.LogError(string.Format("One or more functions failed load from {0}.dll", _dllName)); - return; - } - - // There is an issue with these logging callbacks sometimes throwing inside of the dll which can cause all kinds of problems - // exception consistentency is questionable, often the same exception can be seen at the same point in time - // however periodically a minor difference may occur where the exception is not thrown where expected or even at all - - m_logInfo = logInfo; - m_logError = logError; - - if (m_registerLoggingFunc != null) - m_registerLoggingFunc(m_logInfo); - if (m_registerErrorLoggingFunc != null) - m_registerErrorLoggingFunc(m_logError); - - if (m_logToD3 != null) - Application.logMessageReceivedThreaded += logToD3; - - RS_ERROR error = m_initialise(RENDER_STREAM_VERSION_MAJOR, RENDER_STREAM_VERSION_MINOR); - if (error == RS_ERROR.RS_ERROR_INCOMPATIBLE_VERSION) - Debug.LogError(string.Format("Unsupported RenderStream library, expected version {0}.{1}", RENDER_STREAM_VERSION_MAJOR, RENDER_STREAM_VERSION_MINOR)); - else if (error != RS_ERROR.RS_ERROR_SUCCESS) - Debug.LogError(string.Format("Failed to initialise: {0}", error)); - else - { - Texture2D texture = new Texture2D(1, 1); - error = m_initialiseGpGpuWithDX11Resource(texture.GetNativeTexturePtr()); - if (error != RS_ERROR.RS_ERROR_SUCCESS) - Debug.LogError(string.Format("Failed to initialise GPU interop: {0}", error)); - } - - Debug.Log("Loaded RenderStream"); - -#if UNITY_EDITOR - UnityEditor.EditorApplication.quitting += free; - UnityEditor.AssemblyReloadEvents.beforeAssemblyReload += free; -#else - Application.quitting += free; -#endif - - name = GetProjectName(); -#else - Debug.LogError(string.Format("{0}.dll is only available on Windows", _dllName)); -#endif - } - - ~PluginEntry() - { - free(); - } - - static IntPtr LoadWin32Library(string dllFilePath) - { - System.IntPtr moduleHandle = IntPtr.Zero ; -#if PLUGIN_AVAILABLE - moduleHandle = LoadLibraryEx(dllFilePath, IntPtr.Zero, LOAD_IGNORE_CODE_AUTHZ_LEVEL | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_SEARCH_USER_DIRS); - if (moduleHandle == IntPtr.Zero) - { - // I'm gettin last dll error - int errorCode = Marshal.GetLastWin32Error(); - Debug.LogError(string.Format("There was an error during dll loading : {0}, error - {1}", dllFilePath, errorCode)); - } -#endif - return moduleHandle; - } - - static T DelegateBuilder(IntPtr loadedDLL, string functionName) where T : Delegate - { - IntPtr pAddressOfFunctionToCall = IntPtr.Zero; -#if PLUGIN_AVAILABLE - pAddressOfFunctionToCall = GetProcAddress(loadedDLL, functionName); - if (pAddressOfFunctionToCall == IntPtr.Zero) - { - return null; - } -#endif - T functionDelegate = Marshal.GetDelegateForFunctionPointer(pAddressOfFunctionToCall, typeof(T)) as T; - return functionDelegate; - } - } - - public class FrameSender - { - struct Frame - { - public FrameRegion region; - public RSPixelFormat fmt; - public AsyncGPUReadbackRequest readback; - public CameraResponseData responseData; - } - - private FrameSender() { } - public FrameSender(string name, Camera cam) - { - m_name = name; - Cam = cam; - - Debug.Log(string.Format("Creating stream {0}", m_name)); - StreamDescription stream = Array.Find(DisguiseRenderStream.streams, s => s.name == name); - Debug.Log(string.Format(" Channel {0} at {1}x{2}@{3}", stream.channel, stream.width, stream.height, stream.format)); - - m_lastFrameCount = -1; - m_streamHandle = stream.handle; - m_width = (int)stream.width; - m_height = (int)stream.height; - - m_frameRegion = new Rect(stream.clipping.left, stream.clipping.top, stream.clipping.right - stream.clipping.left, stream.clipping.bottom - stream.clipping.top); - - RenderTextureDescriptor desc = new RenderTextureDescriptor(m_width, m_height, PluginEntry.ToRenderTextureFormat(stream.format), 24); - m_sourceTex = new RenderTexture(desc) - { - name = m_name + " Texture" - }; - Cam.targetTexture = m_sourceTex; - m_convertedTex = new Texture2D(m_sourceTex.width, m_sourceTex.height, PluginEntry.ToTextureFormat(stream.format), false, false); - - Debug.Log(string.Format("Created stream {0} with handle {1}", m_name, m_streamHandle)); - } - - public bool GetCameraData(ref CameraData cameraData) - { - return PluginEntry.instance.getFrameCamera(m_streamHandle, ref cameraData) == RS_ERROR.RS_ERROR_SUCCESS; - } - - public void SendFrame(Texture2D frame) - { - unsafe - { - SenderFrameTypeData data = new SenderFrameTypeData(); - data.dx11_resource = frame.GetNativeTexturePtr(); - RS_ERROR error = PluginEntry.instance.sendFrame(m_streamHandle, SenderFrameType.RS_FRAMETYPE_DX11_TEXTURE, data, m_responseData); - if (error != RS_ERROR.RS_ERROR_SUCCESS) - Debug.LogError(string.Format("Error sending frame: {0}", error)); - } - } - - public void SendFrame(FrameData frameData, CameraData cameraData) - { - if (m_lastFrameCount == Time.frameCount) - return; - - m_lastFrameCount = Time.frameCount; - - if (m_convertedTex.width != m_sourceTex.width || m_convertedTex.height != m_sourceTex.height) - m_convertedTex.Resize(m_sourceTex.width, m_sourceTex.height, m_convertedTex.format, false); - - m_cameraResponseData = new CameraResponseData { tTracked = frameData.tTracked, camera = cameraData }; - - if (cameraHandleReference.IsAllocated) - cameraHandleReference.Free(); - cameraHandleReference = GCHandle.Alloc(m_cameraResponseData, GCHandleType.Pinned); - - m_responseData = new FrameResponseData{ cameraData = cameraHandleReference.AddrOfPinnedObject() }; - -// Blocks HDRP streams in r18.2 -// #if UNITY_PIPELINE_HDRP -// Volume volume = Cam.GetComponent(); -// if (!volume.profile) -// Debug.Log("Missing profile"); - -// if (!volume.profile.TryGet(out m_captureAfterPostProcess)) -// { -// Debug.Log("Missing captureAfterPostProcess"); -// m_captureAfterPostProcess = volume.profile.Add(true); -// } -// m_captureAfterPostProcess.width.value = (Int32)m_width; -// m_captureAfterPostProcess.height.value = (Int32)m_height; -// #else - RenderTexture unflipped = RenderTexture.GetTemporary(m_sourceTex.width, m_sourceTex.height, 0, m_sourceTex.format); - Graphics.Blit(m_sourceTex, unflipped, new Vector2(1.0f, -1.0f), new Vector2(0.0f, 1.0f)); - Graphics.ConvertTexture(unflipped, m_convertedTex); - RenderTexture.ReleaseTemporary(unflipped); - - try - { - SendFrame(m_convertedTex); - } - finally - { - if (cameraHandleReference.IsAllocated) - cameraHandleReference.Free(); - } - -// #endif - } - - public void DestroyStream() - { - m_streamHandle = 0; - } - - public Camera Cam { get; set; } - - private RenderTexture m_sourceTex; - private FrameResponseData m_responseData; - private CameraResponseData m_cameraResponseData; - private GCHandle cameraHandleReference; - - string m_name; - Texture2D m_convertedTex; - int m_lastFrameCount; - - StreamHandle m_streamHandle; - int m_width; - int m_height; - Rect m_frameRegion; - public Rect subRegion - { - get - { - return m_frameRegion; - } - } -// Blocks HDRP streams in r18.2 -// #if UNITY_PIPELINE_HDRP -// private DisguiseCameraCaptureAfterPostProcess m_captureAfterPostProcess; -// #endif - } - -} diff --git a/DisguiseUnityRenderStream/DisguiseRenderStreamSettings.cs b/DisguiseUnityRenderStream/DisguiseRenderStreamSettings.cs deleted file mode 100644 index 55212ab..0000000 --- a/DisguiseUnityRenderStream/DisguiseRenderStreamSettings.cs +++ /dev/null @@ -1,43 +0,0 @@ -using UnityEngine; - -using System; -using System.IO; - -class DisguiseRenderStreamSettings : ScriptableObject -{ - public enum SceneControl - { - Manual, - Selection - } - - [SerializeField] - public SceneControl sceneControl; - -#if UNITY_EDITOR - [UnityEditor.Callbacks.DidReloadScripts] - private static void OnReloadScripts() - { - // Ensure resource is created - DisguiseRenderStreamSettings.GetOrCreateSettings(); - } -#endif - - public static DisguiseRenderStreamSettings GetOrCreateSettings() - { - var settings = Resources.Load("DisguiseRenderStreamSettings"); - if (settings == null) - { - Debug.Log("Using default DisguiseRenderStreamSettings"); - settings = ScriptableObject.CreateInstance(); - settings.sceneControl = SceneControl.Manual; -#if UNITY_EDITOR - if (!Directory.Exists("Assets/Resources")) - Directory.CreateDirectory("Assets/Resources"); - UnityEditor.AssetDatabase.CreateAsset(settings, "Assets/Resources/DisguiseRenderStreamSettings.asset"); - UnityEditor.AssetDatabase.SaveAssets(); -#endif - } - return settings; - } -} diff --git a/DisguiseUnityRenderStream/DisguiseTimeControl.cs b/DisguiseUnityRenderStream/DisguiseTimeControl.cs deleted file mode 100644 index 3b6127c..0000000 --- a/DisguiseUnityRenderStream/DisguiseTimeControl.cs +++ /dev/null @@ -1,43 +0,0 @@ -using UnityEngine; -using UnityEngine.Playables; - -using System; - -[AddComponentMenu("Disguise RenderStream/Time Control")] -[RequireComponent(typeof(PlayableDirector))] -public class DisguiseTimeControl : MonoBehaviour -{ - void Start() - { - playableDirector = GetComponent(); - playableDirector.timeUpdateMode = DirectorUpdateMode.Manual; - } - - void Update() - { - if (DisguiseRenderStream.newFrameData) - { - if (DisguiseRenderStream.frameData.localTime < playableDirector.initialTime || DisguiseRenderStream.frameData.localTimeDelta <= 0) - playableDirector.Pause(); - else - playableDirector.Resume(); - - playableDirector.time = Math.Max(0, DisguiseRenderStream.frameData.localTime - playableDirector.initialTime); - - switch (playableDirector.extrapolationMode) - { - case DirectorWrapMode.Hold: - if (playableDirector.time > playableDirector.duration) - playableDirector.time = playableDirector.duration; - break; - case DirectorWrapMode.Loop: - playableDirector.time = (playableDirector.time % playableDirector.duration); - break; - } - - playableDirector.Evaluate(); - } - } - - private PlayableDirector playableDirector; -} diff --git a/DisguiseUnityRenderStream/Editor/HDRP/DisguisePostProcessEditor.cs b/DisguiseUnityRenderStream/Editor/HDRP/DisguisePostProcessEditor.cs deleted file mode 100644 index 17c4598..0000000 --- a/DisguiseUnityRenderStream/Editor/HDRP/DisguisePostProcessEditor.cs +++ /dev/null @@ -1,38 +0,0 @@ -#if UNITY_PIPELINE_HDRP -using System.Collections; -using System.Collections.Generic; -using System.Reflection.Emit; -using UnityEditor.Rendering; -using UnityEngine; -using UnityEngine.Rendering.HighDefinition; -using UnityEditor; -using UnityEditor.PackageManager; - -[VolumeComponentEditor(typeof(DisguiseCameraCaptureAfterPostProcess))] -public sealed class DisguiseCameraCaptureAfterPostProcessEditor : VolumeComponentEditor -{ - static class Labels - { - internal static readonly GUIContent Width = new GUIContent("Width"); - internal static readonly GUIContent Height = new GUIContent("Height"); - } - - SerializedDataParameter _width; - SerializedDataParameter _height; - - public override void OnEnable() - { - var o = new PropertyFetcher(serializedObject); - - _width = Unpack(o.Find(x => x.width)); - _height = Unpack(o.Find(x => x.height)); - } - - public override void OnInspectorGUI() - { - EditorGUILayout.LabelField("RenderStream", EditorStyles.miniLabel); - PropertyField(_width, Labels.Width); - PropertyField(_height, Labels.Height); - } -} -#endif // UNITY_PIPELINE_HDRP \ No newline at end of file diff --git a/DisguiseUnityRenderStream/HDRP/DisguiseCameraCaptureVolume.cs b/DisguiseUnityRenderStream/HDRP/DisguiseCameraCaptureVolume.cs deleted file mode 100644 index f9703bd..0000000 --- a/DisguiseUnityRenderStream/HDRP/DisguiseCameraCaptureVolume.cs +++ /dev/null @@ -1,94 +0,0 @@ -#if UNITY_STANDALONE_WIN -#define PLUGIN_AVAILABLE -#endif - -#if UNITY_PIPELINE_HDRP -using System; -using System.Threading; -using UnityEngine; -using UnityEngine.Rendering; -using UnityEngine.Rendering.HighDefinition; - -[Serializable, VolumeComponentMenu("Post-processing/Custom/DisguiseCameraCapturePostProcess")] -public sealed class DisguiseCameraCaptureAfterPostProcess : CustomPostProcessVolumeComponent, IPostProcessComponent -{ - public IntParameter width = new IntParameter(1920); - public IntParameter height = new IntParameter(1080); - public RenderTexture colourRenderTexture; - public Texture2D colourTex2D; - - public bool IsActive() => m_colourMaterial != null; - public override CustomPostProcessInjectionPoint injectionPoint => CustomPostProcessInjectionPoint.AfterPostProcess; - - public override void Setup() - { - CreateMaterial(); - CreateTexture(); - } - - public override void Render(CommandBuffer cmd, HDCamera camera, RTHandle source, RTHandle destination) - { - if (m_colourMaterial == null) - return; - m_colourMaterial.SetTexture("_InputTexture", source); - cmd.Blit(source.rt, colourRenderTexture, m_colourMaterial); - FetchTarget(colourRenderTexture, colourTex2D); - - Camera[] cams = Camera.allCameras; - foreach(Camera cam in cams) - { - if (cam.name != camera.camera.name) - continue; - DisguiseCameraCapture dcc = cam.GetComponent(); - if (dcc == null) - continue; - if (dcc.m_frameSender == null) - continue; - dcc.m_frameSender.SendFrame(colourTex2D); - } - - //SaveAsRawImage(colourTex2D, "dump.raw"); - } - - public override void Cleanup() - { - CoreUtils.Destroy(m_colourMaterial); - } - - private void CreateMaterial() - { - if (Shader.Find(kColourShaderName) != null) - m_colourMaterial = new Material(Shader.Find(kColourShaderName)); - else - Debug.LogError($"Unable to find Disguise RenderStream shader '{kColourShaderName}'"); - } - - private void CreateTexture() - { - colourRenderTexture = new RenderTexture(width.value, height.value, 24, RenderTextureFormat.ARGBFloat); - colourTex2D = new Texture2D(width.value, height.value, TextureFormat.RGBAFloat, false); - } - - // Debug code... - private void FetchTarget(RenderTexture renderTexture, Texture2D texture2D) - { - RenderTexture activeRenderTexture = RenderTexture.active; - RenderTexture unflipped = RenderTexture.GetTemporary(renderTexture.width, renderTexture.height, 0, renderTexture.format); - RenderTexture.active = unflipped; - Graphics.Blit(renderTexture, unflipped, new Vector2(1.0f, -1.0f), new Vector2(0.0f, 1.0f)); - Graphics.CopyTexture(unflipped, texture2D); - RenderTexture.active = activeRenderTexture; - RenderTexture.ReleaseTemporary(unflipped); - } - - private void SaveAsRawImage(Texture2D texture2D, string filename) - { - byte[] imageData = texture2D.GetRawTextureData(); - var thread = new Thread(() => System.IO.File.WriteAllBytes(filename, imageData)); - thread.Start(); - } - - private Material m_colourMaterial; - private const string kColourShaderName = "Hidden/Shader/DisguiseColourFramePostProcess"; -} -#endif // UNITY_PIPELINE_HDRP diff --git a/DisguiseUnityRenderStream/HDRP/Resources/DisguiseColourFramePostProcess.shader b/DisguiseUnityRenderStream/HDRP/Resources/DisguiseColourFramePostProcess.shader deleted file mode 100644 index a3cf5df..0000000 --- a/DisguiseUnityRenderStream/HDRP/Resources/DisguiseColourFramePostProcess.shader +++ /dev/null @@ -1,72 +0,0 @@ -Shader "Hidden/Shader/DisguiseColourFramePostProcess" -{ - HLSLINCLUDE - - #pragma target 4.5 - #pragma only_renderers d3d11 ps4 xboxone vulkan metal switch - - #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" - #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" - #include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl" - #include "Packages/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/FXAA.hlsl" - #include "Packages/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/RTUpscale.hlsl" - - struct Attributes - { - uint vertexID : SV_VertexID; - UNITY_VERTEX_INPUT_INSTANCE_ID - }; - - struct Varyings - { - float4 positionCS : SV_POSITION; - float2 texcoord : TEXCOORD0; - UNITY_VERTEX_OUTPUT_STEREO - }; - - Varyings Vert(Attributes input) - { - Varyings output; - UNITY_SETUP_INSTANCE_ID(input); - UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); - output.positionCS = GetFullScreenTriangleVertexPosition(input.vertexID); - output.texcoord = GetFullScreenTriangleTexCoord(input.vertexID); - return output; - } - - // List of properties to control your post process effect - float _Intensity; - TEXTURE2D_X(_InputTexture); - - float4 CustomPostProcess(Varyings input) : SV_Target - { - UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); - - uint2 positionSS = input.texcoord * _ScreenSize.xy; - float3 outColor = LOAD_TEXTURE2D_X(_InputTexture, positionSS).xyz; - outColor = max(1.055f * pow(outColor, 0.416666667f) - 0.055f, 0.0f); - - return float4(outColor, 1); - } - - ENDHLSL - - SubShader - { - Pass - { - Name "DisguiseColourFramePostProcess" - - ZWrite Off - ZTest Always - Blend Off - Cull Off - - HLSLPROGRAM - #pragma fragment CustomPostProcess - #pragma vertex Vert - ENDHLSL - } - } - Fallback Off -} diff --git a/Docs~/images/cluster-settings.png b/Docs~/images/cluster-settings.png new file mode 100644 index 0000000..5d512f4 Binary files /dev/null and b/Docs~/images/cluster-settings.png differ diff --git a/Docs~/images/custom-arguments.png b/Docs~/images/custom-arguments.png new file mode 100644 index 0000000..9060a96 Binary files /dev/null and b/Docs~/images/custom-arguments.png differ diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..e7845b1 --- /dev/null +++ b/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3af259fe85c185c488143434da882f8c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DisguiseUnityRenderStream/Editor/Disguise.RenderStream.Editor.asmdef b/Editor/Disguise.RenderStream.Editor.asmdef similarity index 74% rename from DisguiseUnityRenderStream/Editor/Disguise.RenderStream.Editor.asmdef rename to Editor/Disguise.RenderStream.Editor.asmdef index ad2d001..3fcb02c 100644 --- a/DisguiseUnityRenderStream/Editor/Disguise.RenderStream.Editor.asmdef +++ b/Editor/Disguise.RenderStream.Editor.asmdef @@ -2,10 +2,9 @@ "name": "Disguise.RenderStream.Editor", "references": [ "Disguise.RenderStream", + "Disguise.RenderStream.PipelineAbstraction", "Unity.RenderPipelines.Core.Runtime", - "Unity.RenderPipelines.Core.Editor", - "Unity.RenderPipelines.HighDefinition.Runtime", - "Unity.RenderPipelines.HighDefinition.Editor" + "Unity.RenderPipelines.Core.Editor" ], "includePlatforms": [ "Editor" diff --git a/Editor/Disguise.RenderStream.Editor.asmdef.meta b/Editor/Disguise.RenderStream.Editor.asmdef.meta new file mode 100644 index 0000000..5746497 --- /dev/null +++ b/Editor/Disguise.RenderStream.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: bbdac430fc066e643a5fd71db147a104 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DisguiseUnityRenderStream/Editor/DisguiseRemoteParametersEditor.cs b/Editor/DisguiseRemoteParametersEditor.cs similarity index 100% rename from DisguiseUnityRenderStream/Editor/DisguiseRemoteParametersEditor.cs rename to Editor/DisguiseRemoteParametersEditor.cs diff --git a/Editor/DisguiseRemoteParametersEditor.cs.meta b/Editor/DisguiseRemoteParametersEditor.cs.meta new file mode 100644 index 0000000..d6aeb6d --- /dev/null +++ b/Editor/DisguiseRemoteParametersEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c59c443a0fe5454daec4308e8dc32a9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/DisguiseRenderStreamBuild.cs b/Editor/DisguiseRenderStreamBuild.cs new file mode 100644 index 0000000..0c3aef4 --- /dev/null +++ b/Editor/DisguiseRenderStreamBuild.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEditor.SceneManagement; +using UnityEditor.UnityLinker; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.SceneManagement; +using Object = UnityEngine.Object; + +namespace Disguise.RenderStream +{ + /// + /// Build callback order: + /// 1. IPreprocessBuildWithReport + /// 2. IUnityLinkerProcessor (only when managed code stripping is enabled, for ) + /// 3. IPostprocessBuildWithReport + /// + class DisguiseRenderStreamBuild : + IPreprocessBuildWithReport, + IUnityLinkerProcessor, + IPostprocessBuildWithReport + { + public int callbackOrder { get; set; } = 0; + + bool m_NeededVSyncFix; + bool m_HasGeneratedSchema; + ManagedSchema m_Schema; + int m_NumScenesInBuild; + + void IPreprocessBuildWithReport.OnPreprocessBuild(BuildReport report) + { + m_NeededVSyncFix = DisguiseFramerateManager.VSyncIsEnabled; + m_HasGeneratedSchema = false; + + if (m_NeededVSyncFix) + { + QualitySettings.vSyncCount = 0; + } + + AddAlwaysIncludedShader(BlitExtended.ShaderName); + AddAlwaysIncludedShader(DepthCopy.ShaderName); + + var target = report.summary.platform; + + if (target != BuildTarget.StandaloneWindows64) + { + throw new BuildFailedException("DisguiseRenderStream: RenderStream is only available for 64-bit Windows (x86_64)."); + } + + if (PluginEntry.instance.IsAvailable == false) + { + throw new BuildFailedException("DisguiseRenderStream: RenderStream DLL not available, could not save schema"); + } + } + + string IUnityLinkerProcessor.GenerateAdditionalLinkXmlFile(BuildReport report, UnityLinkerBuildPipelineData data) + { + var preserver = new ReflectedMemberPreserver(); + + GenerateSchema(report, scene => + { + var remoteParameters = Object.FindObjectsByType(FindObjectsSortMode.InstanceID); + + foreach (var remoteParameter in remoteParameters) + { + foreach (var exposedParameter in remoteParameter.exposedParameters()) + { + var memberInfo = remoteParameter.GetMemberInfoFromManagedParameter(exposedParameter); + preserver.Preserve(memberInfo); + } + } + + ProcessSceneForSchema(scene); + }); + + m_HasGeneratedSchema = true; + + return preserver.GenerateAdditionalLinkXmlFile(); + } + + void IPostprocessBuildWithReport.OnPostprocessBuild(BuildReport report) + { + if (m_NeededVSyncFix) + { + Debug.LogWarning($"DisguiseRenderStream: {nameof(QualitySettings)}.{nameof(QualitySettings.vSyncCount)} has been disabled for best output performance with Disguise"); + } + + if (m_HasGeneratedSchema) + return; + + GenerateSchema(report, ProcessSceneForSchema); + } + + void GenerateSchema(BuildReport report, Action processScene) + { + var settings = DisguiseRenderStreamSettings.GetOrCreateSettings(); + m_Schema = new ManagedSchema + { + channels = Array.Empty() + }; + + var allScenesInBuild = EditorBuildSettings.scenes; + m_NumScenesInBuild = allScenesInBuild.Length; + + switch (settings.sceneControl) + { + case DisguiseRenderStreamSettings.SceneControl.Selection: + Debug.Log("Generating scene-selection schema for: " + allScenesInBuild.Length + " scenes"); + m_Schema.scenes = new ManagedRemoteParameters[allScenesInBuild.Length]; + if (allScenesInBuild.Length == 0) + Debug.LogWarning("No scenes in build settings. Schema will be empty."); + break; + case DisguiseRenderStreamSettings.SceneControl.Manual: + default: + Debug.Log("Generating manual schema"); + m_Schema.scenes = new ManagedRemoteParameters[1]; + break; + } + + foreach (var buildScene in allScenesInBuild) + { + if (!buildScene.enabled) + { + continue; + } + + var scene = SceneManager.GetSceneByPath(buildScene.path); + if (!scene.IsValid() || !scene.isLoaded) + { + scene = EditorSceneManager.OpenScene(buildScene.path, OpenSceneMode.Single); + } + + if (!scene.IsValid() || !scene.isLoaded) + { + continue; + } + + processScene.Invoke(scene); + } + + if (settings.enableUnityDebugWindowPresenter) + { + AddPresenterToSchema(m_Schema); + } + + var pathToBuiltProject = report.summary.outputPath; + RS_ERROR error = PluginEntry.instance.saveSchema(pathToBuiltProject, ref m_Schema); + if (error != RS_ERROR.RS_ERROR_SUCCESS) + { + throw new BuildFailedException(string.Format("DisguiseRenderStream: Failed to save schema {0}", error)); + } + } + + void ProcessSceneForSchema(Scene scene) + { + DisguiseRenderStreamSettings settings = DisguiseRenderStreamSettings.GetOrCreateSettings(); + + // In "Manual" mode (Disguise does not control scene changes), all parameters are listed under a single "Default" scene. + // In "Selection" mode (Disguise controls scene changes), parameters are listed under their respective scenes. + var (sceneIndex, managedName, indexMessage) = settings.sceneControl switch { + DisguiseRenderStreamSettings.SceneControl.Manual => (0, "Default", string.Empty), + DisguiseRenderStreamSettings.SceneControl.Selection => (scene.buildIndex, scene.name, $"({scene.buildIndex}/{m_NumScenesInBuild})"), + _ => throw new ArgumentOutOfRangeException() + }; + + Debug.Log($"Processing scene {scene.name} {indexMessage}"); + AddSceneToSchema(m_Schema, sceneIndex, managedName); + } + + static void AddSceneToSchema(ManagedSchema schema, int sceneIndex, string name) + { + var channels = new HashSet(schema.channels); + channels.UnionWith(Camera.allCameras.Select(camera => camera.name)); + schema.channels = channels.ToArray(); + schema.scenes[sceneIndex] ??= new ManagedRemoteParameters + { + name = name, + parameters = Array.Empty() + }; + var currentScene = schema.scenes[sceneIndex]; + + var parameters = currentScene.parameters + .Concat(Object.FindObjectsByType(FindObjectsSortMode.InstanceID) + .SelectMany(p => p.exposedParameters())); + + currentScene.parameters = parameters.ToArray(); + } + + static void AddPresenterToSchema(ManagedSchema schema) + { + foreach (var scene in schema.scenes) + { + var presenterParameters = UnityDebugWindowPresenter.GetManagedRemoteParameters(schema, scene); + scene.parameters = presenterParameters.Concat(scene.parameters).ToArray(); + } + } + + /// + /// Ensure the proper runtime availability of the shader name. + /// + /// + /// Based on logic exposed here: + /// https://forum.unity.com/threads/modify-always-included-shaders-with-pre-processor.509479/ + /// + /// The name of the shader to validate. + static void AddAlwaysIncludedShader(string shaderName) + { + var shader = Shader.Find(shaderName); + if (shader == null) + return; + + var graphicsSettingsObj = AssetDatabase.LoadAssetAtPath("ProjectSettings/GraphicsSettings.asset"); + var serializedObject = new SerializedObject(graphicsSettingsObj); + var arrayProp = serializedObject.FindProperty("m_AlwaysIncludedShaders"); + var hasShader = false; + for (int i = 0; i < arrayProp.arraySize; ++i) + { + var arrayElem = arrayProp.GetArrayElementAtIndex(i); + if (shader == arrayElem.objectReferenceValue) + { + hasShader = true; + break; + } + } + + if (!hasShader) + { + var arrayIndex = arrayProp.arraySize; + arrayProp.InsertArrayElementAtIndex(arrayIndex); + var arrayElem = arrayProp.GetArrayElementAtIndex(arrayIndex); + arrayElem.objectReferenceValue = shader; + + serializedObject.ApplyModifiedProperties(); + + AssetDatabase.SaveAssets(); + } + } + } +} \ No newline at end of file diff --git a/Editor/DisguiseRenderStreamBuild.cs.meta b/Editor/DisguiseRenderStreamBuild.cs.meta new file mode 100644 index 0000000..6b2009b --- /dev/null +++ b/Editor/DisguiseRenderStreamBuild.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 239e604b2b10ab444a430dab340c2d9a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/DisguiseRenderStreamSettingsEditor.cs b/Editor/DisguiseRenderStreamSettingsEditor.cs new file mode 100644 index 0000000..ac8faae --- /dev/null +++ b/Editor/DisguiseRenderStreamSettingsEditor.cs @@ -0,0 +1,35 @@ +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine; +using UnityEngine.UIElements; + +namespace Disguise.RenderStream +{ + [CustomEditor(typeof(DisguiseRenderStreamSettings))] + class DisguiseRenderStreamSettingsEditor : Editor + { + public static class Style + { + public const string DisguiseSettingsContainer = "disguise-settings-container"; + public const string DisguiseSettingsTitle = "disguise-settings-title"; + } + + [SerializeField] + VisualTreeAsset m_Layout; + + [SerializeField] + StyleSheet m_Style; + + public override VisualElement CreateInspectorGUI() + { + var root = new VisualElement(); + + m_Layout.CloneTree(root); + root.styleSheets.Add(m_Style); + + root.Bind(serializedObject); + + return root; + } + } +} diff --git a/Editor/DisguiseRenderStreamSettingsEditor.cs.meta b/Editor/DisguiseRenderStreamSettingsEditor.cs.meta new file mode 100644 index 0000000..79167ef --- /dev/null +++ b/Editor/DisguiseRenderStreamSettingsEditor.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: d85289020b1f03e4981e88ece03dbacf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: + - m_Layout: {fileID: 9197481963319205126, guid: 507d9ffa39ec44b419ba9ab3bbcd1cab, type: 3} + - m_Style: {fileID: 7433441132597879392, guid: fa44cd66f158c0447a66c89089cc0e82, type: 3} + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/DisguiseRenderStreamSettingsEditor.uss b/Editor/DisguiseRenderStreamSettingsEditor.uss new file mode 100644 index 0000000..e42dbf7 --- /dev/null +++ b/Editor/DisguiseRenderStreamSettingsEditor.uss @@ -0,0 +1,12 @@ +.disguise-settings-container +{ + margin-left: 9px; + margin-top: 1px; +} + +.disguise-settings-title +{ + font-size: 19px; + -unity-font-style: bold; + margin-bottom: 12px; +} diff --git a/Editor/DisguiseRenderStreamSettingsEditor.uss.meta b/Editor/DisguiseRenderStreamSettingsEditor.uss.meta new file mode 100644 index 0000000..a367707 --- /dev/null +++ b/Editor/DisguiseRenderStreamSettingsEditor.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fa44cd66f158c0447a66c89089cc0e82 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/Editor/DisguiseRenderStreamSettingsEditor.uxml b/Editor/DisguiseRenderStreamSettingsEditor.uxml new file mode 100644 index 0000000..c7bbd37 --- /dev/null +++ b/Editor/DisguiseRenderStreamSettingsEditor.uxml @@ -0,0 +1,4 @@ + + + + diff --git a/Editor/DisguiseRenderStreamSettingsEditor.uxml.meta b/Editor/DisguiseRenderStreamSettingsEditor.uxml.meta new file mode 100644 index 0000000..8425b33 --- /dev/null +++ b/Editor/DisguiseRenderStreamSettingsEditor.uxml.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 507d9ffa39ec44b419ba9ab3bbcd1cab +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0} diff --git a/Editor/DisguiseRenderStreamSettingsProvider.cs b/Editor/DisguiseRenderStreamSettingsProvider.cs new file mode 100644 index 0000000..ca755da --- /dev/null +++ b/Editor/DisguiseRenderStreamSettingsProvider.cs @@ -0,0 +1,37 @@ +using UnityEditor; +using UnityEngine.UIElements; + +namespace Disguise.RenderStream +{ + static class DisguiseRenderStreamSettingsProvider + { + static readonly string k_SettingsPath = "Project/DisguiseRenderStream"; + + class Contents + { + public const string SettingsName = "Disguise RenderStream"; + } + + [SettingsProvider] + static SettingsProvider CreateSettingsProvider() => + new (k_SettingsPath, SettingsScope.Project) + { + label = Contents.SettingsName, + activateHandler = (searchContext, parentElement) => + { + var settings = DisguiseRenderStreamSettings.GetOrCreateSettings(); + var editor = Editor.CreateEditor(settings); + + var gui = editor.CreateInspectorGUI(); + gui.AddToClassList(DisguiseRenderStreamSettingsEditor.Style.DisguiseSettingsContainer); + + var title = new Label { text = Contents.SettingsName }; + title.AddToClassList(DisguiseRenderStreamSettingsEditor.Style.DisguiseSettingsTitle); + gui.Insert(0, title); + + parentElement.Add(gui); + }, + keywords = SettingsProvider.GetSearchKeywordsFromSerializedObject(new SerializedObject(DisguiseRenderStreamSettings.GetOrCreateSettings())) + }; + } +} diff --git a/Editor/DisguiseRenderStreamSettingsProvider.cs.meta b/Editor/DisguiseRenderStreamSettingsProvider.cs.meta new file mode 100644 index 0000000..9c0218b --- /dev/null +++ b/Editor/DisguiseRenderStreamSettingsProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 283858074c5af2045926847f746495ef +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/ReflectedMemberPreserver.cs b/Editor/ReflectedMemberPreserver.cs new file mode 100644 index 0000000..876f1c2 --- /dev/null +++ b/Editor/ReflectedMemberPreserver.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml; +using System.Xml.Linq; +using UnityEngine; + +namespace Disguise.RenderStream +{ + /// + /// Creates a link.xml file to preserve type members required by , as it relies on reflection. + /// This is to support managed code stripping, which is always enabled in IL2CPP: https://docs.unity3d.com/Manual/ManagedCodeStripping.html. + /// + class ReflectedMemberPreserver + { + const string k_XmlDirectory = "Library/Disguise"; + const string k_XmlName = "DisguisePreserve.xml"; + + Dictionary>> m_MembersToPreserve = new(); + + public void Preserve(MemberInfo member) + { + var type = member.DeclaringType; + + if (type == null) + { + return; + } + + var assembly = type.Assembly; + + if (!m_MembersToPreserve.TryGetValue(assembly, out var typeMembers)) + { + typeMembers = new Dictionary>(); + m_MembersToPreserve.Add(assembly, typeMembers); + } + if (!typeMembers.TryGetValue(type, out var members)) + { + members = new HashSet(); + typeMembers.Add(type, members); + } + + members.Add(member); + } + + public string GenerateAdditionalLinkXmlFile() + { + var projectDir = Path.GetDirectoryName(Application.dataPath); + var xmlDir = $"{projectDir}/{k_XmlDirectory}"; + var xmlPath = $"{xmlDir}/{k_XmlName}"; + + if (!Directory.Exists(xmlDir)) + { + Directory.CreateDirectory(xmlDir); + } + + CreateLinkXml(xmlPath); + + return xmlPath; + } + + void CreateLinkXml(string filepath) + { + var xmlSettings = new XmlWriterSettings + { + OmitXmlDeclaration = true, + Indent = true + }; + + using var xmlWriter = XmlWriter.Create(filepath, xmlSettings); + + var xmlLinker = new XElement("linker"); + + // Sort everything by name so the file output is deterministic. This ensures the build system + // only detects a change when the preserved members are different. + foreach (var assemblyMembers in m_MembersToPreserve.OrderBy(a => a.Key.GetName().Name)) + { + var xmlAssembly = new XElement("assembly", new XAttribute("fullname", assemblyMembers.Key.GetName().Name)); + xmlLinker.Add(xmlAssembly); + + foreach (var typeMembers in assemblyMembers.Value.OrderBy(t => t.Key.FullName)) + { + var xmlType = new XElement("type", new XAttribute("fullname", ToCecilName(typeMembers.Key.FullName))); + xmlAssembly.Add(xmlType); + + foreach (var member in typeMembers.Value.OrderBy(m => m.Name)) + { + switch (member) + { + case FieldInfo field: + { + xmlType.Add(new XElement("field", new XAttribute("name", member.Name))); + break; + } + case PropertyInfo property: + { + xmlType.Add(new XElement("property", new XAttribute("name", member.Name))); + break; + } + case MethodInfo method: + { + xmlType.Add(new XElement("method", new XAttribute("name", member.Name))); + break; + } + default: + { + throw new NotImplementedException($"Unsupported {nameof(MemberInfo)} subtype"); + } + } + } + } + } + + xmlLinker.Save(xmlWriter); + } + + static string ToCecilName(string fullTypeName) + { + return fullTypeName.Replace('+', '/'); + } + } +} diff --git a/Editor/ReflectedMemberPreserver.cs.meta b/Editor/ReflectedMemberPreserver.cs.meta new file mode 100644 index 0000000..c7536b9 --- /dev/null +++ b/Editor/ReflectedMemberPreserver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7afd7cd610f74984ea944bbb12b1bf87 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DisguiseUnityRenderStream/Editor/RenderingPipelineDefines.cs b/Editor/RenderingPipelineDefines.cs similarity index 80% rename from DisguiseUnityRenderStream/Editor/RenderingPipelineDefines.cs rename to Editor/RenderingPipelineDefines.cs index 6035dd2..b274a67 100644 --- a/DisguiseUnityRenderStream/Editor/RenderingPipelineDefines.cs +++ b/Editor/RenderingPipelineDefines.cs @@ -32,6 +32,11 @@ static void UpdateDefines() AddDefine("UNITY_PIPELINE_HDRP"); else RemoveDefine("UNITY_PIPELINE_HDRP"); + + if (PlayerSettings.useHDRDisplay) + AddDefine("DISGUISE_UNITY_USE_HDR_DISPLAY"); + else + RemoveDefine("DISGUISE_UNITY_USE_HDR_DISPLAY"); } static PipelineType GetPipeline() @@ -80,9 +85,15 @@ public static List GetDefines() { var target = EditorUserBuildSettings.activeBuildTarget; var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(target); - var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup); +#if UNITY_2023_1_OR_NEWER + var namedBuildTarget = UnityEditor.Build.NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup); + PlayerSettings.GetScriptingDefineSymbols(namedBuildTarget, out var defines); + return defines.ToList(); +#else + var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup); return defines.Split(';').ToList(); +#endif } public static void SetDefines(List definesList) @@ -91,6 +102,11 @@ public static void SetDefines(List definesList) var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(target); var defines = string.Join(";", definesList.ToArray()); +#if UNITY_2023_1_OR_NEWER + var namedBuildTarget = UnityEditor.Build.NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup); + PlayerSettings.SetScriptingDefineSymbols(namedBuildTarget, defines); +#else PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, defines); +#endif } } diff --git a/Editor/RenderingPipelineDefines.cs.meta b/Editor/RenderingPipelineDefines.cs.meta new file mode 100644 index 0000000..468afb7 --- /dev/null +++ b/Editor/RenderingPipelineDefines.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 90622f68a5edf4148bd74520bd086829 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DisguiseUnityRenderStream/Editor/ReorderableListUtility.cs b/Editor/ReorderableListUtility.cs similarity index 100% rename from DisguiseUnityRenderStream/Editor/ReorderableListUtility.cs rename to Editor/ReorderableListUtility.cs diff --git a/Editor/ReorderableListUtility.cs.meta b/Editor/ReorderableListUtility.cs.meta new file mode 100644 index 0000000..6ff067c --- /dev/null +++ b/Editor/ReorderableListUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 63279323a321ac440a27ac4a296c49ec +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/LICENSE.meta b/LICENSE.meta new file mode 100644 index 0000000..25c2d5e --- /dev/null +++ b/LICENSE.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 64ba18c73060b444398d6be6da1e4f83 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Native~/.gitignore b/Native~/.gitignore new file mode 100644 index 0000000..fd63048 --- /dev/null +++ b/Native~/.gitignore @@ -0,0 +1,59 @@ +## Ignore Visual Studio temporary files and build results +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc diff --git a/Native~/DX12System.h b/Native~/DX12System.h new file mode 100644 index 0000000..4eab889 --- /dev/null +++ b/Native~/DX12System.h @@ -0,0 +1,72 @@ +#pragma once +#include + +#include "Unity/IUnityInterface.h" +#include "Unity/IUnityGraphics.h" + +struct IDXGISwapChain; +#include "d3d12.h" +#include "Unity/IUnityGraphicsD3D12.h" + +#include "Logger.h" + +namespace NativeRenderingPlugin +{ + class DX12System + { + public: + + DX12System(IUnityInterfaces* unityInterfaces) : + m_UnityGraphics(nullptr), + m_Device(nullptr), + m_CommandQueue(nullptr), + m_IsInitialized(false) + { + m_UnityGraphics = unityInterfaces->Get(); + + if (m_UnityGraphics == nullptr) + { + s_Logger->LogError("DX12System: Failed to fetch DX12 interface."); + return; + } + + m_Device = m_UnityGraphics->GetDevice(); + m_CommandQueue = m_UnityGraphics->GetCommandQueue(); + m_IsInitialized = m_Device != nullptr && m_CommandQueue != nullptr; + + if (m_Device == nullptr) + { + s_Logger->LogError("DX12System: Failed to fetch DX12 device."); + } + + if (m_CommandQueue == nullptr) + { + s_Logger->LogError("DX12System: Failed to fetch DX12 command queue."); + } + } + + bool IsInitialized() const + { + return m_IsInitialized; + } + + ID3D12Device* GetDevice() const + { + return m_Device; + } + + ID3D12CommandQueue* GetCommandQueue() const + { + return m_CommandQueue; + } + + private: + + IUnityGraphicsD3D12v5* m_UnityGraphics; + ID3D12Device* m_Device; + ID3D12CommandQueue* m_CommandQueue; + bool m_IsInitialized; + }; + + inline std::unique_ptr s_DX12System; +} diff --git a/Native~/DX12Texture.h b/Native~/DX12Texture.h new file mode 100644 index 0000000..492c59f --- /dev/null +++ b/Native~/DX12Texture.h @@ -0,0 +1,81 @@ +#pragma once +#include "d3d12.h" + +#include "Disguise/d3renderstream.h" + +#include "Logger.h" +#include "DX12System.h" + +namespace NativeRenderingPlugin +{ + DXGI_FORMAT ToDXFormat(RSPixelFormat pixelFormat, bool sRGB) + { + switch (pixelFormat) + { + case RSPixelFormat::RS_FMT_BGRA8: + case RSPixelFormat::RS_FMT_BGRX8: + return sRGB ? DXGI_FORMAT_B8G8R8A8_UNORM_SRGB : DXGI_FORMAT_B8G8R8A8_UNORM; + + case RSPixelFormat::RS_FMT_RGBA32F: + return DXGI_FORMAT_R32G32B32A32_FLOAT; + + case RSPixelFormat::RS_FMT_RGBA16: + return DXGI_FORMAT_R16G16B16A16_UNORM; + + case RSPixelFormat::RS_FMT_RGBA8: + case RSPixelFormat::RS_FMT_RGBX8: + return sRGB ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM; + + case RSPixelFormat::RS_FMT_INVALID: + default: + return DXGI_FORMAT_UNKNOWN; + } + } + + const D3D12_HEAP_PROPERTIES D3D12_DEFAULT_HEAP_PROPS = { + D3D12_HEAP_TYPE_DEFAULT, D3D12_CPU_PAGE_PROPERTY_UNKNOWN, D3D12_MEMORY_POOL_UNKNOWN, 0, 0 + }; + + ID3D12Resource* CreateTexture(const LPCWSTR name, int width, int height, RSPixelFormat pixelFormat, bool sRGB) + { + const DXGI_FORMAT dxFormat = ToDXFormat(pixelFormat, sRGB); + if (dxFormat == DXGI_FORMAT_UNKNOWN) + { + s_Logger->LogError("Unsupported PixelFormat: ", pixelFormat); + return nullptr; + } + + D3D12_RESOURCE_DESC desc{}; + desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + desc.Alignment = 0; + desc.Width = width; + desc.Height = height; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.Format = dxFormat; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS | D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + + const D3D12_HEAP_FLAGS flags = D3D12_HEAP_FLAG_SHARED; + const D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COPY_DEST; + + ID3D12Resource* resource = nullptr; + HRESULT result = s_DX12System->GetDevice()->CreateCommittedResource( + &D3D12_DEFAULT_HEAP_PROPS, flags, &desc, initialState, nullptr, IID_PPV_ARGS(&resource)); + + if (result != S_OK) + { + s_Logger->LogError("CreateTexture: CreateCommittedResource failed: ", result); + return nullptr; + } + + if (name != nullptr) + { + resource->SetName(name); + } + + return resource; + } +} diff --git a/Native~/Disguise/d3renderstream.h b/Native~/Disguise/d3renderstream.h new file mode 100644 index 0000000..07ea0ea --- /dev/null +++ b/Native~/Disguise/d3renderstream.h @@ -0,0 +1,400 @@ +#ifndef D3RENDERSTREAM_H +#define D3RENDERSTREAM_H + +#include + + +enum RSPixelFormat : uint32_t +{ + RS_FMT_INVALID, + + RS_FMT_BGRA8, + RS_FMT_BGRX8, + + RS_FMT_RGBA32F, + + RS_FMT_RGBA16, + + RS_FMT_RGBA8, + RS_FMT_RGBX8, +}; + +struct ID3D11Device; +struct ID3D11Resource; +struct ID3D12Resource; +struct ID3D12Fence; +typedef unsigned int GLuint; +struct ID3D12Device; +struct ID3D12CommandQueue; +typedef struct VkDevice_T* VkDevice; +typedef struct VkDeviceMemory_T* VkDeviceMemory; +typedef uint64_t VkDeviceSize; +typedef struct VkSemaphore_T* VkSemaphore; +// Forward declare Windows compatible handles. +#define D3_DECLARE_HANDLE(name) \ + struct name##__; \ + typedef struct name##__* name +D3_DECLARE_HANDLE(HGLRC); +D3_DECLARE_HANDLE(HDC); + +enum RS_ERROR +{ + RS_ERROR_SUCCESS = 0, + + // Core is not initialised + RS_NOT_INITIALISED, + + // Core is already initialised + RS_ERROR_ALREADYINITIALISED, + + // Given handle is invalid + RS_ERROR_INVALIDHANDLE, + + // Maximum number of frame senders have been created + RS_MAXSENDERSREACHED, + + RS_ERROR_BADSTREAMTYPE, + + RS_ERROR_NOTFOUND, + + RS_ERROR_INCORRECTSCHEMA, + + RS_ERROR_INVALID_PARAMETERS, + + RS_ERROR_BUFFER_OVERFLOW, + + RS_ERROR_TIMEOUT, + + RS_ERROR_STREAMS_CHANGED, + + RS_ERROR_INCOMPATIBLE_VERSION, + + RS_ERROR_FAILED_TO_GET_DXDEVICE_FROM_RESOURCE, + + RS_ERROR_FAILED_TO_INITIALISE_GPGPU, + + RS_ERROR_QUIT, + + RS_ERROR_UNSPECIFIED +}; + +// Bitmask flags +enum FRAMEDATA_FLAGS +{ + FRAMEDATA_NO_FLAGS = 0, + FRAMEDATA_RESET = 1 +}; + +enum REMOTEPARAMETER_FLAGS +{ + REMOTEPARAMETER_NO_FLAGS = 0, + REMOTEPARAMETER_NO_SEQUENCE = 1, + REMOTEPARAMETER_READ_ONLY = 2 +}; + +typedef uint64_t StreamHandle; +typedef uint64_t CameraHandle; +typedef void (*logger_t)(const char*); + +#pragma pack(push, 4) +typedef struct +{ + float virtualZoomScale; + uint8_t virtualReprojectionRequired; + float xRealCamera, yRealCamera, zRealCamera; + float rxRealCamera, ryRealCamera, rzRealCamera; +} D3TrackingData; // Tracking data required by d3 but not used to render content + +typedef struct +{ + StreamHandle id; + CameraHandle cameraHandle; + float x, y, z; + float rx, ry, rz; + float focalLength; + float sensorX, sensorY; + float cx, cy; + float nearZ, farZ; + float orthoWidth; // If > 0, an orthographic camera should be used + D3TrackingData d3Tracking; +} CameraData; + +typedef struct +{ + double tTracked; + double localTime; + double localTimeDelta; + unsigned int frameRateNumerator; + unsigned int frameRateDenominator; + uint32_t flags; // FRAMEDATA_FLAGS + uint32_t scene; +} FrameData; + +typedef struct +{ + double tTracked; + CameraData camera; +} CameraResponseData; + + +typedef struct +{ + uint8_t* data; + uint32_t stride; +} HostMemoryData; + +typedef struct +{ + ID3D11Resource* resource; +} Dx11Data; + +typedef struct +{ + ID3D12Resource* resource; +} Dx12Data; + +typedef struct +{ + GLuint texture; +} OpenGlData; + +typedef struct +{ + VkDeviceMemory memory; + VkDeviceSize size; + RSPixelFormat format; + uint32_t width; + uint32_t height; + VkSemaphore waitSemaphore; + uint64_t waitSemaphoreValue; + VkSemaphore signalSemaphore; + uint64_t signalSemaphoreValue; +} VulkanDataStructure; + +typedef struct +{ + VulkanDataStructure* image; +} VulkanData; + +typedef union +{ + HostMemoryData cpu; + Dx11Data dx11; + Dx12Data dx12; + OpenGlData gl; + VulkanData vk; +} SenderFrameTypeData; + +typedef struct +{ + uint32_t xOffset; + uint32_t yOffset; + uint32_t width; + uint32_t height; +} FrameRegion; + +// Normalised (0-1) clipping planes for the edges of the camera frustum, to be used to perform off-axis perspective projection, or +// to offset and scale 2D orthographic matrices. +typedef struct +{ + float left; + float right; + float top; + float bottom; +} ProjectionClipping; + +typedef struct +{ + StreamHandle handle; + const char* channel; + uint64_t mappingId; + int32_t iViewpoint; + const char* name; + uint32_t width; + uint32_t height; + RSPixelFormat format; + ProjectionClipping clipping; +} StreamDescription; + +typedef struct +{ + uint32_t nStreams; + StreamDescription* streams; +} StreamDescriptions; + +enum RemoteParameterType +{ + RS_PARAMETER_NUMBER, + RS_PARAMETER_IMAGE, + RS_PARAMETER_POSE, // 4x4 TR matrix + RS_PARAMETER_TRANSFORM, // 4x4 TRS matrix + RS_PARAMETER_TEXT, +}; + +enum RemoteParameterDmxType +{ + RS_DMX_DEFAULT, + RS_DMX_8, + RS_DMX_16_BE, +}; + +typedef struct +{ + float min; + float max; + float step; + float defaultValue; +} NumericalDefaults; + +typedef struct +{ + const char* defaultValue; +} TextDefaults; + +typedef union +{ + NumericalDefaults number; + TextDefaults text; +} RemoteParameterTypeDefaults; + +typedef struct +{ + uint32_t width, height; + RSPixelFormat format; + int64_t imageId; +} ImageFrameData; + +typedef struct +{ + const char* group; + const char* displayName; + const char* key; + RemoteParameterType type; + RemoteParameterTypeDefaults defaults; + uint32_t nOptions; + const char** options; + + int32_t dmxOffset; // DMX channel offset or auto (-1) + RemoteParameterDmxType dmxType; + uint32_t flags; // REMOTEPARAMETER_FLAGS +} RemoteParameter; + +typedef struct +{ + const char* name; + uint32_t nParameters; + RemoteParameter* parameters; + uint64_t hash; +} RemoteParameters; + +typedef struct +{ + uint32_t nScenes; + RemoteParameters* scenes; +} Scenes; + +typedef struct +{ + uint32_t nChannels; + const char** channels; +} Channels; + +typedef struct +{ + const char* engineName; + const char* engineVersion; + const char* info; + Channels channels; + Scenes scenes; +} Schema; + +typedef struct +{ + const char* name; + float value; +} ProfilingEntry; + +#pragma pack(pop) + +#define D3_RENDER_STREAM_API __declspec( dllexport ) + +#define RENDER_STREAM_VERSION_MAJOR 1 +#define RENDER_STREAM_VERSION_MINOR 30 + +#define RENDER_STREAM_VERSION_STRING stringify(RENDER_STREAM_VERSION_MAJOR) "." stringify(RENDER_STREAM_VERSION_MINOR) + +enum SenderFrameType +{ + RS_FRAMETYPE_HOST_MEMORY, + RS_FRAMETYPE_DX11_TEXTURE, + RS_FRAMETYPE_DX12_TEXTURE, + RS_FRAMETYPE_OPENGL_TEXTURE, + RS_FRAMETYPE_VULKAN_TEXTURE, + RS_FRAMETYPE_UNKNOWN +}; + +enum UseDX12SharedHeapFlag +{ + RS_DX12_USE_SHARED_HEAP_FLAG, + RS_DX12_DO_NOT_USE_SHARED_HEAP_FLAG +}; + +typedef struct +{ + const CameraResponseData* cameraData; + uint64_t schemaHash; + uint32_t parameterDataSize; + void* parameterData; + uint32_t textDataCount; + const char** textData; +} FrameResponseData; + +// isolated functions, do not require init prior to use +extern "C" D3_RENDER_STREAM_API void rs_registerLoggingFunc(logger_t logger); +extern "C" D3_RENDER_STREAM_API void rs_registerErrorLoggingFunc(logger_t logger); +extern "C" D3_RENDER_STREAM_API void rs_registerVerboseLoggingFunc(logger_t logger); + +extern "C" D3_RENDER_STREAM_API void rs_unregisterLoggingFunc(); +extern "C" D3_RENDER_STREAM_API void rs_unregisterErrorLoggingFunc(); +extern "C" D3_RENDER_STREAM_API void rs_unregisterVerboseLoggingFunc(); + +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_initialise(int expectedVersionMajor, int expectedVersionMinor); +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_initialiseGpGpuWithoutInterop(ID3D11Device* device); +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_initialiseGpGpuWithDX11Device(ID3D11Device* device); +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_initialiseGpGpuWithDX11Resource(ID3D11Resource* resource); +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_initialiseGpGpuWithDX12DeviceAndQueue(ID3D12Device* device, ID3D12CommandQueue* queue); +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_initialiseGpGpuWithOpenGlContexts(HGLRC glContext, HDC deviceContext); +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_initialiseGpGpuWithVulkanDevice(VkDevice device); +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_shutdown(); + +// non-isolated functions, these require init prior to use + +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_useDX12SharedHeapFlag(UseDX12SharedHeapFlag * flag); +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_saveSchema(const char* assetPath, Schema* schema); // Save schema for project file/custom executable at (assetPath) +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_loadSchema(const char* assetPath, /*Out*/Schema* schema, /*InOut*/uint32_t* nBytes); // Load schema for project file/custom executable at (assetPath) into a buffer of size (nBytes) starting at (schema) + +// workload functions, these require the process to be running inside d3's asset launcher environment + +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_setSchema(/*InOut*/Schema* schema); // Set schema and fill in per-scene hash for use with rs_getFrameParameters + +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_getStreams(/*Out*/StreamDescriptions* streams, /*InOut*/uint32_t* nBytes); // Populate streams into a buffer of size (nBytes) starting at (streams) + +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_awaitFrameData(int timeoutMs, /*Out*/FrameData * data); // waits for any asset, any stream to request a frame, provides the parameters for that frame. +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_setFollower(int isFollower); // Used to mark this node as relying on alternative mechanisms to distribute FrameData. Users must provide correct CameraResponseData to sendFrame, and call rs_beginFollowerFrame at the start of the frame, where awaitFrame would normally be called. +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_beginFollowerFrame(double tTracked); // Pass the engine-distributed tTracked value in, if you have called rs_setFollower(1) otherwise do not call this function. + +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_getFrameParameters(uint64_t schemaHash, /*Out*/void* outParameterData, uint64_t outParameterDataSize); // returns the remote parameters for this frame. +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_getFrameImageData(uint64_t schemaHash, /*Out*/ImageFrameData* outParameterData, uint64_t outParameterDataCount); // returns the remote image data for this frame. +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_getFrameImage(int64_t imageId, SenderFrameType frameType, /*InOut*/SenderFrameTypeData data); // fills in (data) with the remote image +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_getFrameText(uint64_t schemaHash, uint32_t textParamIndex, /*Out*/const char** outTextPtr); // // returns the remote text data (pointer only valid until next rs_awaitFrameData) + +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_getFrameCamera(StreamHandle streamHandle, /*Out*/CameraData* outCameraData); // returns the CameraData for this stream, or RS_ERROR_NOTFOUND if no camera data is available for this stream on this frame +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_sendFrame(StreamHandle streamHandle, SenderFrameType frameType, SenderFrameTypeData data, const FrameResponseData* frameData); // publish a frame which was generated from the associated tracking and timing information. + +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_releaseImage(SenderFrameType frameType, SenderFrameTypeData data); // release any references to image (e.g. before deletion) + +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_logToD3(const char* str); // Transmit log message over network. New line automatically appended +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_sendProfilingData(ProfilingEntry * entries, int count); +extern "C" D3_RENDER_STREAM_API RS_ERROR rs_setNewStatusMessage(const char* msg); + +#endif diff --git a/Native~/Events.h b/Native~/Events.h new file mode 100644 index 0000000..68454fd --- /dev/null +++ b/Native~/Events.h @@ -0,0 +1,111 @@ +#pragma once +#include +#include +#include // For ComPtr + +#include "Disguise/d3renderstream.h" + +#include "Logger.h" + +namespace NativeRenderingPlugin +{ + // Render thread event IDs + // Should match EventID (NativeRenderingPlugin.cs) + enum class EventID + { + INPUT_IMAGE, + SEND_FRAME, + MAX + }; + + typedef RS_ERROR(*t_rs_getFrameImage)(int64_t imageId, SenderFrameType frameType, SenderFrameTypeData data); + typedef RS_ERROR(*t_rs_sendFrame)(StreamHandle, SenderFrameType, SenderFrameTypeData, const FrameResponseData*); + + // EventID::INPUT_IMAGE data structure + // Should match InputImageData (NativeRenderingPlugin.cs) + struct InputImageData + { + t_rs_getFrameImage m_rs_getFrameImage; // Function pointer into Disguise DLL + int64_t m_ImageID; + IUnknown* m_Texture; // ID3D11Texture or ID3D12Resource + + RS_ERROR Execute() const + { + if (m_Texture == nullptr) + { + s_Logger->LogError("InputImageData null texture pointer"); + return RS_ERROR::RS_ERROR_INVALID_PARAMETERS; + } + + SenderFrameType senderType = RS_FRAMETYPE_UNKNOWN; + SenderFrameTypeData senderData = {}; + + Microsoft::WRL::ComPtr dx11Resource; + Microsoft::WRL::ComPtr dx12Resource; + + if (m_Texture->QueryInterface(IID_ID3D11Resource, &dx11Resource) == S_OK) + { + senderType = RS_FRAMETYPE_DX11_TEXTURE; + senderData.dx11.resource = dx11Resource.Get(); + } + else if (m_Texture->QueryInterface(IID_ID3D12Resource, &dx12Resource) == S_OK) + { + senderType = RS_FRAMETYPE_DX12_TEXTURE; + senderData.dx12.resource = dx12Resource.Get(); + } + else + { + s_Logger->LogError("InputImageData unknown texture type"); + return RS_ERROR::RS_ERROR_INVALID_PARAMETERS; + } + + return m_rs_getFrameImage(m_ImageID, senderType, senderData); + } + }; + + // EventID::SEND_FRAME data structure + // Should match SendFrameData (NativeRenderingPlugin.cs) + struct SendFrameData + { + t_rs_sendFrame m_rs_sendFrame; // Function pointer into Disguise DLL + StreamHandle m_StreamHandle; + IUnknown* m_Texture; // ID3D11Texture or ID3D12Resource + CameraResponseData m_CameraResponseData; + + RS_ERROR Execute() const + { + if (m_Texture == nullptr) + { + s_Logger->LogError("SendFrameData null texture pointer"); + return RS_ERROR::RS_ERROR_INVALID_PARAMETERS; + } + + SenderFrameType senderType = RS_FRAMETYPE_UNKNOWN; + SenderFrameTypeData senderData = {}; + + Microsoft::WRL::ComPtr dx11Resource; + Microsoft::WRL::ComPtr dx12Resource; + + if (m_Texture->QueryInterface(IID_ID3D11Resource, &dx11Resource) == S_OK) + { + senderType = RS_FRAMETYPE_DX11_TEXTURE; + senderData.dx11.resource = dx11Resource.Get(); + } + else if (m_Texture->QueryInterface(IID_ID3D12Resource, &dx12Resource) == S_OK) + { + senderType = RS_FRAMETYPE_DX12_TEXTURE; + senderData.dx12.resource = dx12Resource.Get(); + } + else + { + s_Logger->LogError("SendFrameData unknown texture type"); + return RS_ERROR::RS_ERROR_INVALID_PARAMETERS; + } + + FrameResponseData responseData = {}; + responseData.cameraData = &m_CameraResponseData; + + return m_rs_sendFrame(m_StreamHandle, senderType, senderData, &responseData); + } + }; +} diff --git a/Native~/Logger.h b/Native~/Logger.h new file mode 100644 index 0000000..cb2f970 --- /dev/null +++ b/Native~/Logger.h @@ -0,0 +1,69 @@ +#pragma once +#include +#include + +#include "Unity/IUnityLog.h" + +namespace NativeRenderingPlugin +{ + class Logger + { + public: + + Logger(IUnityInterfaces* unityInterfaces) : + m_Log(nullptr), + m_IsInitialized(false) + { + m_Log = unityInterfaces->Get(); + + if (m_Log != nullptr) + { + m_IsInitialized = true; + } + } + + bool IsInitialized() const + { + return m_IsInitialized; + } + + void LogWarning(const char* msg) + { + if (m_IsInitialized) + UNITY_LOG_WARNING(m_Log, msg); + } + + void LogWarning(const char* msg, int errorCode) + { + if (m_IsInitialized) + UNITY_LOG_WARNING(m_Log, FormatErrorMessage(msg, errorCode).c_str()); + } + + void LogError(const char* msg) + { + if (m_IsInitialized) + UNITY_LOG_ERROR(m_Log, msg); + } + + void LogError(const char* msg, int errorCode) + { + if (m_IsInitialized) + UNITY_LOG_ERROR(m_Log, FormatErrorMessage(msg, errorCode).c_str()); + } + + private: + + static std::string FormatErrorMessage(const char* msg, int errorCode) + { + std::string str = msg; + str += ": "; + str += std::to_string(errorCode); + return str; + } + + IUnityLog* m_Log; + bool m_IsInitialized; + }; + + inline std::unique_ptr s_Logger; +} diff --git a/Native~/NativeRenderingPlugin.sln b/Native~/NativeRenderingPlugin.sln new file mode 100644 index 0000000..c668989 --- /dev/null +++ b/Native~/NativeRenderingPlugin.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32505.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NativeRenderingPlugin", "NativeRenderingPlugin.vcxproj", "{0D920B8A-A7B9-4A45-B742-9986AF5A6216}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0D920B8A-A7B9-4A45-B742-9986AF5A6216}.Debug|x64.ActiveCfg = Debug|x64 + {0D920B8A-A7B9-4A45-B742-9986AF5A6216}.Debug|x64.Build.0 = Debug|x64 + {0D920B8A-A7B9-4A45-B742-9986AF5A6216}.Debug|x86.ActiveCfg = Debug|Win32 + {0D920B8A-A7B9-4A45-B742-9986AF5A6216}.Debug|x86.Build.0 = Debug|Win32 + {0D920B8A-A7B9-4A45-B742-9986AF5A6216}.Release|x64.ActiveCfg = Release|x64 + {0D920B8A-A7B9-4A45-B742-9986AF5A6216}.Release|x64.Build.0 = Release|x64 + {0D920B8A-A7B9-4A45-B742-9986AF5A6216}.Release|x86.ActiveCfg = Release|Win32 + {0D920B8A-A7B9-4A45-B742-9986AF5A6216}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A78D8640-16FA-4C6D-8838-495E6B101F45} + EndGlobalSection +EndGlobal diff --git a/Native~/NativeRenderingPlugin.vcxproj b/Native~/NativeRenderingPlugin.vcxproj new file mode 100644 index 0000000..562d993 --- /dev/null +++ b/Native~/NativeRenderingPlugin.vcxproj @@ -0,0 +1,167 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {0d920b8a-a7b9-4a45-b742-9986af5a6216} + NativeRenderingPlugin + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level4 + true + WIN32;_DEBUG;NATIVERENDERINGPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + true + 4100;%(DisableSpecificWarnings) + stdcpp17 + + + Windows + true + false + dxguid.lib;%(AdditionalDependencies) + + + + + Level4 + true + true + true + WIN32;NDEBUG;NATIVERENDERINGPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + true + 4100;%(DisableSpecificWarnings) + stdcpp17 + + + Windows + true + true + true + false + dxguid.lib;%(AdditionalDependencies) + + + + + Level4 + true + _DEBUG;NATIVERENDERINGPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + true + 4100;%(DisableSpecificWarnings) + stdcpp17 + + + Windows + true + false + dxguid.lib;%(AdditionalDependencies) + + + + + Level4 + true + true + true + NDEBUG;NATIVERENDERINGPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + true + 4100;%(DisableSpecificWarnings) + stdcpp17 + + + Windows + true + true + true + false + dxguid.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Native~/NativeRenderingPlugin.vcxproj.filters b/Native~/NativeRenderingPlugin.vcxproj.filters new file mode 100644 index 0000000..38d095a --- /dev/null +++ b/Native~/NativeRenderingPlugin.vcxproj.filters @@ -0,0 +1,56 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {f744d287-64ea-4f37-ab6b-5818cfcf29a5} + + + {4697d858-ccc0-42ca-b5f6-746942c3754a} + + + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Unity + + + Unity + + + Unity + + + Unity + + + Disguise + + + Source Files + + + \ No newline at end of file diff --git a/Native~/PublicAPI.h b/Native~/PublicAPI.h new file mode 100644 index 0000000..62f12e5 --- /dev/null +++ b/Native~/PublicAPI.h @@ -0,0 +1,5 @@ +#pragma once + +#include "Unity/IUnityGraphics.h" + +static void UNITY_INTERFACE_API OnGraphicsDeviceEvent(UnityGfxDeviceEventType eventType); diff --git a/Native~/Unity/IUnityGraphics.h b/Native~/Unity/IUnityGraphics.h new file mode 100644 index 0000000..71dea56 --- /dev/null +++ b/Native~/Unity/IUnityGraphics.h @@ -0,0 +1,62 @@ +// Unity Native Plugin API copyright © 2015 Unity Technologies ApS +// +// Licensed under the Unity Companion License for Unity - dependent projects--see[Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License). +// +// Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.Please review the license for details on these and other terms and conditions. + +#pragma once +#include "IUnityInterface.h" + +// Has to match the GfxDeviceRenderer enum +typedef enum UnityGfxRenderer +{ + //kUnityGfxRendererOpenGL = 0, // Legacy OpenGL, removed + //kUnityGfxRendererD3D9 = 1, // Direct3D 9, removed + kUnityGfxRendererD3D11 = 2, // Direct3D 11 + kUnityGfxRendererNull = 4, // "null" device (used in batch mode) + kUnityGfxRendererOpenGLES20 = 8, // OpenGL ES 2.0 + kUnityGfxRendererOpenGLES30 = 11, // OpenGL ES 3.0 + //kUnityGfxRendererGXM = 12, // PlayStation Vita, removed + kUnityGfxRendererPS4 = 13, // PlayStation 4 + kUnityGfxRendererXboxOne = 14, // Xbox One + kUnityGfxRendererMetal = 16, // iOS Metal + kUnityGfxRendererOpenGLCore = 17, // OpenGL core + kUnityGfxRendererD3D12 = 18, // Direct3D 12 + kUnityGfxRendererVulkan = 21, // Vulkan + kUnityGfxRendererNvn = 22, // Nintendo Switch NVN API + kUnityGfxRendererXboxOneD3D12 = 23, // MS XboxOne Direct3D 12 + kUnityGfxRendererGameCoreXboxOne = 24, // GameCore Xbox One + kUnityGfxRendererGameCoreXboxSeries = 25, // GameCore XboxSeries + kUnityGfxRendererPS5 = 26, // PS5 + kUnityGfxRendererPS5NGGC = 27 // PS5 NGGC +} UnityGfxRenderer; + +typedef enum UnityGfxDeviceEventType +{ + kUnityGfxDeviceEventInitialize = 0, + kUnityGfxDeviceEventShutdown = 1, + kUnityGfxDeviceEventBeforeReset = 2, + kUnityGfxDeviceEventAfterReset = 3, +} UnityGfxDeviceEventType; + +typedef void (UNITY_INTERFACE_API * IUnityGraphicsDeviceEventCallback)(UnityGfxDeviceEventType eventType); + +// Should only be used on the rendering thread unless noted otherwise. +UNITY_DECLARE_INTERFACE(IUnityGraphics) +{ + UnityGfxRenderer(UNITY_INTERFACE_API * GetRenderer)(); // Thread safe + + // This callback will be called when graphics device is created, destroyed, reset, etc. + // It is possible to miss the kUnityGfxDeviceEventInitialize event in case plugin is loaded at a later time, + // when the graphics device is already created. + void(UNITY_INTERFACE_API * RegisterDeviceEventCallback)(IUnityGraphicsDeviceEventCallback callback); + void(UNITY_INTERFACE_API * UnregisterDeviceEventCallback)(IUnityGraphicsDeviceEventCallback callback); + int(UNITY_INTERFACE_API * ReserveEventIDRange)(int count); // reserves 'count' event IDs. Plugins should use the result as a base index when issuing events back and forth to avoid event id clashes. +}; +UNITY_REGISTER_INTERFACE_GUID(0x7CBA0A9CA4DDB544ULL, 0x8C5AD4926EB17B11ULL, IUnityGraphics) + + +// Certain Unity APIs (GL.IssuePluginEvent, CommandBuffer.IssuePluginEvent) can callback into native plugins. +// Provide them with an address to a function of this signature. +typedef void (UNITY_INTERFACE_API * UnityRenderingEvent)(int eventId); +typedef void (UNITY_INTERFACE_API * UnityRenderingEventAndData)(int eventId, void* data); diff --git a/Native~/Unity/IUnityGraphicsD3D12.h b/Native~/Unity/IUnityGraphicsD3D12.h new file mode 100644 index 0000000..6b196ac --- /dev/null +++ b/Native~/Unity/IUnityGraphicsD3D12.h @@ -0,0 +1,217 @@ +// Unity Native Plugin API copyright © 2015 Unity Technologies ApS +// +// Licensed under the Unity Companion License for Unity - dependent projects--see[Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License). +// +// Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.Please review the license for details on these and other terms and conditions. + +#pragma once +#include "IUnityInterface.h" +#ifndef __cplusplus + #include +#endif + +struct RenderSurfaceBase; +typedef struct RenderSurfaceBase* UnityRenderBuffer; + +typedef struct UnityGraphicsD3D12ResourceState UnityGraphicsD3D12ResourceState; +struct UnityGraphicsD3D12ResourceState +{ + ID3D12Resource* resource; // Resource to barrier. + D3D12_RESOURCE_STATES expected; // Expected resource state before this command list is executed. + D3D12_RESOURCE_STATES current; // State this resource will be in after this command list is executed. +}; + +struct UnityGraphicsD3D12RecordingState +{ + ID3D12GraphicsCommandList* commandList; // D3D12 command list that is currently recorded by Unity +}; + +enum UnityD3D12GraphicsQueueAccess +{ + // No queue acccess, no work must be submitted to UnityD3D12Instance::graphicsQueue from the plugin event callback + kUnityD3D12GraphicsQueueAccess_DontCare, + + // Make sure that Unity worker threads don't access the D3D12 graphics queue + // This disables access to the current Unity command buffer + kUnityD3D12GraphicsQueueAccess_Allow, +}; + +enum UnityD3D12EventConfigFlagBits +{ + kUnityD3D12EventConfigFlag_EnsurePreviousFrameSubmission = (1 << 0), // default: (NOT SUPPORTED) + kUnityD3D12EventConfigFlag_FlushCommandBuffers = (1 << 1), // submit existing command buffers, default: not set + kUnityD3D12EventConfigFlag_SyncWorkerThreads = (1 << 2), // wait for worker threads to finish, default: not set + kUnityD3D12EventConfigFlag_ModifiesCommandBuffersState = (1 << 3), // should be set when descriptor set bindings, vertex buffer bindings, etc are changed (default: set) +}; + +struct UnityD3D12PluginEventConfig +{ + UnityD3D12GraphicsQueueAccess graphicsQueueAccess; + UINT32 flags; // UnityD3D12EventConfigFlagBits to be used when invoking a native plugin + bool ensureActiveRenderTextureIsBound; // If true, the actively bound render texture will be bound prior the execution of the native plugin method. +}; + +typedef struct UnityGraphicsD3D12PhysicalVideoMemoryControlValues UnityGraphicsD3D12PhysicalVideoMemoryControlValues; +struct UnityGraphicsD3D12PhysicalVideoMemoryControlValues // all absolute values in bytes +{ + UINT64 reservation; // Minimum required physical memory for an application [default = 64MB]. + UINT64 systemMemoryThreshold; // If free physical video memory drops below this threshold, resources will be allocated in system memory. [default = 64MB] + UINT64 residencyHysteresisThreshold; // Minimum free physical video memory needed to start bringing evicted resources back after shrunken video memory budget expands again. [default = 128MB] + float nonEvictableRelativeThreshold; // The relative proportion of the video memory budget that must be kept available for non-evictable resources. [default = 0.25] +}; + +// Should only be used on the rendering/submission thread. +UNITY_DECLARE_INTERFACE(IUnityGraphicsD3D12v7) +{ + ID3D12Device* (UNITY_INTERFACE_API * GetDevice)(); + + IDXGISwapChain* (UNITY_INTERFACE_API * GetSwapChain)(); + UINT32(UNITY_INTERFACE_API * GetSyncInterval)(); + UINT(UNITY_INTERFACE_API * GetPresentFlags)(); + + ID3D12Fence* (UNITY_INTERFACE_API * GetFrameFence)(); + // Returns the value set on the frame fence once the current frame completes or the GPU is flushed + UINT64(UNITY_INTERFACE_API * GetNextFrameFenceValue)(); + + // Executes a given command list on a worker thread. + // [Optional] Declares expected and post-execution resource states. + // Returns the fence value. + UINT64(UNITY_INTERFACE_API * ExecuteCommandList)(ID3D12GraphicsCommandList * commandList, int stateCount, UnityGraphicsD3D12ResourceState * states); + + void(UNITY_INTERFACE_API * SetPhysicalVideoMemoryControlValues)(const UnityGraphicsD3D12PhysicalVideoMemoryControlValues * memInfo); + + ID3D12CommandQueue* (UNITY_INTERFACE_API * GetCommandQueue)(); + + ID3D12Resource* (UNITY_INTERFACE_API * TextureFromRenderBuffer)(UnityRenderBuffer rb); + ID3D12Resource* (UNITY_INTERFACE_API * TextureFromNativeTexture)(UnityTextureID texture); + + // Change the precondition for a specific user-defined event + // Should be called during initialization + void(UNITY_INTERFACE_API * ConfigureEvent)(int eventID, const UnityD3D12PluginEventConfig * pluginEventConfig); + + bool(UNITY_INTERFACE_API * CommandRecordingState)(UnityGraphicsD3D12RecordingState * outCommandRecordingState); +}; +UNITY_REGISTER_INTERFACE_GUID(0x4624B0DA41B64AACULL, 0x915AABCB9BC3F0D3ULL, IUnityGraphicsD3D12v7) + +// Should only be used on the rendering/submission thread. +UNITY_DECLARE_INTERFACE(IUnityGraphicsD3D12v6) +{ + ID3D12Device* (UNITY_INTERFACE_API * GetDevice)(); + + ID3D12Fence* (UNITY_INTERFACE_API * GetFrameFence)(); + // Returns the value set on the frame fence once the current frame completes or the GPU is flushed + UINT64(UNITY_INTERFACE_API * GetNextFrameFenceValue)(); + + // Executes a given command list on a worker thread. + // [Optional] Declares expected and post-execution resource states. + // Returns the fence value. + UINT64(UNITY_INTERFACE_API * ExecuteCommandList)(ID3D12GraphicsCommandList * commandList, int stateCount, UnityGraphicsD3D12ResourceState * states); + + void(UNITY_INTERFACE_API * SetPhysicalVideoMemoryControlValues)(const UnityGraphicsD3D12PhysicalVideoMemoryControlValues * memInfo); + + ID3D12CommandQueue* (UNITY_INTERFACE_API * GetCommandQueue)(); + + ID3D12Resource* (UNITY_INTERFACE_API * TextureFromRenderBuffer)(UnityRenderBuffer rb); + ID3D12Resource* (UNITY_INTERFACE_API * TextureFromNativeTexture)(UnityTextureID texture); + + // Change the precondition for a specific user-defined event + // Should be called during initialization + void(UNITY_INTERFACE_API * ConfigureEvent)(int eventID, const UnityD3D12PluginEventConfig * pluginEventConfig); + + bool(UNITY_INTERFACE_API * CommandRecordingState)(UnityGraphicsD3D12RecordingState* outCommandRecordingState); +}; +UNITY_REGISTER_INTERFACE_GUID(0xA396DCE58CAC4D78ULL, 0xAFDD9B281F20B840ULL, IUnityGraphicsD3D12v6) + +// Should only be used on the rendering/submission thread. +UNITY_DECLARE_INTERFACE(IUnityGraphicsD3D12v5) +{ + ID3D12Device* (UNITY_INTERFACE_API * GetDevice)(); + + ID3D12Fence* (UNITY_INTERFACE_API * GetFrameFence)(); + // Returns the value set on the frame fence once the current frame completes or the GPU is flushed + UINT64(UNITY_INTERFACE_API * GetNextFrameFenceValue)(); + + // Executes a given command list on a worker thread. + // [Optional] Declares expected and post-execution resource states. + // Returns the fence value. + UINT64(UNITY_INTERFACE_API * ExecuteCommandList)(ID3D12GraphicsCommandList * commandList, int stateCount, UnityGraphicsD3D12ResourceState * states); + + void(UNITY_INTERFACE_API * SetPhysicalVideoMemoryControlValues)(const UnityGraphicsD3D12PhysicalVideoMemoryControlValues * memInfo); + + ID3D12CommandQueue* (UNITY_INTERFACE_API * GetCommandQueue)(); + + ID3D12Resource* (UNITY_INTERFACE_API * TextureFromRenderBuffer)(UnityRenderBuffer rb); +}; +UNITY_REGISTER_INTERFACE_GUID(0xF5C8D8A37D37BC42ULL, 0xB02DFE93B5064A27ULL, IUnityGraphicsD3D12v5) + +// Should only be used on the rendering/submission thread. +UNITY_DECLARE_INTERFACE(IUnityGraphicsD3D12v4) +{ + ID3D12Device* (UNITY_INTERFACE_API * GetDevice)(); + + ID3D12Fence* (UNITY_INTERFACE_API * GetFrameFence)(); + // Returns the value set on the frame fence once the current frame completes or the GPU is flushed + UINT64(UNITY_INTERFACE_API * GetNextFrameFenceValue)(); + + // Executes a given command list on a worker thread. + // [Optional] Declares expected and post-execution resource states. + // Returns the fence value. + UINT64(UNITY_INTERFACE_API * ExecuteCommandList)(ID3D12GraphicsCommandList * commandList, int stateCount, UnityGraphicsD3D12ResourceState * states); + + void(UNITY_INTERFACE_API * SetPhysicalVideoMemoryControlValues)(const UnityGraphicsD3D12PhysicalVideoMemoryControlValues * memInfo); + + ID3D12CommandQueue* (UNITY_INTERFACE_API * GetCommandQueue)(); +}; +UNITY_REGISTER_INTERFACE_GUID(0X498FFCC13EC94006ULL, 0XB18F8B0FF67778C8ULL, IUnityGraphicsD3D12v4) + +// Should only be used on the rendering/submission thread. +UNITY_DECLARE_INTERFACE(IUnityGraphicsD3D12v3) +{ + ID3D12Device* (UNITY_INTERFACE_API * GetDevice)(); + + ID3D12Fence* (UNITY_INTERFACE_API * GetFrameFence)(); + // Returns the value set on the frame fence once the current frame completes or the GPU is flushed + UINT64(UNITY_INTERFACE_API * GetNextFrameFenceValue)(); + + // Executes a given command list on a worker thread. + // [Optional] Declares expected and post-execution resource states. + // Returns the fence value. + UINT64(UNITY_INTERFACE_API * ExecuteCommandList)(ID3D12GraphicsCommandList * commandList, int stateCount, UnityGraphicsD3D12ResourceState * states); + + void(UNITY_INTERFACE_API * SetPhysicalVideoMemoryControlValues)(const UnityGraphicsD3D12PhysicalVideoMemoryControlValues * memInfo); +}; +UNITY_REGISTER_INTERFACE_GUID(0x57C3FAFE59E5E843ULL, 0xBF4F5998474BB600ULL, IUnityGraphicsD3D12v3) + +// Should only be used on the rendering/submission thread. +UNITY_DECLARE_INTERFACE(IUnityGraphicsD3D12v2) +{ + ID3D12Device* (UNITY_INTERFACE_API * GetDevice)(); + + ID3D12Fence* (UNITY_INTERFACE_API * GetFrameFence)(); + // Returns the value set on the frame fence once the current frame completes or the GPU is flushed + UINT64(UNITY_INTERFACE_API * GetNextFrameFenceValue)(); + + // Executes a given command list on a worker thread. + // [Optional] Declares expected and post-execution resource states. + // Returns the fence value. + UINT64(UNITY_INTERFACE_API * ExecuteCommandList)(ID3D12GraphicsCommandList * commandList, int stateCount, UnityGraphicsD3D12ResourceState * states); +}; +UNITY_REGISTER_INTERFACE_GUID(0xEC39D2F18446C745ULL, 0xB1A2626641D6B11FULL, IUnityGraphicsD3D12v2) + + +// Obsolete +UNITY_DECLARE_INTERFACE(IUnityGraphicsD3D12) +{ + ID3D12Device* (UNITY_INTERFACE_API * GetDevice)(); + ID3D12CommandQueue* (UNITY_INTERFACE_API * GetCommandQueue)(); + + ID3D12Fence* (UNITY_INTERFACE_API * GetFrameFence)(); + // Returns the value set on the frame fence once the current frame completes or the GPU is flushed + UINT64(UNITY_INTERFACE_API * GetNextFrameFenceValue)(); + + // Returns the state a resource will be in after the last command list is executed + bool(UNITY_INTERFACE_API * GetResourceState)(ID3D12Resource * resource, D3D12_RESOURCE_STATES * outState); + // Specifies the state a resource will be in after a plugin command list with resource barriers is executed + void(UNITY_INTERFACE_API * SetResourceState)(ID3D12Resource * resource, D3D12_RESOURCE_STATES state); +}; +UNITY_REGISTER_INTERFACE_GUID(0xEF4CEC88A45F4C4CULL, 0xBD295B6F2A38D9DEULL, IUnityGraphicsD3D12) diff --git a/Native~/Unity/IUnityInterface.h b/Native~/Unity/IUnityInterface.h new file mode 100644 index 0000000..ab713ec --- /dev/null +++ b/Native~/Unity/IUnityInterface.h @@ -0,0 +1,206 @@ +// Unity Native Plugin API copyright © 2015 Unity Technologies ApS +// +// Licensed under the Unity Companion License for Unity - dependent projects--see[Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License). +// +// Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.Please review the license for details on these and other terms and conditions. + +#pragma once + +// Unity native plugin API +// Compatible with C99 + +#if defined(__CYGWIN32__) + #define UNITY_INTERFACE_API __stdcall + #define UNITY_INTERFACE_EXPORT __declspec(dllexport) +#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WINAPI_FAMILY) + #define UNITY_INTERFACE_API __stdcall + #define UNITY_INTERFACE_EXPORT __declspec(dllexport) +#elif defined(__MACH__) || defined(__ANDROID__) || defined(__linux__) || defined(LUMIN) + #define UNITY_INTERFACE_API + #define UNITY_INTERFACE_EXPORT __attribute__ ((visibility ("default"))) +#else + #define UNITY_INTERFACE_API + #define UNITY_INTERFACE_EXPORT +#endif + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// IUnityInterface is a registry of interfaces we choose to expose to plugins. +// +// USAGE: +// --------- +// To retrieve an interface a user can do the following from a plugin, assuming they have the header file for the interface: +// +// IMyInterface * ptr = registry->Get(); +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Unity Interface GUID +// Ensures global uniqueness. +// +// Template specialization is used to produce a means of looking up a GUID from its interface type at compile time. +// The net result should compile down to passing around the GUID. +// +// UNITY_REGISTER_INTERFACE_GUID should be placed in the header file of any interface definition outside of all namespaces. +// The interface structure and the registration GUID are all that is required to expose the interface to other systems. +struct UnityInterfaceGUID +{ +#ifdef __cplusplus + UnityInterfaceGUID(unsigned long long high, unsigned long long low) + : m_GUIDHigh(high) + , m_GUIDLow(low) + { + } + + UnityInterfaceGUID(const UnityInterfaceGUID& other) + { + m_GUIDHigh = other.m_GUIDHigh; + m_GUIDLow = other.m_GUIDLow; + } + + UnityInterfaceGUID& operator=(const UnityInterfaceGUID& other) + { + m_GUIDHigh = other.m_GUIDHigh; + m_GUIDLow = other.m_GUIDLow; + return *this; + } + + bool Equals(const UnityInterfaceGUID& other) const { return m_GUIDHigh == other.m_GUIDHigh && m_GUIDLow == other.m_GUIDLow; } + bool LessThan(const UnityInterfaceGUID& other) const { return m_GUIDHigh < other.m_GUIDHigh || (m_GUIDHigh == other.m_GUIDHigh && m_GUIDLow < other.m_GUIDLow); } +#endif + unsigned long long m_GUIDHigh; + unsigned long long m_GUIDLow; +}; +#ifdef __cplusplus +inline bool operator==(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return left.Equals(right); } +inline bool operator!=(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return !left.Equals(right); } +inline bool operator<(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return left.LessThan(right); } +inline bool operator>(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return right.LessThan(left); } +inline bool operator>=(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return !operator<(left, right); } +inline bool operator<=(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return !operator>(left, right); } +#else +typedef struct UnityInterfaceGUID UnityInterfaceGUID; +#endif + + +#ifdef __cplusplus + #define UNITY_DECLARE_INTERFACE(NAME) \ + struct NAME : IUnityInterface + +// Generic version of GetUnityInterfaceGUID to allow us to specialize it +// per interface below. The generic version has no actual implementation +// on purpose. +// +// If you get errors about return values related to this method then +// you have forgotten to include UNITY_REGISTER_INTERFACE_GUID with +// your interface, or it is not visible at some point when you are +// trying to retrieve or add an interface. +template +inline const UnityInterfaceGUID GetUnityInterfaceGUID(); + +// This is the macro you provide in your public interface header +// outside of a namespace to allow us to map between type and GUID +// without the user having to worry about it when attempting to +// add or retrieve and interface from the registry. + #define UNITY_REGISTER_INTERFACE_GUID(HASHH, HASHL, TYPE) \ + template<> \ + inline const UnityInterfaceGUID GetUnityInterfaceGUID() \ + { \ + return UnityInterfaceGUID(HASHH,HASHL); \ + } + +// Same as UNITY_REGISTER_INTERFACE_GUID but allows the interface to live in +// a particular namespace. As long as the namespace is visible at the time you call +// GetUnityInterfaceGUID< INTERFACETYPE >() or you explicitly qualify it in the template +// calls this will work fine, only the macro here needs to have the additional parameter + #define UNITY_REGISTER_INTERFACE_GUID_IN_NAMESPACE(HASHH, HASHL, TYPE, NAMESPACE) \ + const UnityInterfaceGUID TYPE##_GUID(HASHH, HASHL); \ + template<> \ + inline const UnityInterfaceGUID GetUnityInterfaceGUID< NAMESPACE :: TYPE >() \ + { \ + return UnityInterfaceGUID(HASHH,HASHL); \ + } + +// These macros allow for C compatibility in user code. + #define UNITY_GET_INTERFACE_GUID(TYPE) GetUnityInterfaceGUID< TYPE >() + + +#else + #define UNITY_DECLARE_INTERFACE(NAME) \ + typedef struct NAME NAME; \ + struct NAME + +// NOTE: This has the downside that one some compilers it will not get stripped from all compilation units that +// can see a header containing this constant. However, it's only for C compatibility and thus should have +// minimal impact. + #define UNITY_REGISTER_INTERFACE_GUID(HASHH, HASHL, TYPE) \ + const UnityInterfaceGUID TYPE##_GUID = {HASHH, HASHL}; + +// In general namespaces are going to be a problem for C code any interfaces we expose in a namespace are +// not going to be usable from C. + #define UNITY_REGISTER_INTERFACE_GUID_IN_NAMESPACE(HASHH, HASHL, TYPE, NAMESPACE) + +// These macros allow for C compatibility in user code. + #define UNITY_GET_INTERFACE_GUID(TYPE) TYPE##_GUID +#endif + +// Using this in user code rather than INTERFACES->Get() will be C compatible for those places in plugins where +// this may be needed. Unity code itself does not need this. +#define UNITY_GET_INTERFACE(INTERFACES, TYPE) (TYPE*)INTERFACES->GetInterfaceSplit (UNITY_GET_INTERFACE_GUID(TYPE).m_GUIDHigh, UNITY_GET_INTERFACE_GUID(TYPE).m_GUIDLow); + + +#ifdef __cplusplus +struct IUnityInterface +{ +}; +#else +typedef void IUnityInterface; +#endif + + +typedef struct IUnityInterfaces +{ + // Returns an interface matching the guid. + // Returns nullptr if the given interface is unavailable in the active Unity runtime. + IUnityInterface* (UNITY_INTERFACE_API * GetInterface)(UnityInterfaceGUID guid); + + // Registers a new interface. + void(UNITY_INTERFACE_API * RegisterInterface)(UnityInterfaceGUID guid, IUnityInterface * ptr); + + // Split APIs for C + IUnityInterface* (UNITY_INTERFACE_API * GetInterfaceSplit)(unsigned long long guidHigh, unsigned long long guidLow); + void(UNITY_INTERFACE_API * RegisterInterfaceSplit)(unsigned long long guidHigh, unsigned long long guidLow, IUnityInterface * ptr); + +#ifdef __cplusplus + // Helper for GetInterface. + template + INTERFACE* Get() + { + return static_cast(GetInterface(GetUnityInterfaceGUID())); + } + + // Helper for RegisterInterface. + template + void Register(IUnityInterface* ptr) + { + RegisterInterface(GetUnityInterfaceGUID(), ptr); + } + +#endif +} IUnityInterfaces; + + +#ifdef __cplusplus +extern "C" { +#endif + +// If exported by a plugin, this function will be called when the plugin is loaded. +void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces); +// If exported by a plugin, this function will be called when the plugin is about to be unloaded. +void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginUnload(); + +#ifdef __cplusplus +} +#endif + +struct RenderSurfaceBase; +typedef struct RenderSurfaceBase* UnityRenderBuffer; +typedef unsigned int UnityTextureID; diff --git a/Native~/Unity/IUnityLog.h b/Native~/Unity/IUnityLog.h new file mode 100644 index 0000000..c537585 --- /dev/null +++ b/Native~/Unity/IUnityLog.h @@ -0,0 +1,37 @@ +// Unity Native Plugin API copyright © 2015 Unity Technologies ApS +// +// Licensed under the Unity Companion License for Unity - dependent projects--see[Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License). +// +// Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.Please review the license for details on these and other terms and conditions. + +#pragma once +#include "IUnityInterface.h" + +/// The type of the log message +enum UnityLogType +{ + /// UnityLogType used for Errors. + kUnityLogTypeError = 0, + /// UnityLogType used for Warnings. + kUnityLogTypeWarning = 2, + /// UnityLogType used for regular log messages. + kUnityLogTypeLog = 3, + /// UnityLogType used for Exceptions. + kUnityLogTypeException = 4, +}; + +#define UNITY_WRAP_CODE(CODE_) do { CODE_; } while (0) +#define UNITY_LOG(PTR_, MSG_) UNITY_WRAP_CODE((PTR_)->Log(kUnityLogTypeLog, MSG_, __FILE__, __LINE__)) +#define UNITY_LOG_WARNING(PTR_, MSG_) UNITY_WRAP_CODE((PTR_)->Log(kUnityLogTypeWarning, MSG_, __FILE__, __LINE__)) +#define UNITY_LOG_ERROR(PTR_, MSG_) UNITY_WRAP_CODE((PTR_)->Log(kUnityLogTypeError, MSG_, __FILE__, __LINE__)) + +UNITY_DECLARE_INTERFACE(IUnityLog) +{ + // Writes information message to Unity log. + // \param type type log channel type which defines importance of the message. + // \param message UTF-8 null terminated string. + // \param fileName UTF-8 null terminated string with file name of the point where message is generated. + // \param fileLine integer file line number of the point where message is generated. + void(UNITY_INTERFACE_API * Log)(UnityLogType type, const char* message, const char *fileName, const int fileLine); +}; +UNITY_REGISTER_INTERFACE_GUID(0x9E7507fA5B444D5DULL, 0x92FB979515EA83FCULL, IUnityLog) diff --git a/Native~/Unity/LICENSE.md b/Native~/Unity/LICENSE.md new file mode 100644 index 0000000..7b839ca --- /dev/null +++ b/Native~/Unity/LICENSE.md @@ -0,0 +1,5 @@ +Unity Native Plugin API copyright © 2015 Unity Technologies ApS + +Licensed under the Unity Companion License for Unity-dependent projects--see [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License). + +Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions. diff --git a/Native~/main.cpp b/Native~/main.cpp new file mode 100644 index 0000000..b5b56b3 --- /dev/null +++ b/Native~/main.cpp @@ -0,0 +1,163 @@ +#include "PublicAPI.h" + +#include "Unity/IUnityInterface.h" +#include "Unity/IUnityGraphics.h" + +#include "Disguise/d3renderstream.h" + +#include "Events.h" +#include "Logger.h" +#include "DX12System.h" +#include "DX12Texture.h" + +using namespace NativeRenderingPlugin; + +static IUnityInterfaces* s_UnityInterfaces = nullptr; +static IUnityGraphics* s_Graphics = nullptr; +static int s_BaseEventId = 0; + +// Unity plugin load event +extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API +UnityPluginLoad(IUnityInterfaces * unityInterfaces) +{ + s_UnityInterfaces = unityInterfaces; + s_Graphics = unityInterfaces->Get(); + s_Logger = std::make_unique(unityInterfaces); + + if (s_Graphics != nullptr) + { + s_Graphics->RegisterDeviceEventCallback(OnGraphicsDeviceEvent); + + s_BaseEventId = s_Graphics->ReserveEventIDRange((int)EventID::MAX); + } + + // Run OnGraphicsDeviceEvent(initialize) manually on plugin load + // to not miss the event in case the graphics device is already initialized + OnGraphicsDeviceEvent(kUnityGfxDeviceEventInitialize); +} + +// Unity plugin unload event +extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API +UnityPluginUnload() +{ + if (s_Graphics != nullptr) + { + s_Graphics->UnregisterDeviceEventCallback(OnGraphicsDeviceEvent); + } +} + +// Always called on the main thread, even by IUnityGraphics +static void UNITY_INTERFACE_API +OnGraphicsDeviceEvent(UnityGfxDeviceEventType eventType) +{ + switch (eventType) + { + case kUnityGfxDeviceEventInitialize: + { + if (s_Graphics != nullptr && s_Graphics->GetRenderer() == kUnityGfxRendererD3D12) + { + s_DX12System = std::make_unique(s_UnityInterfaces); + } + + break; + } + case kUnityGfxDeviceEventShutdown: + { + s_DX12System.reset(); + + break; + } + case kUnityGfxDeviceEventBeforeReset: + { + break; + } + case kUnityGfxDeviceEventAfterReset: + { + break; + } + }; +} + +// Render event (via IssuePluginEvent) callback +static void UNITY_INTERFACE_API +OnRenderEvent(int eventID, void* eventData) +{ + eventID = eventID - s_BaseEventId; + + if (eventID == (int)NativeRenderingPlugin::EventID::INPUT_IMAGE) + { + auto data = reinterpret_cast(eventData); + auto result = data->Execute(); + if (result != RS_ERROR_SUCCESS) + { + s_Logger->LogError("EventID::INPUT_IMAGE error", result); + } + } + else if (eventID == (int)NativeRenderingPlugin::EventID::SEND_FRAME) + { + auto data = reinterpret_cast(eventData); + auto result = data->Execute(); + if (result != RS_ERROR_SUCCESS) + { + s_Logger->LogError("EventID::SEND_FRAME error", result); + } + } + else + { + s_Logger->LogError("Unsupported event ID", eventID); + } +} + +extern "C" UnityRenderingEventAndData UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API +GetRenderEventCallback() +{ + return OnRenderEvent; +} + +extern "C" bool UNITY_INTERFACE_EXPORT +IsInitialized() +{ + return s_DX12System != nullptr && s_DX12System->IsInitialized(); +} + +extern "C" int UNITY_INTERFACE_EXPORT +GetBaseEventID() +{ + return s_BaseEventId; +} + +extern "C" UNITY_INTERFACE_EXPORT void* +GetD3D12Device() +{ + if (!IsInitialized()) + { + s_Logger->LogError("GetD3D12Device: called before successful initialization."); + return nullptr; + } + + return s_DX12System->GetDevice(); +} + +extern "C" UNITY_INTERFACE_EXPORT void* +GetD3D12CommandQueue() +{ + if (!IsInitialized()) + { + s_Logger->LogError("GetD3D12CommandQueue: called before successful initialization."); + return nullptr; + } + + return s_DX12System->GetCommandQueue(); +} + +extern "C" UNITY_INTERFACE_EXPORT void* +CreateNativeTexture(const LPCWSTR name, int width, int height, RSPixelFormat pixelFormat, bool sRGB) +{ + if (!IsInitialized()) + { + s_Logger->LogError("CreateNativeTexture: called before successful initialization."); + return nullptr; + } + + return CreateTexture(name, width, height, pixelFormat, sRGB); +} diff --git a/Plugin.meta b/Plugin.meta new file mode 100644 index 0000000..56d289b --- /dev/null +++ b/Plugin.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0a3f56a8310821b4d9b0fe84007572fc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Plugin/NativeRenderingPlugin.dll b/Plugin/NativeRenderingPlugin.dll new file mode 100644 index 0000000..1a873d5 Binary files /dev/null and b/Plugin/NativeRenderingPlugin.dll differ diff --git a/Plugin/NativeRenderingPlugin.dll.meta b/Plugin/NativeRenderingPlugin.dll.meta new file mode 100644 index 0000000..df75f55 --- /dev/null +++ b/Plugin/NativeRenderingPlugin.dll.meta @@ -0,0 +1,63 @@ +fileFormatVersion: 2 +guid: 944cfc645d7504443add8c71712c33b8 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Editor: 0 + Exclude Linux64: 0 + Exclude OSXUniversal: 0 + Exclude Win: 1 + Exclude Win64: 0 + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: x86_64 + DefaultValueInitialized: true + OS: Windows + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: x86_64 + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b71d17 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# RenderStream Unity Plugin + +![alt text](https://download.disguise.one/media/6921/unity.png) + +For more info please refer to our [RenderStream and Unity Help page](https://help.disguise.one/Content/Configuring/Render-engines/RenderStream-Unity.htm) + +A **Demo Unity Project** can be found on the [disguise Resources page](https://download.disguise.one/#resources) + +## Installing the RenderStream Unity Plugin + +Install this package using the [Package Manager](https://docs.unity3d.com/Manual/upm-ui.html), using one of these methods: +* From a [Git URL](https://docs.unity3d.com/Manual/upm-ui-giturl.html) +* Clone this repository and install from a [local folder](https://docs.unity3d.com/Manual/upm-ui-local.html) + +## Using the RenderStream Unity Plugin + +The act of adding this package to your Unity project, is enough to enable RenderStream in your built executable. + +More control can be added using the included Disguise RenderStream components: + +* **To enable control of GameObject(s) properties**, attach a Disguise RenderStream > **Remote Parameters** component to the appropriate game object for remote parameter control. + * Note: you can enable/disable the exact GameObject properties using the List editor in the Unity Inspector. +* **To add designer timeline control**, attach a Disguise RenderStream > **Time Control** component to a Playable Director + +## Building a RenderStream asset for Disguise Designer + +To use your Unity Project with disguise designer, you build an executable and import it into your designer project. To do this: +* Ensure Build Settings are set to build a **Windows x86_64** executable +* Copy the build folder to your **RenderStream Projects** folder +* In Designer, set up a new RenderStream Layer and point the Asset to your built executable. + +## Optional: Cluster Rendering + +If you are running RenderStream in a cluster and you sychronization between nodes, you will also need to install the [Cluster Display](https://github.com/Unity-Technologies/ClusterDisplay) package, using one of these methods: + +* From the following git URL: https://github.com/Unity-Technologies/ClusterDisplay.git?path=/source/com.unity.cluster-display +* Clone the repository and install the package locally from `path/to/repo/source/com.unity.cluster-display/package.json` + +The cluster is composed of one (1) emitter/controller plus one or more repeaters/followers. For example, if your cluster has 4 render nodes, then it has 1 emitter/controller + 3 repeaters/followers. It is important that you inform Unity of the number of repeaters in the cluster: + +* Before you build the project, can specify the number of repeaters in **Project Settings | Cluster Display** > **Repeater Count** (you can leave all other "Play in Editor" settings alone). +![Cluster settings](Docs~/images/cluster-settings.png) +* Optionally, after you've built the project, you can also specify (or override) the repeater/follower count in Designer by adding the following custom arguments in the "Engine Settings" of the Cluster Workload asset: `-followers`, `N` where `N` is the number of repeaters/followers in the cluster (1 less than the number of nodes). +![Custom arguments](Docs~/images/custom-arguments.png) + +## Notes: + +* The Scene Origin in Designer corresponds to the initial position of the camera in the Unity Scene. +* Firewall settings can interfere with transport streams and/or cluster communication. You may see a Windows security dialog pop-up the first time you run the asset. + * Click Enable / Allow on this Windows pop-up to allow RenderStream communication to/from designer. +* If the firewall is the problem check your outbound firewall rules on the Unity machine, and the inbound firewall rules on the disguise machine for either software being specifically blocked. diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..02774e5 --- /dev/null +++ b/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ae53892d7c6789345bf4ac49b9de3d0f +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources.meta b/Resources.meta new file mode 100644 index 0000000..1b1cd3e --- /dev/null +++ b/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8f4d9152f8a0064418b1c17ba965b206 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/UnityDebugWindowPresenter.prefab b/Resources/UnityDebugWindowPresenter.prefab new file mode 100644 index 0000000..ba5eb6f --- /dev/null +++ b/Resources/UnityDebugWindowPresenter.prefab @@ -0,0 +1,173 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &360651681370543317 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 360651681370543319} + - component: {fileID: 360651681370543318} + m_Layer: 0 + m_Name: Output Presenter + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &360651681370543319 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 360651681370543317} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 360651681440703413} + m_RootOrder: -1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &360651681370543318 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 360651681370543317} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a76b687eeb2f3eb4ab0f3cdb0e21cb99, type: 3} + m_Name: + m_EditorClassIdentifier: + m_source: {fileID: 0} + m_sourceColorSpace: -1 + m_strategy: 5 + m_autoFlipY: 1 + m_clearScreen: 1 + m_mode: 0 +--- !u!1 &360651681440703410 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 360651681440703413} + - component: {fileID: 360651681440703412} + - component: {fileID: 360651681440703411} + m_Layer: 0 + m_Name: UnityDebugWindowPresenter + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &360651681440703413 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 360651681440703410} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 360651681370543319} + - {fileID: 360651682882557475} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &360651681440703412 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 360651681440703410} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2c135780edf6e674ca031a94a2e3b4ee, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Selected: 0 + m_ResizeStrategy: 3 + m_OutputPresenter: {fileID: 360651681370543318} + m_InputPresenter: {fileID: 360651682882557474} +--- !u!114 &360651681440703411 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 360651681440703410} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: af5276befa899474cb1ba07f3cd321ba, type: 3} + m_Name: + m_EditorClassIdentifier: + exposedObject: {fileID: 360651681440703412} + prefix: unity-debug-window-presenter + fields: + - exposed: 1 + fieldName: Selected + groupName: Unity Debug Window Presenter + - exposed: 1 + fieldName: Resize Strategy + groupName: Unity Debug Window Presenter +--- !u!1 &360651682882557473 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 360651682882557475} + - component: {fileID: 360651682882557474} + m_Layer: 0 + m_Name: Input Presenter + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &360651682882557475 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 360651682882557473} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 360651681440703413} + m_RootOrder: -1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &360651682882557474 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 360651682882557473} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e11750ce543afca48962fc383425bc33, type: 3} + m_Name: + m_EditorClassIdentifier: + m_source: {fileID: 0} + m_sourceColorSpace: -1 + m_strategy: 6 + m_autoFlipY: 1 + m_clearScreen: 1 diff --git a/Resources/UnityDebugWindowPresenter.prefab.meta b/Resources/UnityDebugWindowPresenter.prefab.meta new file mode 100644 index 0000000..b5ae567 --- /dev/null +++ b/Resources/UnityDebugWindowPresenter.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8aedf4d02b750c24aada38b8e2dce4a5 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime.meta b/Runtime.meta new file mode 100644 index 0000000..5d13e4b --- /dev/null +++ b/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: afd5d953d0c42fe488518957aa3b9515 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/AssemblyInfo.cs b/Runtime/AssemblyInfo.cs new file mode 100644 index 0000000..a714a51 --- /dev/null +++ b/Runtime/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Disguise.RenderStream.Editor")] \ No newline at end of file diff --git a/Runtime/AssemblyInfo.cs.meta b/Runtime/AssemblyInfo.cs.meta new file mode 100644 index 0000000..e9c17fc --- /dev/null +++ b/Runtime/AssemblyInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d37ec95dffed89442b0b3792e5ebe599 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DisguiseUnityRenderStream/Disguise.RenderStream.asmdef b/Runtime/Disguise.RenderStream.asmdef similarity index 50% rename from DisguiseUnityRenderStream/Disguise.RenderStream.asmdef rename to Runtime/Disguise.RenderStream.asmdef index d485f30..d7cc743 100644 --- a/DisguiseUnityRenderStream/Disguise.RenderStream.asmdef +++ b/Runtime/Disguise.RenderStream.asmdef @@ -1,8 +1,11 @@ { "name": "Disguise.RenderStream", + "rootNamespace": "", "references": [ + "Disguise.RenderStream.PipelineAbstraction", "Unity.RenderPipelines.Core.Runtime", - "Unity.RenderPipelines.HighDefinition.Runtime" + "Unity.ClusterDisplay.Runtime", + "Unity.ClusterDisplay.Runtime.Shared" ], "includePlatforms": [], "excludePlatforms": [], @@ -11,6 +14,12 @@ "precompiledReferences": [], "autoReferenced": true, "defineConstraints": [], - "versionDefines": [], + "versionDefines": [ + { + "name": "com.unity.cluster-display", + "expression": "0.0.0", + "define": "ENABLE_CLUSTER_DISPLAY" + } + ], "noEngineReferences": false -} \ No newline at end of file +} diff --git a/Runtime/Disguise.RenderStream.asmdef.meta b/Runtime/Disguise.RenderStream.asmdef.meta new file mode 100644 index 0000000..a7109c8 --- /dev/null +++ b/Runtime/Disguise.RenderStream.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 23030bd001aba8144b910898f28cff19 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/DisguiseCameraCapture.cs b/Runtime/DisguiseCameraCapture.cs new file mode 100644 index 0000000..e06637a --- /dev/null +++ b/Runtime/DisguiseCameraCapture.cs @@ -0,0 +1,185 @@ +using System; +using System.Linq; +using UnityEngine; +using UnityEngine.Rendering; + +namespace Disguise.RenderStream +{ + [AddComponentMenu("")] + [RequireComponent(typeof(Camera))] + public class DisguiseCameraCapture : MonoBehaviour + { + internal CameraCapture Capture => m_capture; + + Camera m_camera; + FrameSender m_frameSender; + CameraCapture m_capture; + + CameraData m_cameraData; + bool m_newFrameData; + DisguiseRenderStream m_RenderStream; + + void Awake() + { + m_RenderStream = DisguiseRenderStream.Instance; + if (m_RenderStream == null || PluginEntry.instance.IsAvailable == false) + { + Debug.LogError("DisguiseCameraCapture: RenderStream DLL not available, capture cannot start."); + enabled = false; + return; + } + + m_cameraData = new CameraData(); + + m_camera = GetComponent(); + var stream = m_RenderStream.Streams.FirstOrDefault(x => x.Description.name == gameObject.name).Description; + m_frameSender = new FrameSender(gameObject.name, stream); + + m_capture = gameObject.AddComponent(); + m_capture.hideFlags |= HideFlags.HideAndDontSave; + + m_capture.description = m_frameSender.description; + m_capture.onCapture += OnCapture; + } + + void OnEnable() + { + m_capture.enabled = true; + } + + void OnDisable() + { + m_capture.enabled = false; + } + + void Update() + { + if (m_RenderStream == null) + return; + + m_newFrameData = m_RenderStream.HasNewFrameData && + m_frameSender != null && + m_frameSender.GetCameraData(ref m_cameraData); + + UpdateCamera( + m_camera, + m_newFrameData ? m_cameraData : null, + m_frameSender?.subRegion + ); + } + + void OnCapture(ScriptableRenderContext context, CameraCapture.Capture capture) + { + if (m_newFrameData) + { + m_frameSender?.SendFrame(context, m_RenderStream.LatestFrameData, m_cameraData, capture.cameraTexture); + m_newFrameData = false; + } + } + + static void UpdateCamera(Camera cam, CameraData? cameraData, Rect? cameraSubRegion) + { + var transform = cam.transform; + var cameraAspect = cam.aspect; + var lensShift = new Vector2(0.0f, 0.0f); + + if (cameraData is { } data) + { + cameraAspect = data.sensorX / data.sensorY; + if (data.cameraHandle != 0) // If no camera, only set aspect + { + transform.localPosition = new Vector3(data.x, data.y, data.z); + transform.localRotation = Quaternion.Euler(new Vector3(-data.rx, data.ry, -data.rz)); + cam.nearClipPlane = data.nearZ; + cam.farClipPlane = data.farZ; + + if (data.orthoWidth > 0.0f) // Use an orthographic camera + { + cam.orthographic = true; + cam.orthographicSize = 0.5f * data.orthoWidth / cameraAspect; + transform.localPosition = new Vector3(data.x, data.y, data.z); + transform.localRotation = Quaternion.Euler(new Vector3(-data.rx, data.ry, -data.rz)); + } + else // Perspective projection, use camera lens properties + { + cam.usePhysicalProperties = true; + cam.sensorSize = new Vector2(data.sensorX, data.sensorY); + cam.focalLength = data.focalLength; + lensShift = new Vector2(-data.cx, data.cy); + } + } + } + else if (cameraSubRegion.HasValue) + { + var subRegion = cameraSubRegion.Value; + + // By default aspect is resolution aspect. We need to undo the effect of the subregion on this to get the whole image aspect. + cameraAspect = cam.aspect * (subRegion.height / subRegion.width); + } + + // Clip to correct subregion and calculate projection matrix + if (cameraSubRegion.HasValue) + { + var subRegion = cameraSubRegion.Value; + + float imageHeight, imageWidth; + if (cam.orthographic) + { + imageHeight = 2.0f * cam.orthographicSize; + imageWidth = cameraAspect * imageHeight; + } + else + { + float fovV = cam.fieldOfView * Mathf.Deg2Rad; + float fovH = Camera.VerticalToHorizontalFieldOfView(cam.fieldOfView, cameraAspect) * Mathf.Deg2Rad; + imageWidth = 2.0f * (float)Math.Tan(0.5f * fovH); + imageHeight = 2.0f * (float)Math.Tan(0.5f * fovV); + } + + float l = (-0.5f + subRegion.xMin) * imageWidth; + float r = (-0.5f + subRegion.xMax) * imageWidth; + float t = (-0.5f + 1.0f - subRegion.yMin) * imageHeight; + float b = (-0.5f + 1.0f - subRegion.yMax) * imageHeight; + + Matrix4x4 projectionMatrix; + if (cam.orthographic) + projectionMatrix = Matrix4x4.Ortho(l, r, b, t, cam.nearClipPlane, cam.farClipPlane); + else + projectionMatrix = PerspectiveOffCenter(l * cam.nearClipPlane, r * cam.nearClipPlane, b * cam.nearClipPlane, t * cam.nearClipPlane, cam.nearClipPlane, cam.farClipPlane); + + Matrix4x4 clippingTransform = Matrix4x4.Translate(new Vector3(-lensShift.x / subRegion.width, lensShift.y / subRegion.height, 0.0f)); + cam.projectionMatrix = clippingTransform * projectionMatrix; + } + } + + // From http://docs.unity3d.com/ScriptReference/Camera-projectionMatrix.html + static Matrix4x4 PerspectiveOffCenter(float left, float right, float bottom, float top, float near, float far) + { + float x = 2.0F * near / (right - left); + float y = 2.0F * near / (top - bottom); + float a = (right + left) / (right - left); + float b = (top + bottom) / (top - bottom); + float c = -(far + near) / (far - near); + float d = -(2.0F * far * near) / (far - near); + float e = -1.0F; + Matrix4x4 m = new Matrix4x4(); + m[0, 0] = x; + m[0, 1] = 0; + m[0, 2] = a; + m[0, 3] = 0; + m[1, 0] = 0; + m[1, 1] = y; + m[1, 2] = b; + m[1, 3] = 0; + m[2, 0] = 0; + m[2, 1] = 0; + m[2, 2] = c; + m[2, 3] = d; + m[3, 0] = 0; + m[3, 1] = 0; + m[3, 2] = e; + m[3, 3] = 0; + return m; + } + } +} diff --git a/Runtime/DisguiseCameraCapture.cs.meta b/Runtime/DisguiseCameraCapture.cs.meta new file mode 100644 index 0000000..62e812e --- /dev/null +++ b/Runtime/DisguiseCameraCapture.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a6ed3a420b36f76488ebde7d6ca53004 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/DisguiseFramerateManager.cs b/Runtime/DisguiseFramerateManager.cs new file mode 100644 index 0000000..d71b57b --- /dev/null +++ b/Runtime/DisguiseFramerateManager.cs @@ -0,0 +1,42 @@ +using UnityEngine; + +namespace Disguise.RenderStream +{ + public static class DisguiseFramerateManager + { + const int k_FrameRateUnlimited = -1; + + public static bool FrameRateIsLimited => Application.targetFrameRate >= 0; + + public static bool VSyncIsEnabled => QualitySettings.vSyncCount > 0; + + static bool s_WarnedVSync; + static bool s_WarnedFrameRate; + + [RuntimeInitializeOnLoadMethod] + public static void Initialize() + { + RemoveFrameLimit(); + } + + public static void Update() + { + if (!s_WarnedVSync && VSyncIsEnabled) + { + Debug.LogWarning($"{nameof(DisguiseFramerateManager)}: {nameof(QualitySettings)}.{nameof(QualitySettings.vSyncCount)} is enabled and may affect performance."); + s_WarnedVSync = true; + } + + if (!s_WarnedFrameRate && FrameRateIsLimited) + { + Debug.LogWarning($"{nameof(DisguiseFramerateManager)}: {nameof(Application)}.{nameof(Application.targetFrameRate)} is limiting framerate and may affect performance."); + s_WarnedFrameRate = true; + } + } + + static void RemoveFrameLimit() + { + Application.targetFrameRate = k_FrameRateUnlimited; + } + } +} diff --git a/Runtime/DisguiseFramerateManager.cs.meta b/Runtime/DisguiseFramerateManager.cs.meta new file mode 100644 index 0000000..807886a --- /dev/null +++ b/Runtime/DisguiseFramerateManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ed283af92fef7cd4fa345b8da6a10b79 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/DisguisePluginDataTypes.cs b/Runtime/DisguisePluginDataTypes.cs new file mode 100644 index 0000000..127aed2 --- /dev/null +++ b/Runtime/DisguisePluginDataTypes.cs @@ -0,0 +1,341 @@ +using System; +using System.Runtime.InteropServices; + +namespace Disguise.RenderStream +{ + // d3renderstream/d3renderstream.h + using StreamHandle = UInt64; + using CameraHandle = UInt64; + + delegate void logger_t(string message); + + public enum RS_ERROR : UInt32 + { + RS_ERROR_SUCCESS = 0, + + // Core is not initialised + RS_NOT_INITIALISED, + + // Core is already initialised + RS_ERROR_ALREADYINITIALISED, + + // Given handle is invalid + RS_ERROR_INVALIDHANDLE, + + // Maximum number of frame senders have been created + RS_MAXSENDERSREACHED, + + RS_ERROR_BADSTREAMTYPE, + + RS_ERROR_NOTFOUND, + + RS_ERROR_INCORRECTSCHEMA, + + RS_ERROR_INVALID_PARAMETERS, + + RS_ERROR_BUFFER_OVERFLOW, + + RS_ERROR_TIMEOUT, + + RS_ERROR_STREAMS_CHANGED, + + RS_ERROR_INCOMPATIBLE_VERSION, + + RS_ERROR_FAILED_TO_GET_DXDEVICE_FROM_RESOURCE, + + RS_ERROR_FAILED_TO_INITIALISE_GPGPU, + + RS_ERROR_QUIT, + + RS_ERROR_UNSPECIFIED + } + + public enum RSPixelFormat : UInt32 + { + RS_FMT_INVALID, + + RS_FMT_BGRA8, + RS_FMT_BGRX8, + + RS_FMT_RGBA32F, + + RS_FMT_RGBA16, + + RS_FMT_RGBA8, + RS_FMT_RGBX8, + } + + public enum SenderFrameType : UInt32 + { + RS_FRAMETYPE_HOST_MEMORY = 0x00000000, + RS_FRAMETYPE_DX11_TEXTURE, + RS_FRAMETYPE_DX12_TEXTURE, + RS_FRAMETYPE_OPENGL_TEXTURE, + RS_FRAMETYPE_UNKNOWN + } + + public enum UseDX12SharedHeapFlag : UInt32 + { + RS_DX12_USE_SHARED_HEAP_FLAG, + RS_DX12_DO_NOT_USE_SHARED_HEAP_FLAG + } + + // Bitmask flags + public enum FRAMEDATA_FLAGS : UInt32 + { + FRAMEDATA_NO_FLAGS = 0, + FRAMEDATA_RESET = 1 + } + + public enum REMOTEPARAMETER_FLAGS : UInt32 + { + REMOTEPARAMETER_NO_FLAGS = 0, + REMOTEPARAMETER_NO_SEQUENCE = 1 + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct D3TrackingData + { + public float virtualZoomScale; + public byte virtualReprojectionRequired; + public float xRealCamera, yRealCamera, zRealCamera; + public float rxRealCamera, ryRealCamera, rzRealCamera; + } // Tracking data required by d3 but not used to render content + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct CameraData + { + public StreamHandle streamHandle; + public CameraHandle cameraHandle; + public float x, y, z; + public float rx, ry, rz; + public float focalLength; + public float sensorX, sensorY; + public float cx, cy; + public float nearZ, farZ; + public float orthoWidth; // If > 0, an orthographic camera should be used + public D3TrackingData d3Tracking; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public readonly struct FrameData + { + public readonly double tTracked; + public readonly double localTime; + public readonly double localTimeDelta; + public readonly UInt32 frameRateNumerator; + public readonly UInt32 frameRateDenominator; + public readonly UInt32 flags; // FRAMEDATA_FLAGS + public readonly UInt32 scene; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct CameraResponseData + { + public double tTracked; + public CameraData camera; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct FrameResponseData + { + public /*CameraResponseData**/ IntPtr cameraData; + public UInt64 schemaHash; + public UInt64 parameterDataSize; + public IntPtr parameterData; + public UInt32 textDataCount; + public /*const char***/ IntPtr textData; + } + + [StructLayout(LayoutKind.Explicit)] + public /*union*/ struct SenderFrameTypeData + { + // struct HostMemoryData + [FieldOffset(0)] + public /*uint8_t**/ IntPtr host_data; + [FieldOffset(8)] + public UInt32 host_stride; + // struct Dx11Data + [FieldOffset(0)] + public /*struct ID3D11Resource**/ IntPtr dx11_resource; + // struct Dx12Data + [FieldOffset(0)] + public /*struct ID3D12Resource**/ IntPtr dx12_resource; + // struct OpenGlData + [FieldOffset(0)] + public /*GLuint**/ UInt32 gl_texture; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct FrameRegion + { + public UInt32 xOffset; + public UInt32 yOffset; + public UInt32 width; + public UInt32 height; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct ProjectionClipping + { + public float left; + public float right; + public float top; + public float bottom; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct StreamDescription + { + public StreamHandle handle; + [MarshalAs(UnmanagedType.LPStr)] + public string channel; + public UInt64 mappingId; + public Int32 iViewpoint; + [MarshalAs(UnmanagedType.LPStr)] + public string name; + public UInt32 width; + public UInt32 height; + public RSPixelFormat format; + public ProjectionClipping clipping; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct StreamDescriptions + { + public UInt32 nStreams; + public /*StreamDescription**/ IntPtr streams; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct ProfilingEntry + { + public string name; + public float value; + } + + public enum RemoteParameterType : UInt32 + { + RS_PARAMETER_NUMBER, + RS_PARAMETER_IMAGE, + RS_PARAMETER_POSE, // 4x4 TR matrix + RS_PARAMETER_TRANSFORM, // 4x4 TRS matrix + RS_PARAMETER_TEXT, + } + + public enum RemoteParameterDmxType : UInt32 + { + RS_DMX_DEFAULT, + RS_DMX_8, + RS_DMX_16_BE, + } + + [StructLayout(LayoutKind.Explicit)] + public /*union*/ struct RemoteParameterTypeDefaults + { + [FieldOffset(0)] + public float numerical_min; + [FieldOffset(4)] + public float numerical_max; + [FieldOffset(8)] + public float numerical_step; + [FieldOffset(12)] + public float numerical_defaultValue; + [FieldOffset(0)] + public /*const char**/ IntPtr text_defaultValue; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct ImageFrameData + { + public UInt32 width; + public UInt32 height; + public RSPixelFormat format; + public Int64 imageId; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct RemoteParameter + { + [MarshalAs(UnmanagedType.LPStr)] + public string group; + [MarshalAs(UnmanagedType.LPStr)] + public string displayName; + [MarshalAs(UnmanagedType.LPStr)] + public string key; + public RemoteParameterType type; + public RemoteParameterTypeDefaults defaults; + public UInt32 nOptions; + public /*const char***/ IntPtr options; + + public Int32 dmxOffset; // DMX channel offset or auto (-1) + public RemoteParameterDmxType dmxType; + public UInt32 flags; // REMOTEPARAMETER_FLAGS + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct RemoteParameters + { + [MarshalAs(UnmanagedType.LPStr)] + public string name; + public UInt32 nParameters; + public /*RemoteParameter**/ IntPtr parameters; + public UInt64 hash; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct Scenes + { + public UInt32 nScenes; + public /*RemoteParameters**/ IntPtr scenes; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct Channels + { + public UInt32 nChannels; + public /*const char***/ IntPtr channels; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct Schema + { + [MarshalAs(UnmanagedType.LPStr)] + public string engineName; + [MarshalAs(UnmanagedType.LPStr)] + public string engineVersion; + [MarshalAs(UnmanagedType.LPStr)] + public string info; + public Channels channels; + public Scenes scenes; + } + + public class ManagedRemoteParameter + { + public string group; + public string displayName; + public string key; + public RemoteParameterType type; + public float min; + public float max; + public float step; + public object defaultValue; + public string[] options = { }; + + public Int32 dmxOffset; + public RemoteParameterDmxType dmxType; + } + + public class ManagedRemoteParameters + { + public string name; + public ManagedRemoteParameter[] parameters = { }; + public UInt64 hash; + } + + public class ManagedSchema + { + public string[] channels = { }; + public ManagedRemoteParameters[] scenes = { }; + } +} \ No newline at end of file diff --git a/Runtime/DisguisePluginDataTypes.cs.meta b/Runtime/DisguisePluginDataTypes.cs.meta new file mode 100644 index 0000000..3467ae1 --- /dev/null +++ b/Runtime/DisguisePluginDataTypes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6c3719d40c0497943ababa8fd469d649 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DisguiseUnityRenderStream/DisguiseRemoteParameters.cs b/Runtime/DisguiseRemoteParameters.cs similarity index 82% rename from DisguiseUnityRenderStream/DisguiseRemoteParameters.cs rename to Runtime/DisguiseRemoteParameters.cs index 0e810cc..eb811f3 100644 --- a/DisguiseUnityRenderStream/DisguiseRemoteParameters.cs +++ b/Runtime/DisguiseRemoteParameters.cs @@ -62,7 +62,11 @@ void OnValidate() { do { - displayNames.Add(property.displayName); + MemberInfo _ = GetMemberInfo(property, out var isPublic); + if (isPublic) + { + displayNames.Add(property.displayName); + } } while (property.NextVisible(false)); } @@ -135,15 +139,15 @@ public List exposedParameters() nesting.Push(previousName); for (; depth > property.depth; --depth) nesting.Pop(); - string propertyPath = property.propertyPath; - MemberInfo info = GetMemberInfoFromPropertyPath(propertyPath); - if (info == null && propertyPath.StartsWith("m_")) + + MemberInfo info = GetMemberInfo(property, out var isPublic); + if (!isPublic) { - string modifiedPropertyPath = char.ToLower(propertyPath[2]) + propertyPath.Substring(3); - info = GetMemberInfoFromPropertyPath(modifiedPropertyPath); - if (info != null) - propertyPath = modifiedPropertyPath; + continue; } + + string propertyPath = info?.Name; + HeaderAttribute header = info != null ? info.GetCustomAttributes(typeof(HeaderAttribute), true).FirstOrDefault() as HeaderAttribute : null; RangeAttribute range = info != null ? info.GetCustomAttributes(typeof(RangeAttribute), true).FirstOrDefault() as RangeAttribute : null; MinAttribute min = info != null ? info.GetCustomAttributes(typeof(MinAttribute), true).FirstOrDefault() as MinAttribute : null; @@ -234,6 +238,16 @@ public List exposedParameters() parameters.Add(createField(group, property.displayName, prefix + " " + propertyPath, RemoteParameterType.RS_PARAMETER_NUMBER, "w", "w", min != null ? min.min : (range != null ? range.min : -1.0f), range != null ? range.max : +1.0f, 0.001f, v.w, new string[0])); } + else if (property.propertyType == UnityEditor.SerializedPropertyType.Quaternion) + { + Vector3 v = property.quaternionValue.eulerAngles; + parameters.Add(createField(group, property.displayName, prefix + " " + propertyPath, RemoteParameterType.RS_PARAMETER_NUMBER, "x", "x", + -360f, +360f, 0.1f, v.x, new string[0])); + parameters.Add(createField(group, property.displayName, prefix + " " + propertyPath, RemoteParameterType.RS_PARAMETER_NUMBER, "y", "y", + -360f, +360f, 0.1f, v.y, new string[0])); + parameters.Add(createField(group, property.displayName, prefix + " " + propertyPath, RemoteParameterType.RS_PARAMETER_NUMBER, "z", "z", + -360f, +360f, 0.1f, v.z, new string[0])); + } else if (property.propertyType == UnityEditor.SerializedPropertyType.Color) { Color v = property.colorValue; @@ -292,16 +306,47 @@ public List exposedParameters() } return parameters; } + + MemberInfo GetMemberInfo(UnityEditor.SerializedProperty property, out bool isPublic) + { + string propertyPath = property.propertyPath; + MemberInfo info = GetMemberInfoFromPropertyPath(propertyPath); + if (info == null && propertyPath.StartsWith("m_")) + { + string modifiedPropertyPath = propertyPath.Substring(2); + info = GetMemberInfoFromPropertyPath(modifiedPropertyPath, true); + } + + isPublic = info switch + { + FieldInfo fieldInfo => fieldInfo.IsPublic, + PropertyInfo propertyInfo => propertyInfo.GetSetMethod(true) != null, + _ => false + }; + + return info; + } #endif - public MemberInfo GetMemberInfoFromPropertyPath(string propertyPath) + MemberInfo GetMemberInfoFromPropertyPath(string propertyPath, bool ignoreCase = false) { + // Note on fields/properties: + // * BindingFlags.Public: DynamicSetterCache needs public access and to avoid exposing class internals + // * BindingFlags.DeclaredOnly: Avoids System.Reflection.AmbiguousMatchException when a child class member + // overshadows a parent member (ex TextMeshPro.renderer). Since we traverse the hierarchy top to bottom, + // the top-level member will be resolved. + // * BindingFlags.IgnoreCase: For secondary lookup (ex "Position" after "m_Position" failed we want to match both "Position" and "position"). + + var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly; + if (ignoreCase) + bindingFlags |= BindingFlags.IgnoreCase; + if (exposedObject == null) return null; MemberInfo info = null; for (Type currentType = exposedObject.GetType(); info == null && currentType != null; currentType = currentType.BaseType) { - FieldInfo fieldInfo = currentType.GetField(propertyPath, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + FieldInfo fieldInfo = currentType.GetField(propertyPath, bindingFlags); if (fieldInfo != null && (!fieldInfo.IsInitOnly || fieldInfo.FieldType.IsSubclassOf(typeof(UnityEngine.Object)))) { info = (MemberInfo)fieldInfo; @@ -311,7 +356,7 @@ public MemberInfo GetMemberInfoFromPropertyPath(string propertyPath) } for (Type currentType = exposedObject.GetType(); info == null && currentType != null; currentType = currentType.BaseType) { - PropertyInfo propertyInfo = currentType.GetProperty(propertyPath, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + PropertyInfo propertyInfo = currentType.GetProperty(propertyPath, bindingFlags); if (propertyInfo != null && propertyInfo.CanRead && (propertyInfo.CanWrite || propertyInfo.PropertyType.IsSubclassOf(typeof(UnityEngine.Object)))) { info = (MemberInfo)propertyInfo; @@ -320,4 +365,36 @@ public MemberInfo GetMemberInfoFromPropertyPath(string propertyPath) } return info; } + + public MemberInfo GetMemberInfoFromManagedParameter(ManagedRemoteParameter managedParameter) + { + var key = managedParameter.key; + + if (key.Length >= 2) + { + var underscore = key[key.Length - 2]; + var suffix = key[key.Length - 1]; + + if (underscore == '_') + { + var isComposite = suffix switch + { + 'x' => true, + 'y' => true, + 'z' => true, + 'w' => true, + 'r' => true, + 'g' => true, + 'b' => true, + 'a' => true, + _ => false + }; + + if (isComposite) + key = key.Substring(0, key.Length - 2); + } + } + + return GetMemberInfoFromPropertyPath(key.Substring(prefix.Length + 1)); + } } diff --git a/Runtime/DisguiseRemoteParameters.cs.meta b/Runtime/DisguiseRemoteParameters.cs.meta new file mode 100644 index 0000000..0c4c708 --- /dev/null +++ b/Runtime/DisguiseRemoteParameters.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: af5276befa899474cb1ba07f3cd321ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/DisguiseRenderStream.cs b/Runtime/DisguiseRenderStream.cs new file mode 100644 index 0000000..61227a8 --- /dev/null +++ b/Runtime/DisguiseRenderStream.cs @@ -0,0 +1,577 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Disguise.RenderStream.Utils; +using Unity.Collections; +using UnityEngine; +using UnityEngine.PlayerLoop; +using UnityEngine.Rendering; +using UnityEngine.SceneManagement; + +namespace Disguise.RenderStream +{ + class DisguiseRenderStream + { + public struct DisguiseStream + { + public StreamDescription Description; + public Camera Camera; + public DisguiseCameraCapture Capture; + } + + /// + /// Initializes RenderStream objects. + /// + /// + /// Ensures that the RenderStream plugin is initialized, and then creates the + /// singleton. This is called after Awake but before the scene is loaded. + /// + [RuntimeInitializeOnLoadMethod] + static void OnLoad() + { + if (Application.isEditor) + { + // No play in editor support currently + return; + } + + if (PluginEntry.instance.IsAvailable == false) + { + Debug.LogError("DisguiseRenderStream: RenderStream DLL not available"); + return; + } + + string pathToBuiltProject = ApplicationPath.GetExecutablePath(); + RS_ERROR error = PluginEntry.instance.LoadSchema(pathToBuiltProject, out var schema); + if (error != RS_ERROR.RS_ERROR_SUCCESS) + { + Debug.LogError(string.Format("DisguiseRenderStream: Failed to load schema {0}", error)); + } + +#if !ENABLE_CLUSTER_DISPLAY + Instance = new DisguiseRenderStream(schema); +#else + Instance = new DisguiseRenderStreamWithCluster(schema); +#endif + + Instance.Initialize(); + SceneManager.sceneLoaded += Instance.OnSceneLoaded; + if (SceneManager.GetActiveScene() is { isLoaded: true } scene) + { + Instance.OnSceneLoaded(scene, LoadSceneMode.Single); + } + } + + struct RenderStreamUpdate { } + struct RenderStreamGfxUpdate { } + + protected virtual void Initialize() + { + PlayerLoopExtensions.RegisterUpdate(AwaitFrame); + InitializeGfxResources(); + } + + protected void InitializeGfxResources() + { + PlayerLoopExtensions.RegisterUpdate(UpdateGfxResources); + } + + protected DisguiseRenderStream(ManagedSchema schema) + { + if (schema != null) + { + m_SceneFields = new SceneFields[schema.scenes.Length]; + } + else + { + schema = new ManagedSchema(); + schema.channels = new string[0]; + schema.scenes = new ManagedRemoteParameters[1]; + schema.scenes[0] = new ManagedRemoteParameters(); + schema.scenes[0].name = "Default"; + schema.scenes[0].parameters = new ManagedRemoteParameter[0]; + m_SceneFields = new SceneFields[schema.scenes.Length]; + CreateStreams(); + } + Schema = schema; + m_Settings = DisguiseRenderStreamSettings.GetOrCreateSettings(); + } + + void OnSceneLoaded(Scene loadedScene, LoadSceneMode mode) + { + if (DisguiseRenderStreamSettings.GetOrCreateSettings().enableUnityDebugWindowPresenter) + { + GameObject.Instantiate(UnityDebugWindowPresenter.LoadPrefab()); + } + + CreateStreams(); + int sceneIndex = 0; + DisguiseRenderStreamSettings settings = DisguiseRenderStreamSettings.GetOrCreateSettings(); + switch (settings.sceneControl) + { + case DisguiseRenderStreamSettings.SceneControl.Selection: + sceneIndex = loadedScene.buildIndex; + break; + } + DisguiseRemoteParameters[] remoteParameters = UnityEngine.Object.FindObjectsByType(typeof(DisguiseRemoteParameters), FindObjectsSortMode.None) as DisguiseRemoteParameters[]; + ManagedRemoteParameters scene = Schema.scenes[sceneIndex]; + m_SceneFields[sceneIndex] = new SceneFields{ numerical = new List(), images = new List(), texts = new List() }; + SceneFields fields = m_SceneFields[sceneIndex]; + for (int j = 0; j < scene.parameters.Length;) + { + ManagedRemoteParameter managedParameter = scene.parameters[j]; + string key = managedParameter.key; + DisguiseRemoteParameters remoteParams = Array.Find(remoteParameters, rp => key.StartsWith(rp.prefix)); + ObjectField field = new ObjectField(); + field.target = remoteParams.exposedObject; + + if (key.EndsWith("_x")) + { + string baseKey = key.Substring(0, key.Length - 2); + field.info = remoteParams.GetMemberInfoFromManagedParameter(managedParameter); + Type fieldType = field.FieldType; + if ((fieldType == typeof(Vector2) || fieldType == typeof(Vector2Int)) && + j + 1 < scene.parameters.Length && scene.parameters[j + 1].key == baseKey + "_y") + { + j += 2; + } + else if ((fieldType == typeof(Vector3) || fieldType == typeof(Vector3Int) || fieldType == typeof(Quaternion)) && + j + 2 < scene.parameters.Length && scene.parameters[j + 1].key == baseKey + "_y" && scene.parameters[j + 2].key == baseKey + "_z") + { + j += 3; + } + else if (fieldType == typeof(Vector4) && + j + 3 < scene.parameters.Length && scene.parameters[j + 1].key == baseKey + "_y" && scene.parameters[j + 2].key == baseKey + "_z" && scene.parameters[j + 3].key == baseKey + "_w") + { + j += 4; + } + else + { + field.info = null; + } + } + else if (key.EndsWith("_r")) + { + string baseKey = key.Substring(0, key.Length - 2); + field.info = remoteParams.GetMemberInfoFromManagedParameter(managedParameter); + Type fieldType = field.FieldType; + if (fieldType == typeof(Color) && + j + 3 < scene.parameters.Length && scene.parameters[j + 1].key == baseKey + "_g" && scene.parameters[j + 2].key == baseKey + "_b" && scene.parameters[j + 3].key == baseKey + "_a") + { + j += 4; + } + else + { + field.info = null; + } + } + + if (field.info == null) + { + field.info = remoteParams.GetMemberInfoFromManagedParameter(managedParameter); + ++j; + } + + if (field.info == null) + { + Debug.LogError("Unhandled remote parameter: " + key); + } + + if (field.FieldType == typeof(Texture)) + fields.images.Add(field); + else if (field.FieldType == typeof(String) || field.FieldType == typeof(String[])) + fields.texts.Add(field); + else + fields.numerical.Add(field); + } + + SceneLoaded?.Invoke(); + } + + protected void CreateStreams() + { + Debug.Log("CreateStreams"); + if (PluginEntry.instance.IsAvailable == false) + { + Debug.LogError("DisguiseRenderStream: RenderStream DLL not available"); + return; + } + + StreamDescription[] streams; + + do + { + RS_ERROR error = PluginEntry.instance.getStreams(out streams); + if (error != RS_ERROR.RS_ERROR_SUCCESS) + { + Debug.LogError(string.Format("DisguiseRenderStream: Failed to get streams {0}", error)); + return; + } + + Debug.Assert(streams != null); + if (streams.Length == 0) + { + Debug.Log("Waiting for streams..."); + Thread.Sleep(1000); + } + } while (streams.Length == 0); + + Debug.Log(string.Format("Found {0} streams", streams.Length)); + + foreach (var stream in m_Streams) + UnityEngine.Object.Destroy(stream.Camera.gameObject); + + Array.Resize(ref m_Streams, streams.Length); + + // cache the template cameras prior to instantiating our instance cameras + Camera[] templateCameras = getTemplateCameras(); + const int cullUIOnly = ~(1 << 5); + + ScratchTexture2DManager.Instance.Clear(); + + for (int i = 0; i < m_Streams.Length; ++i) + { + StreamDescription stream = streams[i]; + m_Streams[i].Description = stream; + + Camera channelCamera = GetChannelCamera(stream.channel); + GameObject cameraObject; + if (channelCamera) + { + cameraObject = UnityEngine.Object.Instantiate(channelCamera.gameObject, channelCamera.gameObject.transform); + cameraObject.name = stream.name; + } + else if (Camera.main) + { + cameraObject = UnityEngine.Object.Instantiate(Camera.main.gameObject, Camera.main.gameObject.transform); + cameraObject.name = stream.name; + } + else + { + cameraObject = new GameObject(stream.name); + cameraObject.AddComponent(); + } + + cameraObject.transform.localPosition = Vector3.zero; + cameraObject.transform.localRotation = Quaternion.identity; + + Camera camera = cameraObject.GetComponent(); + m_Streams[i].Camera = camera; + + camera.enabled = true; // ensure the camera component is enable + camera.cullingMask &= cullUIOnly; // cull the UI so RenderStream and other error messages don't render to RenderStream outputs + } + + for (int i = 0; i < m_Streams.Length; ++i) + { + var stream = m_Streams[i]; + DisguiseCameraCapture capture = stream.Camera.gameObject.GetComponent(); + if (capture == null) + capture = stream.Camera.gameObject.AddComponent(); + m_Streams[i].Capture = capture; + } + + // stop template cameras impacting performance + foreach (var templateCam in templateCameras) + { + templateCam.enabled = false; // disable the camera component on the template camera so these cameras won't render and impact performance + // we don't want to disable the game object otherwise we won't be able to find the object again to instantiate instance cameras if we get a streams changed event + } + + LatestFrameData = new FrameData(); + Awaiting = false; + + StreamsChanged?.Invoke(); + } + + protected void ProcessFrameData(in FrameData receivedFrameData) + { + if (receivedFrameData.scene >= Schema.scenes.Length) return; + + ManagedRemoteParameters spec = Schema.scenes[receivedFrameData.scene]; + SceneFields fields = m_SceneFields[receivedFrameData.scene]; + int nNumericalParameters = 0; + int nTextParameters = 0; + for (int i = 0; i < spec.parameters.Length; ++i) + { + if (spec.parameters[i].type == RemoteParameterType.RS_PARAMETER_NUMBER) + ++nNumericalParameters; + else if (spec.parameters[i].type == RemoteParameterType.RS_PARAMETER_POSE || spec.parameters[i].type == RemoteParameterType.RS_PARAMETER_TRANSFORM) + nNumericalParameters += 16; + else if (spec.parameters[i].type == RemoteParameterType.RS_PARAMETER_TEXT) + ++nTextParameters; + } + + using var parameters = new NativeArray(nNumericalParameters, Allocator.Temp); + if (PluginEntry.instance.GetFrameParameters(spec.hash, parameters.AsSpan()) == RS_ERROR.RS_ERROR_SUCCESS) + { + if (fields.numerical != null) + { + int i = 0; + foreach (var field in fields.numerical) + { + Type fieldType = field.FieldType; + if (fieldType.IsEnum) + { + field.SetValue(Enum.ToObject(fieldType, Convert.ToUInt64(parameters[i]))); + ++i; + } + else if (fieldType == typeof(Vector2)) + { + Vector2 v = new Vector2(parameters[i + 0], parameters[i + 1]); + field.SetValue(v); + i += 2; + } + else if (fieldType == typeof(Vector2Int)) + { + Vector2Int v = new Vector2Int((int)parameters[i + 0], (int)parameters[i + 1]); + field.SetValue(v); + i += 2; + } + else if (fieldType == typeof(Vector3)) + { + Vector3 v = new Vector3(parameters[i + 0], parameters[i + 1], parameters[i + 2]); + field.SetValue(v); + i += 3; + } + else if (fieldType == typeof(Vector3Int)) + { + Vector3Int v = new Vector3Int((int)parameters[i + 0], (int)parameters[i + 1], (int)parameters[i + 2]); + field.SetValue(v); + i += 3; + } + else if (fieldType == typeof(Vector4)) + { + Vector4 v = new Vector4(parameters[i + 0], parameters[i + 1], parameters[i + 2], parameters[i + 3]); + field.SetValue(v); + i += 4; + } + else if (fieldType == typeof(Quaternion)) + { + Vector3 euler = new Vector3(parameters[i + 0], parameters[i + 1], parameters[i + 2]); + Quaternion q = Quaternion.Euler(euler); + field.SetValue(q); + i += 3; + } + else if (fieldType == typeof(Color)) + { + Color v = new Color(parameters[i + 0], parameters[i + 1], parameters[i + 2], parameters[i + 3]); + field.SetValue(v); + i += 4; + } + else if (fieldType == typeof(Transform)) + { + Matrix4x4 m = new Matrix4x4(); + m.SetColumn(0, new Vector4(parameters[i + 0], parameters[i + 1], parameters[i + 2], parameters[i + 3])); + m.SetColumn(1, new Vector4(parameters[i + 4], parameters[i + 5], parameters[i + 6], parameters[i + 7])); + m.SetColumn(2, new Vector4(parameters[i + 8], parameters[i + 9], parameters[i + 10], parameters[i + 11])); + m.SetColumn(3, new Vector4(parameters[i + 12], parameters[i + 13], parameters[i + 14], parameters[i + 15])); + Transform transform = field.GetValue() as Transform; + transform.localPosition = new Vector3(m[0, 3], m[1, 3], m[2, 3]); + transform.localScale = m.lossyScale; + transform.localRotation = m.rotation; + i += 16; + } + else if (fieldType == typeof(float)) + { + field.SetValue(parameters[i]); + ++i; + } + else + { + if (field.info != null) + field.SetValue(Convert.ChangeType(parameters[i], fieldType)); + ++i; + } + } + } + + if (fields.texts != null) + { + uint i = 0; + foreach (var field in fields.texts) + { + string text = ""; + if (PluginEntry.instance.getFrameText(spec.hash, i, ref text) == RS_ERROR.RS_ERROR_SUCCESS) + { + if (field.FieldType == typeof(String[])) + field.SetValue(text.Split(' ')); + else + field.SetValue(text); + } + } + + ++i; + } + } + + } + + protected void AwaitFrame() + { + RS_ERROR error = PluginEntry.instance.awaitFrameData(500, out var frameData); + LatestFrameData = frameData; + + if (error == RS_ERROR.RS_ERROR_QUIT) + Application.Quit(); + if (error == RS_ERROR.RS_ERROR_STREAMS_CHANGED) + CreateStreams(); + switch (m_Settings.sceneControl) + { + case DisguiseRenderStreamSettings.SceneControl.Selection: + if (SceneManager.GetActiveScene().buildIndex != LatestFrameData.scene) + { + HasNewFrameData = false; + SceneManager.LoadScene((int)LatestFrameData.scene); + return; + } + break; + } + HasNewFrameData = (error == RS_ERROR.RS_ERROR_SUCCESS); + if (HasNewFrameData) + { + ProcessFrameData(LatestFrameData); + } + + DisguiseFramerateManager.Update(); + } + + // Updates the RenderTextures assigned to image parameters on the render thread to avoid stalling the main thread + void UpdateGfxResources() + { + if (!HasNewFrameData) + return; + + if (LatestFrameData.scene >= Schema.scenes.Length) + return; + + var spec = Schema.scenes[LatestFrameData.scene]; + var images = m_SceneFields[LatestFrameData.scene].images; + if (images == null) + return; + + var nImageParameters = spec.parameters.Count(t => t.type == RemoteParameterType.RS_PARAMETER_IMAGE); + + using var imageData = new NativeArray(nImageParameters, Allocator.Temp); + if (PluginEntry.instance.GetFrameImageData(spec.hash, imageData.AsSpan()) != RS_ERROR.RS_ERROR_SUCCESS) + return; + + CommandBuffer cmd = null; + var i = 0; + foreach (var field in images) + { + if (field.GetValue() is RenderTexture renderTexture) + { + // We may be temped to use RenderTexture instead of Texture2D for the shared textures. + // RenderTextures are always stored as typeless texture resources though, which aren't supported + // by CUDA interop (used by Disguise under the hood): + // https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__D3D11.html#group__CUDART__D3D11_1g85d07753780643584b8febab0370623b + // Texture2D apply their GraphicsFormat to their texture resources. + + var sharedTexture = TemporaryTexture2DManager.Instance.Get(new Texture2DDescriptor() + { + Width = (int)imageData[i].width, + Height = (int)imageData[i].height, + Format = imageData[i].format, + Linear = true + }); + + NativeRenderingPlugin.InputImageData data = new NativeRenderingPlugin.InputImageData() + { + m_rs_getFrameImage = PluginEntry.instance.rs_getFrameImage_ptr, + m_ImageId = imageData[i].imageId, + m_Texture = sharedTexture.GetNativeTexturePtr() + }; + + if (NativeRenderingPlugin.InputImageDataPool.TryPreserve(data, out var dataPtr)) + { + if (cmd == null) + cmd = CommandBufferPool.Get($"Receiving Disguise Image Parameters"); + + cmd.IssuePluginEventAndData( + NativeRenderingPlugin.GetRenderEventCallback(), + NativeRenderingPlugin.GetEventID(NativeRenderingPlugin.EventID.InputImage), + dataPtr); + cmd.IncrementUpdateCount(sharedTexture); + + cmd.Blit(sharedTexture, renderTexture, new Vector2(1.0f, -1.0f), new Vector2(0.0f, 1.0f)); + cmd.IncrementUpdateCount(renderTexture); + } + else + { + Debug.LogError($"DisguiseRenderStream: {nameof(NativeRenderingPlugin)}.{nameof(NativeRenderingPlugin.InputImageData)} pool exceeded, skipping input texture '{field.info.Name}'"); + } + } + + ++i; + } + + if (cmd != null) + { + Graphics.ExecuteCommandBuffer(cmd); + CommandBufferPool.Release(cmd); + } + } + + static Camera[] getTemplateCameras() + { + return Camera.allCameras; + } + + static Camera GetChannelCamera(string channel) + { + try + { + return Array.Find(getTemplateCameras(), camera => camera.name == channel); + } + catch (ArgumentNullException) + { + return Camera.main; + } + } + + public static DisguiseRenderStream Instance { get; private set; } + + public static Action SceneLoaded { get; set; } = delegate { }; + + public static Action StreamsChanged { get; set; } = delegate { }; + + public ManagedSchema Schema { get; } = new (); + + public IReadOnlyList Streams => m_Streams; + + public IEnumerable InputTextures + { + get + { + if (LatestFrameData.scene > m_SceneFields.Length) + return Enumerable.Empty(); + + var images = m_SceneFields[LatestFrameData.scene].images; + if (images == null) + return Enumerable.Empty(); + + return images.Select(x => x.GetValue() as RenderTexture); + } + } + + public bool Awaiting { get; private set; } + + public FrameData LatestFrameData { get; protected set; } + + public bool HasNewFrameData { get; protected set; } + + DisguiseStream[] m_Streams = { }; + + public struct SceneFields + { + public List numerical; + public List images; + public List texts; + } + + SceneFields[] m_SceneFields; + DisguiseRenderStreamSettings m_Settings; + } +} \ No newline at end of file diff --git a/Runtime/DisguiseRenderStream.cs.meta b/Runtime/DisguiseRenderStream.cs.meta new file mode 100644 index 0000000..a541d42 --- /dev/null +++ b/Runtime/DisguiseRenderStream.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6dbf1468d11d04e469dfbf5f1c3dde27 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/DisguiseRenderStreamSettings.cs b/Runtime/DisguiseRenderStreamSettings.cs new file mode 100644 index 0000000..eac7bfa --- /dev/null +++ b/Runtime/DisguiseRenderStreamSettings.cs @@ -0,0 +1,69 @@ +using UnityEngine; + +using System; +using System.IO; + +class DisguiseRenderStreamSettings : ScriptableObject +{ + public enum SceneControl + { + /// + /// Restricts the disguise software's control of scenes and instead merges all channels and remote parameters into a single scene. + /// + Manual, + + /// + /// Allows scenes to be controlled from inside the disguise software; channels are merged into a single list (duplicates removed) and remote parameters are per-scene. + /// + Selection + } + + /// + /// The Disguise software's behavior for controlling scenes. + /// + [Tooltip("Manual: Restricts the disguise software's control of scenes and instead merges all channels and remote parameters into a single scene.\n" + + "Selection: Allows scenes to be controlled from inside the disguise software; channels are merged into a single list (duplicates removed) and remote parameters are per-scene.")] + [SerializeField] + public SceneControl sceneControl = SceneControl.Manual; + + /// + /// When true, the Unity window will be able to display streams or live textures to the local screen for debug purposes. + /// The generated schema will include remote parameters to select the texture to display and how to resize it to fit the screen. + /// + [Tooltip("When true, the Unity window will be able to display streams or live textures to the local screen for debug purposes.\n" + + "The generated schema will include remote parameters to select the texture to display and how to resize it to fit the screen.")] + [SerializeField] + public bool enableUnityDebugWindowPresenter = true; + + static DisguiseRenderStreamSettings s_CachedSettings; + +#if UNITY_EDITOR + [UnityEditor.Callbacks.DidReloadScripts] + private static void OnReloadScripts() + { + // Ensure resource is created + DisguiseRenderStreamSettings.GetOrCreateSettings(); + } +#endif + + public static DisguiseRenderStreamSettings GetOrCreateSettings() + { + if (s_CachedSettings == null) + { + s_CachedSettings = Resources.Load("DisguiseRenderStreamSettings"); + } + + if (s_CachedSettings == null) + { + Debug.Log("Using default DisguiseRenderStreamSettings"); + s_CachedSettings = ScriptableObject.CreateInstance(); +#if UNITY_EDITOR + if (!Directory.Exists("Assets/Resources")) + Directory.CreateDirectory("Assets/Resources"); + UnityEditor.AssetDatabase.CreateAsset(s_CachedSettings, "Assets/Resources/DisguiseRenderStreamSettings.asset"); + UnityEditor.AssetDatabase.SaveAssets(); +#endif + } + return s_CachedSettings; + } +} diff --git a/Runtime/DisguiseRenderStreamSettings.cs.meta b/Runtime/DisguiseRenderStreamSettings.cs.meta new file mode 100644 index 0000000..fc2d313 --- /dev/null +++ b/Runtime/DisguiseRenderStreamSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 32c97564f5be14e43a9505c9babec64d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/DisguiseRenderStreamWithClustercs.cs b/Runtime/DisguiseRenderStreamWithClustercs.cs new file mode 100644 index 0000000..fb08365 --- /dev/null +++ b/Runtime/DisguiseRenderStreamWithClustercs.cs @@ -0,0 +1,360 @@ +#if ENABLE_CLUSTER_DISPLAY +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Disguise.RenderStream.Utils; +using UnityEngine; +using UnityEngine.SceneManagement; +using Unity.ClusterDisplay; +using Unity.ClusterDisplay.Utils; +using UnityEngine.Scripting; +using Debug = UnityEngine.Debug; + +namespace Disguise.RenderStream +{ + /// + /// A specialized version of that inserts synchronization logic from + /// the Cluster Display package. + /// + /// + /// The Cluster Display synchronization functionality keeps render nodes in lockstep, which is necessary for + /// physics, visual effects, and other computed/simulated behaviors. + /// + [ClusterParamProcessor] + class DisguiseRenderStreamWithCluster : DisguiseRenderStream, IDisposable + { + EventBus m_FrameDataBus; + IDisposable m_FollowerFrameDataSubscription; + + /// + /// An instance of that we own. If null, it means cluster rendering is disabled or + /// there is already another instance of . + /// + ClusterSync m_ClusterSync; + + internal DisguiseRenderStreamWithCluster(ManagedSchema schema) + : base(schema) { } + + protected override void Initialize() + { + ServiceLocator.TryGet(out IClusterSyncState clusterSyncState); + if (clusterSyncState is not { IsClusterLogicEnabled: true }) + { + // We're not actually running in a cluster. + // Fall back to base implementation. + base.Initialize(); + return; + } + + if (m_FrameDataBus != null) + { + ClusterDebug.LogWarning("RenderStream is already registered with Cluster Display"); + return; + } + + m_FrameDataBus = new EventBus(clusterSyncState); + switch (clusterSyncState.NodeRole) + { + // If we're the emitter (the "controller" in disguise-land), wait for the frame request (rs_awaitFrame) + // before the Cluster Display sync point. + case NodeRole.Emitter when !clusterSyncState.RepeatersDelayedOneFrame: + { + ClusterDebug.Log("Setting the node as Disguise RenderStream controller (no delay)"); + + // If we're only syncing the basic engine state (no custom data being computed during + // the frame), then we can cheat a bit to reduce latency, by publishing disguise FrameData + // *before* the sync point, so it will get transmitted on the current sync point (custom data is + // typically transmitted during the *next* sync point) + ClusterSyncLooper.onInstanceDoPreFrame += AwaitFrame; + ClusterSyncLooper.onInstanceDoPreFrame += PublishEmitterEvents; + break; + } + case NodeRole.Emitter when clusterSyncState.RepeatersDelayedOneFrame: + { + ClusterDebug.Log("Setting the node as Disguise RenderStream controller (one frame delay)"); + + // Custom data is computed during the frame and transmitted at the next sync point, which requires + // repeaters to be operating 1 frame behind. + ClusterSyncLooper.onInstanceDoPreFrame += AwaitFrame; + ClusterSyncLooper.onInstanceDoLateFrame += PublishEmitterEvents; + break; + } + case NodeRole.Repeater: + { + // If we're a repeater (a "follower"), we don't need to wait for disguise, and instead just wait on the + // normal Cluster Display sync point. The disguise FrameData structure is transmitted as custom + // data on the EventBus. + m_FollowerFrameDataSubscription = m_FrameDataBus.Subscribe(BeginFollowerFrameOnRepeaterNode); + ClusterDebug.Log("Setting the node as Disguise RenderStream follower"); + var error = PluginEntry.instance.setFollower(1); + if (error is not RS_ERROR.RS_ERROR_SUCCESS) + { + ClusterDebug.Log($"Could not set follower: {error.ToString()}"); + } + + break; + } + case NodeRole.Unassigned: + ClusterDebug.LogError("Attempting to use Cluster Display, but Cluster Display has not been initialized."); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + InitializeGfxResources(); + } + + void PublishEmitterEvents() => m_FrameDataBus.Publish(LatestFrameData); + + void BeginFollowerFrameOnRepeaterNode(FrameData emitterFrameData) + { + DisguiseRenderStreamSettings settings = DisguiseRenderStreamSettings.GetOrCreateSettings(); + RS_ERROR error = PluginEntry.instance.beginFollowerFrame(emitterFrameData.tTracked); + + Debug.Assert(error != RS_ERROR.RS_NOT_INITIALISED); + LatestFrameData = emitterFrameData; + + if (error == RS_ERROR.RS_ERROR_QUIT) + Application.Quit(); + if (error == RS_ERROR.RS_ERROR_STREAMS_CHANGED) + { + CreateStreams(); + } + + switch (settings.sceneControl) + { + case DisguiseRenderStreamSettings.SceneControl.Selection: + if (SceneManager.GetActiveScene().buildIndex != LatestFrameData.scene) + { + HasNewFrameData = false; + SceneManager.LoadScene((int)LatestFrameData.scene); + } + + break; + } + + HasNewFrameData = (error == RS_ERROR.RS_ERROR_SUCCESS); + + if (HasNewFrameData) + { + ProcessFrameData(LatestFrameData); + } + } + + [ClusterParamProcessorMethod, Preserve] + public static ClusterParams ProcessClusterParams(ClusterParams clusterParams) + { + var commandLineArgs = Environment.GetCommandLineArgs(); + var repeaterCountIdx = Array.IndexOf(commandLineArgs, "-followers"); + int? maybeRepeaterCount = + repeaterCountIdx > 0 && commandLineArgs.Length > repeaterCountIdx + 1 && + int.TryParse(commandLineArgs[repeaterCountIdx + 1], out var repeaterCountArg) + ? repeaterCountArg + : null; + + if (maybeRepeaterCount.HasValue) + { + clusterParams.RepeaterCount = maybeRepeaterCount.Value; + } + + ClusterDebug.Log($"Trying to assign ids for {clusterParams.RepeaterCount} repeaters"); + if (clusterParams.RepeaterCount < 1) + { + ClusterDebug.LogWarning("There are no repeater nodes specified."); + // Leave the parameters alone, skip the rest of the parameter processing. + // Other processors may still want to work with the parameters. + return clusterParams; + } + + try + { + var adapterInfo = MulticastExtensions.SelectNetworkInterface(clusterParams.AdapterName); + clusterParams.NodeID = NegotiateNodeID(clusterParams, adapterInfo.address); + ClusterDebug.Log($"Auto-assigning node ID {clusterParams.NodeID} (repeaters: {clusterParams.RepeaterCount})"); + + clusterParams.AdapterName = adapterInfo.name; + clusterParams.Fence = FrameSyncFence.External; + clusterParams.ClusterLogicSpecified = true; + + // Arbitrarily assign Node 0 as the emitter + clusterParams.Role = clusterParams.NodeID == 0 ? NodeRole.Emitter : NodeRole.Repeater; + } + catch (Exception e) + { + Debug.LogException(e); + clusterParams.ClusterLogicSpecified = false; + } + + return clusterParams; + } + + /// + /// Negotiate a distinct ID amongst other running render nodes. Uses the same multi-cast end point + /// as the core Cluster Display logic. + /// + /// Parameters containing the networking information. + /// The (unicast) IP address of the network interface we're negotiating on. + /// + /// The operation timed out. + /// Multiple errors were encountered. See the log for details. + static byte NegotiateNodeID(ClusterParams clusterParams, IPAddress adapterAddress) + { + var address = IPAddress.Parse(clusterParams.MulticastAddress); + var sendEndPoint = new IPEndPoint(address, clusterParams.Port); + var timeoutMilliseconds = (int)clusterParams.HandshakeTimeout.TotalMilliseconds; + + using var udpClient = new UdpClient(); + udpClient.EnableMulticast(address, + clusterParams.Port, + adapterAddress, + timeoutMilliseconds); + + int nodeIdResult; + var expectedNodeCount = clusterParams.RepeaterCount + 1; + + const string announcePrefix = "8e310677-85cf-4c0c-8209-15890342c4e4"; + const string readyPrefix = "772c27a7-e38d-42a9-835c-158f05dc50e0"; + + // Message that indicates that we're available to join a group. + // Each node must have a unique announcement message. + var announceMessage = $"{announcePrefix}:{adapterAddress}-{Process.GetCurrentProcess().Id}"; + + // Message that indicates that we're discovered a valid group. + var readyMessage = $"{readyPrefix}:{adapterAddress}-{Process.GetCurrentProcess().Id}"; + var foundGroup = false; + + // Set up our listening and announcing tasks. + var cancellation = new CancellationTokenSource(); + var token = cancellation.Token; + + var listen = Task.Run(async () => + { + // Listen for messages, including from self + SortedSet announcements = new(); + HashSet readyReports = new(); + while (!token.IsCancellationRequested) + { + var result = await udpClient.ReceiveAsync(); + var str = Encoding.UTF8.GetString(result.Buffer); + Debug.Log($"Received negotiation message {str}"); + if (str.StartsWith(announcePrefix)) + { + if (announcements.Add(str)) + { + // We've received announcements from the expected nodes. We have a group. + foundGroup = announcements.Count >= expectedNodeCount; + } + } + else if (str.StartsWith(readyPrefix)) + { + // We're going to keep going until each node is reporting that they have a group. + if (readyReports.Add(str) && readyReports.Count >= expectedNodeCount) + { + Debug.Log($"All {expectedNodeCount} nodes reported ready. Stop listening for messages."); + break; + } + } + } + + // Done! + // At this point, we've found a group, and received "ready" announcements from all other nodes. + // Return all the announcements that we received from the completed group. + return announcements; + }); + + // Add a timeout to the listen task. + var listenWithTimeout = Task.WhenAny(listen, + Task.Run(async () => + { + await Task.Delay(clusterParams.HandshakeTimeout, token); + return new List(); + })); + + var announce = Task.Run(async () => + { + var announceBytes = Encoding.UTF8.GetBytes(announceMessage); + var readyBytes = Encoding.UTF8.GetBytes(readyMessage); + while (!token.IsCancellationRequested) + { + // Announce our presence to the network. + // Even if we've already found a group, we want to continue announcing until this task + // is cancelled (to give other nodes a chance to discover us). + await udpClient.SendAsync(announceBytes, announceBytes.Length, sendEndPoint); + if (foundGroup) + { + // Announce that we have a group + Debug.Log("Reporting ready"); + await udpClient.SendAsync(readyBytes, readyBytes.Length, sendEndPoint); + } + await Task.Delay(100, token); + } + }); + + try + { + if (listenWithTimeout.Result == listen) + { + // Now that we have a completed group, we can assign a node ID. + var announcements = listen.Result; + + // Order the announcements in a list. The node ID will the index + // of this process's message. + nodeIdResult = announcements.ToList().IndexOf(announceMessage); + } + else + { + throw new TimeoutException("Timed out waiting for announcements"); + } + } + catch (AggregateException e) + { + foreach (var exception in e.InnerExceptions) + { + Debug.LogException(exception); + } + + throw new OperationCanceledException(""); + } + finally + { + // Cancel outstanding tasks. + cancellation.Cancel(); + try + { + Task.WaitAll(new[] { listen, announce }, clusterParams.HandshakeTimeout); + } + catch (AggregateException e) + { + // Nothing to do here. + // This exception is should be coming from the "announce" task being cancelled forceably. + // Errors from the "listen" task should have been caught already. + } + } + + return (byte)nodeIdResult; + } + + public void Dispose() + { + ClusterSyncLooper.onInstanceDoPreFrame -= AwaitFrame; + ClusterSyncLooper.onInstanceDoPreFrame -= PublishEmitterEvents; + ClusterSyncLooper.onInstanceDoLateFrame -= PublishEmitterEvents; + m_FollowerFrameDataSubscription?.Dispose(); + m_FrameDataBus?.Dispose(); + m_FrameDataBus = null; + if (m_ClusterSync != null) + { + m_ClusterSync.DisableClusterDisplay(); + ServiceLocator.Withdraw(); + m_ClusterSync = null; + } + } + } +} +#endif diff --git a/Runtime/DisguiseRenderStreamWithClustercs.cs.meta b/Runtime/DisguiseRenderStreamWithClustercs.cs.meta new file mode 100644 index 0000000..c75e8ab --- /dev/null +++ b/Runtime/DisguiseRenderStreamWithClustercs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07ae4c8ede166bf46b39d835422f9f51 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/DisguiseTextures.cs b/Runtime/DisguiseTextures.cs new file mode 100644 index 0000000..cb2f539 --- /dev/null +++ b/Runtime/DisguiseTextures.cs @@ -0,0 +1,50 @@ +using System; +using UnityEngine; +using UnityEngine.Experimental.Rendering; +using UnityEngine.Rendering; + +namespace Disguise.RenderStream +{ + static class DisguiseTextures + { + public static Texture2D CreateTexture(int width, int height, RSPixelFormat format, bool sRGB, string name) + { + Texture2D texture = null; + + switch (PluginEntry.instance.GraphicsDeviceType) + { + case GraphicsDeviceType.Direct3D11: + texture = new Texture2D(width, height, PluginEntry.ToGraphicsFormat(format, sRGB), 1, TextureCreationFlags.None); + break; + + case GraphicsDeviceType.Direct3D12: + RS_ERROR error = PluginEntry.instance.useDX12SharedHeapFlag(out var heapFlag); + if (error != RS_ERROR.RS_ERROR_SUCCESS) + Debug.LogError(string.Format("Error checking shared heap flag: {0}", error)); + + if (heapFlag == UseDX12SharedHeapFlag.RS_DX12_USE_SHARED_HEAP_FLAG) + { + var nativeTex = NativeRenderingPlugin.CreateNativeTexture(name, width, height, format, sRGB); + var graphicsFormat = PluginEntry.ToGraphicsFormat(format, sRGB); + var textureFormat = GraphicsFormatUtility.GetTextureFormat(graphicsFormat); + texture = Texture2D.CreateExternalTexture(width, height, textureFormat, false, !sRGB, nativeTex); + + break; + } + else + { + texture = new Texture2D(width, height, PluginEntry.ToGraphicsFormat(format, sRGB), 1, TextureCreationFlags.None); + break; + } + } + + if (texture != null) + { + texture.hideFlags = HideFlags.HideAndDontSave; + texture.name = name; + } + + return texture; + } + } +} diff --git a/Runtime/DisguiseTextures.cs.meta b/Runtime/DisguiseTextures.cs.meta new file mode 100644 index 0000000..38a120a --- /dev/null +++ b/Runtime/DisguiseTextures.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 49d6f1da40c170d4cb016bdaf01d6c5a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/DisguiseTimeControl.cs b/Runtime/DisguiseTimeControl.cs new file mode 100644 index 0000000..b37c614 --- /dev/null +++ b/Runtime/DisguiseTimeControl.cs @@ -0,0 +1,55 @@ +using UnityEngine; +using UnityEngine.Playables; + +using System; + +namespace Disguise.RenderStream +{ + [AddComponentMenu("Disguise RenderStream/Time Control")] + [RequireComponent(typeof(PlayableDirector))] + public class DisguiseTimeControl : MonoBehaviour + { + void Start() + { + playableDirector = GetComponent(); + playableDirector.timeUpdateMode = DirectorUpdateMode.Manual; + } + + void Update() + { + Time.captureDeltaTime = 0; + var renderStream = DisguiseRenderStream.Instance; + if (renderStream == null) return; + + if (renderStream.HasNewFrameData) + { + if (renderStream.LatestFrameData.localTime < playableDirector.initialTime || renderStream.LatestFrameData.localTimeDelta <= 0) + playableDirector.Pause(); + else + playableDirector.Resume(); + + if (renderStream.LatestFrameData.localTimeDelta > 0) + { + Time.captureDeltaTime = (float)renderStream.LatestFrameData.localTimeDelta; + } + + playableDirector.time = Math.Max(0, renderStream.LatestFrameData.localTime - playableDirector.initialTime); + + switch (playableDirector.extrapolationMode) + { + case DirectorWrapMode.Hold: + if (playableDirector.time > playableDirector.duration) + playableDirector.time = playableDirector.duration; + break; + case DirectorWrapMode.Loop: + playableDirector.time = (playableDirector.time % playableDirector.duration); + break; + } + + playableDirector.Evaluate(); + } + } + + private PlayableDirector playableDirector; + } +} \ No newline at end of file diff --git a/Runtime/DisguiseTimeControl.cs.meta b/Runtime/DisguiseTimeControl.cs.meta new file mode 100644 index 0000000..90ac9a9 --- /dev/null +++ b/Runtime/DisguiseTimeControl.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f774fe2a62ffb644387c8035a75bf286 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/FrameSender.cs b/Runtime/FrameSender.cs new file mode 100644 index 0000000..3027ec7 --- /dev/null +++ b/Runtime/FrameSender.cs @@ -0,0 +1,126 @@ +using System; +using UnityEngine; +using UnityEngine.Rendering; + +namespace Disguise.RenderStream +{ + public class FrameSender + { + internal CameraCaptureDescription description => m_description; + public Rect subRegion => m_frameRegion; + + string m_name; + int m_lastFrameCount; + + UInt64 m_streamHandle; + CameraCaptureDescription m_description; + RSPixelFormat m_pixelFormat; + Rect m_frameRegion; + + public FrameSender(string name, StreamDescription stream) + { + m_name = name; + + Debug.Log($"Creating stream {m_name}"); + Debug.Log($" Channel {stream.channel} at {stream.width}x{stream.height}@{stream.format}"); + + m_lastFrameCount = -1; + m_streamHandle = stream.handle; + m_pixelFormat = stream.format; + + m_description = new CameraCaptureDescription() + { + m_colorSpace = CameraCaptureDescription.ColorSpace.sRGB, + m_autoFlipY = true, + m_width = (int)stream.width, + m_height = (int)stream.height, + m_colorFormat = PluginEntry.ToGraphicsFormat(m_pixelFormat, true), + m_msaaSamples = 1, + m_depthBufferBits = 24, + m_copyDepth = false + }; + + m_frameRegion = new Rect(stream.clipping.left, stream.clipping.top, stream.clipping.right - stream.clipping.left, stream.clipping.bottom - stream.clipping.top); + + // Create texture ahead of time + GetSharedTexture(); + + Debug.Log($"Created stream {m_name} with handle {m_streamHandle}"); + } + + public bool GetCameraData(ref CameraData cameraData) + { + return PluginEntry.instance.getFrameCamera(m_streamHandle, ref cameraData) == RS_ERROR.RS_ERROR_SUCCESS; + } + + public void SendFrame(ScriptableRenderContext context, FrameData frameData, CameraData cameraData, RenderTexture texture) + { + if (m_lastFrameCount == Time.frameCount) + return; + + m_lastFrameCount = Time.frameCount; + + var cameraResponseData = new CameraResponseData { tTracked = frameData.tTracked, camera = cameraData }; + + var sharedTexture = GetSharedTexture(); + + var cmd = CommandBufferPool.Get("Disguise FrameSender"); + + // Copy to shared texture + cmd.CopyTexture(texture, sharedTexture); + + SendFrame(cmd, sharedTexture, cameraResponseData); + + context.ExecuteCommandBuffer(cmd); + context.Submit(); + + CommandBufferPool.Release(cmd); + } + + void SendFrame(CommandBuffer cmd, Texture2D frame, CameraResponseData cameraResponseData) + { + switch (PluginEntry.instance.GraphicsDeviceType) + { + case GraphicsDeviceType.Direct3D11: + case GraphicsDeviceType.Direct3D12: + break; + + default: + Debug.LogError($"Unsupported graphics device type {PluginEntry.instance.GraphicsDeviceType}"); + return; + } + + NativeRenderingPlugin.SendFrameData sendFrameData = new NativeRenderingPlugin.SendFrameData() + { + m_rs_sendFrame = PluginEntry.instance.rs_sendFrame_ptr, + m_StreamHandle = m_streamHandle, + m_Texture = frame.GetNativeTexturePtr(), + m_CameraResponseData = cameraResponseData + }; + + if (NativeRenderingPlugin.SendFrameDataPool.TryPreserve(sendFrameData, out var dataPtr)) + { + cmd.IssuePluginEventAndData( + NativeRenderingPlugin.GetRenderEventCallback(), + NativeRenderingPlugin.GetEventID(NativeRenderingPlugin.EventID.SendFrame), + dataPtr); + } + } + + // We may be temped to use RenderTexture instead of Texture2D for the shared textures. + // RenderTextures are always stored as typeless texture resources though, which aren't supported + // by CUDA interop (used by Disguise under the hood): + // https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__D3D11.html#group__CUDART__D3D11_1g85d07753780643584b8febab0370623b + // Texture2D apply their GraphicsFormat to their texture resources. + Texture2D GetSharedTexture() + { + return ScratchTexture2DManager.Instance.Get(new Texture2DDescriptor + { + Width = m_description.m_width, + Height = m_description.m_height, + Format = m_pixelFormat, + Linear = m_description.m_colorSpace != CameraCaptureDescription.ColorSpace.sRGB + }); + } + } +} diff --git a/Runtime/FrameSender.cs.meta b/Runtime/FrameSender.cs.meta new file mode 100644 index 0000000..4c665fe --- /dev/null +++ b/Runtime/FrameSender.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9d94e3fa595d9e44696d9b7e41cf18a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/NativeRenderingPlugin.cs b/Runtime/NativeRenderingPlugin.cs new file mode 100644 index 0000000..8ccdfa8 --- /dev/null +++ b/Runtime/NativeRenderingPlugin.cs @@ -0,0 +1,278 @@ +#if UNITY_STANDALONE_WIN && UNITY_64 +#define NATIVE_RENDERING_PLUGIN_AVAILABLE +#endif + +using System; +using System.Runtime.InteropServices; +using Disguise.RenderStream.Utils; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine.PlayerLoop; + +namespace Disguise.RenderStream +{ + static class NativeRenderingPlugin + { + struct FinishFrameRendering { } + + public enum PixelFormat + { + Invalid, + BGRA8, + BGRX8, + RGBA32F, + RGBA16, + RGBA8, + } + + public enum EventID + { + InputImage, + SendFrame + } + + public struct InputImageData + { + public IntPtr m_rs_getFrameImage; + public Int64 m_ImageId; + public IntPtr m_Texture; + } + + public struct SendFrameData + { + public IntPtr m_rs_sendFrame; + public ulong m_StreamHandle; + public IntPtr m_Texture; + public CameraResponseData m_CameraResponseData; + } + + public static EventDataPool InputImageDataPool { get; } = new EventDataPool(); + public static EventDataPool SendFrameDataPool { get; } = new EventDataPool(); + + static int? s_BaseEventID; + + static NativeRenderingPlugin() + { + PlayerLoopExtensions.RegisterUpdate(OnFinishFrameRendering); + } + + public static int GetEventID(EventID evt) + { + s_BaseEventID ??= GetBaseEventID(); + return (int)evt + s_BaseEventID.Value; + } + + static void OnFinishFrameRendering() + { + InputImageDataPool.OnFrameEnd(); + SendFrameDataPool.OnFrameEnd(); + } + +#if NATIVE_RENDERING_PLUGIN_AVAILABLE + public static IntPtr GetRenderEventCallback() + { + return NativeRenderingPluginNative.GetRenderEventCallback(); + } + + public static bool IsInitialized() + { + return NativeRenderingPluginNative.IsInitialized(); + } + + static int GetBaseEventID() + { + return NativeRenderingPluginNative.GetBaseEventID(); + } + + public static IntPtr GetD3D12Device() + { + if (IsInitialized()) + { + return NativeRenderingPluginNative.GetD3D12Device(); + } + return IntPtr.Zero; + } + + public static IntPtr GetD3D12CommandQueue() + { + if (IsInitialized()) + { + return NativeRenderingPluginNative.GetD3D12CommandQueue(); + } + return IntPtr.Zero; + } + + public static IntPtr CreateNativeTexture(string name, int width, int height, RSPixelFormat pixelFormat, bool sRGB) + { + if (IsInitialized()) + { + return NativeRenderingPluginNative.CreateNativeTexture(name, width, height, pixelFormat, sRGB); + } + return IntPtr.Zero; + } +#else + public static IntPtr GetRenderEventCallback() + { + return IntPtr.Zero; + } + + public static bool IsInitialized() + { + return false; + } + + static int GetBaseEventID() + { + return 0; + } + + public static IntPtr GetD3D12Device() + { + return IntPtr.Zero; + } + + public static IntPtr GetD3D12CommandQueue() + { + return IntPtr.Zero; + } + + public static IntPtr CreateNativeTexture(string name, int width, int height, RSPixelFormat pixelFormat, bool sRGB) + { + return IntPtr.Zero; + } +#endif + } + +#if NATIVE_RENDERING_PLUGIN_AVAILABLE + static class NativeRenderingPluginNative + { + const string PluginName = "NativeRenderingPlugin"; + + [DllImport(PluginName)] + public static extern IntPtr GetRenderEventCallback(); + + [DllImport(PluginName)] + public static extern bool IsInitialized(); + + [DllImport(PluginName)] + public static extern int GetBaseEventID(); + + [DllImport(PluginName)] + public static extern IntPtr GetD3D12Device(); + + [DllImport(PluginName)] + public static extern IntPtr GetD3D12CommandQueue(); + + [DllImport(PluginName)] + public static extern IntPtr CreateNativeTexture([MarshalAs(UnmanagedType.LPWStr)] string name, int width, int height, RSPixelFormat pixelFormat, bool sRGB); + } +#endif + + class EventDataPool : AutoDisposable where TData : unmanaged + { + class Record + { + public bool InUse => m_InUse; + public int FramesSinceCreated => m_FramesSinceCreated; + + bool m_InUse; + int m_FramesSinceCreated; + + public Record() + { + MarkUnused(); + } + + public void MarkUsed() + { + m_InUse = true; + m_FramesSinceCreated = 0; + } + + public void MarkUnused() + { + m_InUse = false; + m_FramesSinceCreated = 0; + } + + public void Update() + { + m_FramesSinceCreated++; + } + } + + // There can be max 1 pipelined render thread frame in progress, so we keep alive for 2 main thread frames + const int k_NumFramesToKeepAlive = 2; + + const int k_Capacity = 64; + NativeArray m_Data = new(k_Capacity, Allocator.Persistent); + readonly Record[] m_Records = new Record[k_Capacity]; + + public EventDataPool() + { + for (int i = 0; i < m_Records.Length; i++) + { + m_Records[i] = new Record(); + } + } + + protected override void Dispose() + { + m_Data.Dispose(); + } + + // Copy the data into unmanaged memory + public bool TryPreserve(TData data, out IntPtr pointer) + { + var freeIndex = -1; + for (var i = 0; i < m_Records.Length; i++) + { + if (!m_Records[i].InUse) + { + freeIndex = i; + m_Records[i].MarkUsed(); + break; + } + } + + if (freeIndex >= 0) + { + m_Data[freeIndex] = data; + unsafe + { + TData* ptr = (TData*)m_Data.GetUnsafePtr(); + pointer = (IntPtr)(ptr + freeIndex); + return true; + } + } + + pointer = IntPtr.Zero; + return false; + } + + /// + /// Call once at the end of the frame. + /// + public void OnFrameEnd() + { + for (var i = m_Records.Length - 1; i >= 0; i--) + { + var record = m_Records[i]; + + if (ShouldDispose(record)) + { + record.MarkUnused(); + } + else + { + record.Update(); + } + } + } + + static bool ShouldDispose(Record record) + { + return record.InUse && record.FramesSinceCreated >= k_NumFramesToKeepAlive; + } + } +} diff --git a/Runtime/NativeRenderingPlugin.cs.meta b/Runtime/NativeRenderingPlugin.cs.meta new file mode 100644 index 0000000..d06251a --- /dev/null +++ b/Runtime/NativeRenderingPlugin.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65208259ba642384a98f7e8c57435729 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction.meta b/Runtime/PipelineAbstraction.meta new file mode 100644 index 0000000..98af7dc --- /dev/null +++ b/Runtime/PipelineAbstraction.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f5a92e4c469646d4e9bfee444a098350 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/AssemblyInfo.cs b/Runtime/PipelineAbstraction/AssemblyInfo.cs new file mode 100644 index 0000000..04569e8 --- /dev/null +++ b/Runtime/PipelineAbstraction/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Disguise.RenderStream")] +[assembly: InternalsVisibleTo("Disguise.RenderStream.Editor")] \ No newline at end of file diff --git a/Runtime/PipelineAbstraction/AssemblyInfo.cs.meta b/Runtime/PipelineAbstraction/AssemblyInfo.cs.meta new file mode 100644 index 0000000..ca1a3c5 --- /dev/null +++ b/Runtime/PipelineAbstraction/AssemblyInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a526d157660a6d34db40073d2c776562 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/BlitExtended.cs b/Runtime/PipelineAbstraction/BlitExtended.cs new file mode 100644 index 0000000..de60844 --- /dev/null +++ b/Runtime/PipelineAbstraction/BlitExtended.cs @@ -0,0 +1,135 @@ +using System; +using System.Linq; +using Disguise.RenderStream; +using UnityEngine; +using UnityEngine.Assertions; +using UnityEngine.Rendering; + +/// +/// +/// Based on: +/// , +/// com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl, +/// com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/Blit.shader, +/// com.unity.render-pipelines.universal/Shaders/Utils/CoreBlit.shader. +/// +/// +/// +/// This version of performs color space +/// conversion at the same time as the blit. +/// +/// +class BlitExtended +{ + public enum ColorSpaceConversion + { + None, + LinearToSRGB, + SRGBToLinear + } + + enum Geometry + { + FullscreenTriangle, + Quad + } + + /// + /// The strings are identical to + /// + static class ShaderIDs + { + public static readonly int _BlitTexture = Shader.PropertyToID("_BlitTexture"); + public static readonly int _BlitScaleBias = Shader.PropertyToID("_BlitScaleBias"); + public static readonly int _BlitScaleBiasRt = Shader.PropertyToID("_BlitScaleBiasRt"); + } + + public static BlitExtended Instance { get; } + + static BlitExtended() + { + Instance = new BlitExtended(); + } + + public static ColorSpaceConversion GetSRGBConversion(SRGBConversions.Conversion conversion) => conversion switch + { + SRGBConversions.Conversion.None or + SRGBConversions.Conversion.HardwareLinearToSRGB or + SRGBConversions.Conversion.HardwareSRGBToLinear => ColorSpaceConversion.None, + + SRGBConversions.Conversion.SoftwareLinearToSRGB => ColorSpaceConversion.LinearToSRGB, + + SRGBConversions.Conversion.SoftwareSRGBToLinear => ColorSpaceConversion.SRGBToLinear, + + _ => throw new ArgumentOutOfRangeException() + }; + + static readonly int k_ColorSpaceConversionCount = Enum.GetValues(typeof(ColorSpaceConversion)).Cast().Max() + 1; + + /// + /// This function and the order of the Pass { ... } blocks in have to match + /// + static int GetPass(Geometry geometry, ColorSpaceConversion conversion) + { + return (int)geometry * k_ColorSpaceConversionCount + (int)conversion; + } + +#if UNITY_PIPELINE_HDRP && HDRP_VERSION_SUPPORTED + public const string ShaderName = "Hidden/Disguise/RenderStream/BlitExtendedHDRP"; +#elif UNITY_PIPELINE_URP && URP_VERSION_SUPPORTED + public const string ShaderName = "Hidden/Disguise/RenderStream/BlitExtendedURP"; +#else + public const string ShaderName = null; +#endif + + readonly Material m_Blit; + readonly MaterialPropertyBlock m_PropertyBlock = new MaterialPropertyBlock(); + + BlitExtended() + { +#if !(UNITY_PIPELINE_HDRP && HDRP_VERSION_SUPPORTED) && !(UNITY_PIPELINE_URP && URP_VERSION_SUPPORTED) + Debug.LogError($"No supported render pipeline was found for {nameof(BlitExtended)}."); +#endif + + var shader = Shader.Find(ShaderName); + if (shader != null) + m_Blit = CoreUtils.CreateEngineMaterial(shader); + + Assert.IsTrue(shader != null && m_Blit != null, $"Couldn't load the shader resources for {nameof(BlitExtended)}"); + } + + /// + /// Similar to + /// + public void BlitTexture(CommandBuffer cmd, RenderTexture source, RenderTexture destination, ColorSpaceConversion conversion, ScaleBias scaleBias) + { + m_PropertyBlock.SetTexture(ShaderIDs._BlitTexture, source); + m_PropertyBlock.SetVector(ShaderIDs._BlitScaleBias, scaleBias.Vector); + + var shaderPass = GetPass(Geometry.FullscreenTriangle, conversion); + + CoreUtils.DrawFullScreen(cmd, m_Blit, destination, m_PropertyBlock, shaderPass); + } + + /// + /// Similar to + /// + public void BlitQuad(CommandBuffer cmd, RenderTexture source, ColorSpaceConversion conversion, ScaleBias srcScaleBias, ScaleBias dstScaleBias) + { + m_PropertyBlock.SetTexture(ShaderIDs._BlitTexture, source); + m_PropertyBlock.SetVector(ShaderIDs._BlitScaleBias, srcScaleBias.Vector); + m_PropertyBlock.SetVector(ShaderIDs._BlitScaleBiasRt, dstScaleBias.Vector); + + var shaderPass = GetPass(Geometry.Quad, conversion); + + DrawQuad(cmd, m_Blit, shaderPass); + } + + /// + /// Similar to + /// + void DrawQuad(CommandBuffer cmd, Material material, int shaderPass) + { + cmd.DrawProcedural(Matrix4x4.identity, material, shaderPass, MeshTopology.Quads, 4, 1, m_PropertyBlock); + } +} diff --git a/Runtime/PipelineAbstraction/BlitExtended.cs.meta b/Runtime/PipelineAbstraction/BlitExtended.cs.meta new file mode 100644 index 0000000..c8ee043 --- /dev/null +++ b/Runtime/PipelineAbstraction/BlitExtended.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 04ec036ed54bc1644b5e91be80234a56 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/BlitStrategy.cs b/Runtime/PipelineAbstraction/BlitStrategy.cs new file mode 100644 index 0000000..cea0a25 --- /dev/null +++ b/Runtime/PipelineAbstraction/BlitStrategy.cs @@ -0,0 +1,210 @@ +using System; +using UnityEngine; +using UnityEngine.Rendering; + +namespace Disguise.RenderStream +{ + /// + /// + /// Represents a transformation applied to a source or destination texture inside a blit shader. + /// + /// + /// + /// The transformation applied is: + /// TransformedUV = OriginalUV * + + /// + /// + public readonly struct ScaleBias + { + /// + /// UVs remain unchanged. + /// + public static ScaleBias Identity { get; } = new ScaleBias(1f, 1f, 0f, 0f); + + /// + /// The UVs are flipped vertically. + /// + public static ScaleBias FlippedY { get; } = FlipY(Identity); + + /// + /// Blit shaders expect a packed Vector4. + /// + public Vector4 Vector { get; } + + public float ScaleX => Vector.x; + public float ScaleY => Vector.y; + public float BiasX => Vector.z; + public float BiasY => Vector.w; + + public Vector2 Scale => new Vector2(ScaleX, ScaleY); + public Vector2 Bias => new Vector2(BiasX, BiasY); + + public ScaleBias(float scaleX, float scaleY, float biasX, float biasY) + { + Vector = new Vector4(scaleX, scaleY, biasX, biasY); + } + + /// + /// Returns a copy of flipped vertically. + /// + public static ScaleBias FlipY(ScaleBias scaleBias) => + new ScaleBias(scaleBias.ScaleX, -scaleBias.ScaleY, scaleBias.BiasX, 1f - scaleBias.BiasY); + } + + /// + /// Strategy calculations for the and APIs. + /// + static class BlitStrategy + { + /// + /// A strategy to handle the size and aspect ratio differences between two surfaces. + /// + public enum Strategy + { + /// + /// Stretches the source to have the same size as the destination. + /// The aspect ratio may be lost. + /// + Stretch, + + /// + /// The source isn't scaled at all but it's centered within the destination. + /// + NoResize, + + /// + /// The source is scaled while conserving the aspect ratio so that the width matches the destination. + /// + FitWidth, + + /// + /// The source is scaled while conserving the aspect ratio so that the height matches the destination. + /// + FitHeight, + + /// + /// The source is scaled while conserving the aspect ratio to fill the destination. + /// It can't overflow but can leave black bars on the sides. + /// + Letterbox, + + /// + /// The source is scaled while conserving the aspect ratio to fill the destination. + /// It can overflow but won't leave black bars on the sides. + /// + Fill, + + /// + /// Behaves like if it fits inside the destination. + /// Otherwise behaves like . + /// + Clamp + } + + /// + /// Computes a strategy for the API. + /// + /// A to apply to the blit destination texture's UV coordinates. + public static ScaleBias DoStrategy(Strategy strategy, Vector2 srcSize, Vector2 dstSize) + { + switch (strategy) + { + case Strategy.Stretch: + return Stretch(srcSize, dstSize); + case Strategy.NoResize: + return NoResize(srcSize, dstSize); + case Strategy.FitWidth: + return FitWidth(srcSize, dstSize); + case Strategy.FitHeight: + return FitHeight(srcSize, dstSize); + case Strategy.Letterbox: + return Letterbox(srcSize, dstSize); + case Strategy.Fill: + return Fill(srcSize, dstSize); + case Strategy.Clamp: + return Clamp(srcSize, dstSize); + default: + throw new NotImplementedException(); + } + } + + static ScaleBias Stretch(Vector2 srcSize, Vector2 dstSize) + { + return ScaleBias.Identity; + } + + static ScaleBias NoResize(Vector2 srcSize, Vector2 dstSize) + { + var scale = srcSize / dstSize; + var offset = CenterUVOffset(scale); + + return new ScaleBias(scale.x, scale.y, offset.x, offset.y); + } + + static ScaleBias FitWidth(Vector2 srcSize, Vector2 dstSize) + { + var yScale = InverseAspectRatio(srcSize) * AspectRatio(dstSize); + var yOffset = CenterUVOffset(yScale); + + return new ScaleBias(1f, yScale, 0f, yOffset); + } + + static ScaleBias FitHeight(Vector2 srcSize, Vector2 dstSize) + { + var xScale = AspectRatio(srcSize) * InverseAspectRatio(dstSize); + var xOffset = CenterUVOffset(xScale); + + return new ScaleBias(xScale, 1f, xOffset, 0f); + } + + static ScaleBias Letterbox(Vector2 srcSize, Vector2 dstSize) + { + var scrAspect = AspectRatio(srcSize); + var dstAspect = AspectRatio(dstSize); + + if (scrAspect > dstAspect) + return FitWidth(srcSize, dstSize); + else + return FitHeight(srcSize, dstSize); + } + + static ScaleBias Fill(Vector2 srcSize, Vector2 dstSize) + { + var scrAspect = AspectRatio(srcSize); + var dstAspect = AspectRatio(dstSize); + + if (scrAspect < dstAspect) + return FitWidth(srcSize, dstSize); + else + return FitHeight(srcSize, dstSize); + } + + static ScaleBias Clamp(Vector2 srcSize, Vector2 dstSize) + { + if (srcSize.x > dstSize.x || srcSize.y > dstSize.y) + return Letterbox(srcSize, dstSize); + else + return NoResize(srcSize, dstSize); + } + + static float AspectRatio(Vector2 size) + { + return size.x / size.y; + } + + static float InverseAspectRatio(Vector2 size) + { + return size.y / size.x; + } + + static float CenterUVOffset(float scale) + { + return (1f - scale) / 2f; + } + + static Vector2 CenterUVOffset(Vector2 scale) + { + return (Vector2.one - scale) / 2f; + } + } +} diff --git a/Runtime/PipelineAbstraction/BlitStrategy.cs.meta b/Runtime/PipelineAbstraction/BlitStrategy.cs.meta new file mode 100644 index 0000000..623f739 --- /dev/null +++ b/Runtime/PipelineAbstraction/BlitStrategy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ba894c4ad494bdf4c9eaf7ed9964a4d2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/CameraCapture.cs b/Runtime/PipelineAbstraction/CameraCapture.cs new file mode 100644 index 0000000..b153789 --- /dev/null +++ b/Runtime/PipelineAbstraction/CameraCapture.cs @@ -0,0 +1,241 @@ +using System; +using UnityEngine; +using UnityEngine.Rendering; + +namespace Disguise.RenderStream +{ + /// + /// Configures the 's camera for offscreen rendering and provides access to its + /// color and optionally its depth buffer. Also handles color space conversions and Y flip. + /// + /// + /// The camera will no longer render to the local screen. + /// Use the component to display a captured texture on the local screen. + /// It will handle size and aspect ratio differences between the screen and the texture. + /// + /// + [ExecuteAlways] + [RequireComponent(typeof(Camera))] + class CameraCapture : MonoBehaviour + { + public readonly struct Capture + { + internal Capture(RenderTexture cameraTexture, RenderTexture depthTexture) + { + this.cameraTexture = cameraTexture; + this.depthTexture = depthTexture; + } + + /// + /// Refers to . + /// + public RenderTexture cameraTexture { get; } + + /// + /// Refers to . + /// + public RenderTexture depthTexture { get; } + } + + /// + /// Called once textures have been captured and are ready to be consumed. + /// + public event Action onCapture = delegate {}; + + /// + /// + /// The captured color texture. Its format is defined by . + /// + /// + /// + /// Note: while the texture may be configured for MSAA, Unity will always use an implicitly resolved version + /// of the texture for APIs such + /// and see . + /// + /// + /// + /// To avoid unnecessary texture copies, this can refer directly to (depending on + /// and ). + /// + /// + public RenderTexture cameraTexture => m_description.NeedsBlit ? m_cameraBlitTexture : m_cameraTexture; + + /// + /// The captured depth texture. Its format is defined by . + /// if is not configured for depth. + /// + public RenderTexture depthTexture => m_depthTexture; + + /// + /// Defines the color and depth textures. The textures are automatically disposed and created on change. + /// + public CameraCaptureDescription description + { + get => m_description; + set + { + if (m_description != value) + { + m_description = value; + Refresh(); + } + } + } + + [SerializeField] + CameraCaptureDescription m_description = CameraCaptureDescription.Default; + + Camera m_camera; + RenderTexture m_cameraTexture; + RenderTexture m_cameraBlitTexture; + RenderTexture m_depthTexture; + CameraCaptureDescription m_lastDescription; + +#if DEBUG + bool m_HasSetSecondNames; +#endif + + const string k_blitProfilerTag = "Camera Capture Blit"; + + void Awake() + { + m_camera = GetComponent(); + + CreateResources(m_description); + } + + void OnEnable() + { +#if !(UNITY_PIPELINE_HDRP && HDRP_VERSION_SUPPORTED) && !(UNITY_PIPELINE_URP && URP_VERSION_SUPPORTED) + Debug.LogError($"No supported render pipeline was found for {nameof(CameraCapture)}."); +#endif + + RenderPipelineManager.endCameraRendering += OnEndCameraRendering; + } + + void OnDisable() + { + RenderPipelineManager.endCameraRendering -= OnEndCameraRendering; + } + + void OnDestroy() + { + DisposeResources(); + } + + // Check for updates from the inspector UI +#if UNITY_EDITOR + void Update() + { + if (m_lastDescription != m_description) + Refresh(); + } +#endif + + void Refresh() + { + DisposeResources(); + CreateResources(m_description); + } + + void CreateResources(CameraCaptureDescription desc) + { + m_lastDescription = desc; + + if (!desc.IsValid(out var message)) + { + Debug.LogWarning($"{nameof(CameraCapture)} is disabled because of an invalid configuration: {message}"); + return; + } + + var cameraDesc = desc.GetCameraDescriptor(); + m_cameraTexture = new RenderTexture(cameraDesc); + + m_camera.targetTexture = m_cameraTexture; + + if (desc.NeedsBlit) + { + var cameraBlitDesc = desc.GetCameraBlitDescriptor(); + m_cameraBlitTexture = new RenderTexture(cameraBlitDesc); + } + + if (desc.m_copyDepth) + { + var depthCopyDescriptor = desc.GetDepthCopyDescriptor(); + m_depthTexture = new RenderTexture(depthCopyDescriptor); + } + +#if DEBUG + m_cameraTexture.name = $"{nameof(CameraCapture)} Camera Texture Initial {m_cameraTexture.width}x{m_cameraTexture.height}"; + if (m_cameraBlitTexture != null) + m_cameraBlitTexture.name = $"{nameof(CameraCapture)} Camera Blit Texture Initial {m_cameraBlitTexture.width}x{m_cameraBlitTexture.height}"; + if (m_depthTexture != null) + m_depthTexture.name = $"{nameof(CameraCapture)} Depth Copy Texture Initial {m_depthTexture.width}x{m_depthTexture.height}"; + m_HasSetSecondNames = false; +#endif + } + + void DisposeResources() + { + if (m_cameraTexture != null) + m_cameraTexture.Release(); + if (m_cameraBlitTexture != null) + m_cameraBlitTexture.Release(); + if (m_depthTexture != null) + m_depthTexture.Release(); + + m_cameraTexture = null; + m_cameraBlitTexture = null; + m_depthTexture = null; + } + + void OnEndCameraRendering(ScriptableRenderContext ctx, Camera camera) + { + if (camera != m_camera) + return; + + // Disabled because of invalid configuration? + if (m_cameraTexture == null) + return; + + var needsBlit = m_description.NeedsBlit; + + if (needsBlit) + { + var cmd = CommandBufferPool.Get(k_blitProfilerTag); + + BlitExtended.Instance.BlitTexture(cmd, + m_cameraTexture, + m_cameraBlitTexture, + BlitExtended.GetSRGBConversion(m_description.SRGBConversion), + m_description.NeedsFlipY ? ScaleBias.FlippedY : ScaleBias.Identity); + + ctx.ExecuteCommandBuffer(cmd); + ctx.Submit(); + + CommandBufferPool.Release(cmd); + } + + if (m_description.m_copyDepth) + { + DepthCopy.instance.Execute(ctx, m_depthTexture, m_description.m_depthCopyMode); + } + + onCapture.Invoke(ctx, new Capture(needsBlit ? m_cameraBlitTexture : m_cameraTexture, m_depthTexture)); + +#if DEBUG + // A RenderTexture holds MSAA and resolved versions of its textures. + // This second naming step will name the resolved textures which Unity creates in a deferred manner. + if (!m_HasSetSecondNames) + { + m_cameraTexture.name = $"{nameof(CameraCapture)} Camera Texture {m_cameraTexture.width}x{m_cameraTexture.height}"; + if (m_cameraBlitTexture != null) + m_cameraBlitTexture.name = $"{nameof(CameraCapture)} Camera Blit Texture {m_cameraBlitTexture.width}x{m_cameraBlitTexture.height}"; + if (m_depthTexture != null) + m_depthTexture.name = $"{nameof(CameraCapture)} Depth Copy Texture {m_depthTexture.width}x{m_depthTexture.height}"; + m_HasSetSecondNames = true; + } +#endif + } + } +} diff --git a/Runtime/PipelineAbstraction/CameraCapture.cs.meta b/Runtime/PipelineAbstraction/CameraCapture.cs.meta new file mode 100644 index 0000000..d527d33 --- /dev/null +++ b/Runtime/PipelineAbstraction/CameraCapture.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9b7ec4064b174f648b9563f5d189ba29 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/CameraCaptureDescription.cs b/Runtime/PipelineAbstraction/CameraCaptureDescription.cs new file mode 100644 index 0000000..86fdd3a --- /dev/null +++ b/Runtime/PipelineAbstraction/CameraCaptureDescription.cs @@ -0,0 +1,251 @@ +using System; +using UnityEngine; +using UnityEngine.Experimental.Rendering; + +namespace Disguise.RenderStream +{ + /// + /// Holds the configuration of a component. + /// + [Serializable] + struct CameraCaptureDescription : IEquatable + { + /// + /// Describes the desired final color space of the camera capture + /// + public enum ColorSpace + { + /// + /// sRGB color primaries + linear transfer function + /// + Linear, + + /// + /// sRGB color primaries + sRGB transfer function + /// + sRGB + } + + public static CameraCaptureDescription Default = new CameraCaptureDescription() + { + m_colorSpace = ColorSpace.sRGB, + m_autoFlipY = true, + m_width = 0, + m_height = 0, + m_colorFormat = GraphicsFormat.R8G8B8A8_SRGB, + m_msaaSamples = 1, + m_depthBufferBits = 24, + m_copyDepth = false, + m_depthCopyFormat = GraphicsFormat.R32_SFloat, + m_depthCopyMode = DepthCopy.Mode.Linear01 + }; + + /// + /// The desired color space of the final output. + /// + public ColorSpace m_colorSpace; + + /// + /// Unity unifies UV coordinates across graphics APIs. This can result in RenderTextures looking flipped + /// to third-party applications in DirectX for example. + /// When true, this setting will ensure correct UV orientation for third-party applications. + /// + public bool m_autoFlipY; + + /// + /// The width of the camera render target in pixels. + /// + public int m_width; + + /// + /// The height of the camera render target in pixels. + /// + public int m_height; + + /// + /// The format of the texture that holds the camera color output. + /// + public GraphicsFormat m_colorFormat; + + /// + /// The number of MSAA samples for the camera output. + /// The final captured texture will be resolved to a non-MSAA texture. + /// + public int m_msaaSamples; + + /// + /// This can define the precision of the camera's depth attachment during the rendering of the scene. + /// + /// It doesn't necessarily need to be the same as the number of bits in . + /// + /// + public int m_depthBufferBits; + + /// + /// When true, enables the capture of the camera's depth texture. + /// + public bool m_copyDepth; + + /// + /// The format of the texture that holds the camera depth output. + /// This should normally be a single-channel float format with precision >= . + /// + public GraphicsFormat m_depthCopyFormat; + + /// + /// Sets the encoding of depth inside the captured depth texture. + /// For details on how to decode the texture in a third-party application, see . + /// + public DepthCopy.Mode m_depthCopyMode; + + public bool IsValid(out string message) + { + if (m_width == 0) + { + message = "Width is 0"; + return false; + } + + if (m_height == 0) + { + message = "Height is 0"; + return false; + } + + if (m_colorSpace == ColorSpace.Linear && GraphicsFormatUtility.IsSRGBFormat(m_colorFormat)) + { + message = "The combination of linear color space and SRGB texture format is not supported"; + return false; + } + + message = null; + return true; + } + + /// + /// When true, the final capture is a processed version of the . + /// This depends on and . + /// + public bool NeedsBlit => NeedsFlipY || NeedsSRGBConversion; + + /// + /// When true, the final capture has flipped Y UV coordinates relative to + /// depending on and the current graphics API. + /// + public bool NeedsFlipY => m_autoFlipY && SystemInfo.graphicsUVStartsAtTop; + + /// + /// We sometimes need to perform a blit to convert from to . + /// This is a function of the and of the camera capture. + /// + public bool NeedsSRGBConversion + { + get + { + var srcDescriptor = SRGBConversions.GetAutoDescriptor(m_colorFormat); + var srcColorSpace = srcDescriptor.colorSpace switch + { + SRGBConversions.Space.Linear => ColorSpace.Linear, + SRGBConversions.Space.sRGB => ColorSpace.sRGB, + _ => throw new ArgumentOutOfRangeException() + }; + + return srcColorSpace != m_colorSpace; + } + } + + /// + public SRGBConversions.Conversion SRGBConversion + { + get + { + var srcDescriptor = SRGBConversions.GetAutoDescriptor(m_colorFormat); + + var dstColorSpace = m_colorSpace switch + { + ColorSpace.Linear => SRGBConversions.Space.Linear, + ColorSpace.sRGB => SRGBConversions.Space.sRGB, + _ => throw new ArgumentOutOfRangeException() + }; + var dstDescriptor = new SRGBConversions.Descriptor(dstColorSpace, SRGBConversions.GetTextureFormat(m_colorFormat)); + + return SRGBConversions.GetConversion(srcDescriptor, dstDescriptor); + } + } + + public bool CameraTextureIsMSAA => m_msaaSamples > 1; + + /// + /// Describes the texture to use for . + /// + public RenderTextureDescriptor GetCameraDescriptor() + { + var descriptor = new RenderTextureDescriptor(m_width, m_height, m_colorFormat, m_depthBufferBits, 1); + descriptor.msaaSamples = m_msaaSamples; + + return descriptor; + } + + /// + /// Describes the texture to contain the flipped and/or color space converted version of the . + /// + public RenderTextureDescriptor GetCameraBlitDescriptor() + { + var descriptor = GetCameraDescriptor(); + descriptor.depthBufferBits = 0; + descriptor.msaaSamples = 1; + + return descriptor; + } + + /// + /// Describes the texture to use for storing the depth capture. + /// + public RenderTextureDescriptor GetDepthCopyDescriptor() + { + return new RenderTextureDescriptor(m_width, m_height, m_depthCopyFormat, 0, 1); + } + + public override bool Equals(object obj) + { + return obj is CameraCaptureDescription other && Equals(other); + } + + public override int GetHashCode() + { + var hashCode = new HashCode(); + + hashCode.Add((int)m_colorSpace); + hashCode.Add(m_autoFlipY ? 1 : 0); + hashCode.Add(m_width); + hashCode.Add(m_height); + hashCode.Add((int)m_colorFormat); + hashCode.Add(m_msaaSamples); + hashCode.Add(m_depthBufferBits); + hashCode.Add(m_copyDepth ? 1 : 0); + hashCode.Add((int)m_depthCopyFormat); + hashCode.Add((int)m_depthCopyMode); + + return hashCode.ToHashCode(); + } + + public bool Equals(CameraCaptureDescription other) + { + return + m_colorSpace == other.m_colorSpace && + m_autoFlipY == other.m_autoFlipY && + m_width == other.m_width && + m_height == other.m_height && + m_colorFormat == other.m_colorFormat && + m_msaaSamples == other.m_msaaSamples && + m_depthBufferBits == other.m_depthBufferBits && + m_copyDepth == other.m_copyDepth && + m_depthCopyFormat == other.m_depthCopyFormat && + m_depthCopyMode == other.m_depthCopyMode; + } + + public static bool operator ==(CameraCaptureDescription lhs, CameraCaptureDescription rhs) => lhs.Equals(rhs); + + public static bool operator !=(CameraCaptureDescription lhs, CameraCaptureDescription rhs) => !(lhs == rhs); + } +} \ No newline at end of file diff --git a/Runtime/PipelineAbstraction/CameraCaptureDescription.cs.meta b/Runtime/PipelineAbstraction/CameraCaptureDescription.cs.meta new file mode 100644 index 0000000..72a85d3 --- /dev/null +++ b/Runtime/PipelineAbstraction/CameraCaptureDescription.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 977542ff79029214f9e92a056e4500b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/CameraCapturePresenter.cs b/Runtime/PipelineAbstraction/CameraCapturePresenter.cs new file mode 100644 index 0000000..cf7671e --- /dev/null +++ b/Runtime/PipelineAbstraction/CameraCapturePresenter.cs @@ -0,0 +1,108 @@ +using System; +using UnityEngine; +using UnityEngine.Assertions; + +namespace Disguise.RenderStream +{ + /// + /// + /// Blits this 's to the local screen. + /// A number of strategies are available to handle + /// the size and aspect ratio differences between the two surfaces. + /// + /// + /// + /// is disabled when is enabled to avoid flipping twice. + /// + /// + /// + /// is automatically set to match . + /// + /// + /// + /// and are responsible for + /// adjusting the mouse coordinates to account for the blit. + /// + /// + [ExecuteAlways] + class CameraCapturePresenter : Presenter + { + /// + /// Describes which texture to present. + /// + public enum Mode + { + Color, + Depth + } + + [SerializeField] + Mode m_mode; + + CameraCapture m_cameraCapture; + + public CameraCapture cameraCapture + { + get => m_cameraCapture; + set + { + m_cameraCapture = value; + Assign(m_cameraCapture); + } + } + + protected override void OnEnable() + { + base.OnEnable(); + + if (m_cameraCapture == null) + m_cameraCapture = GetComponent(); + } + + protected override void Update() + { + Assign(m_cameraCapture); + + base.Update(); + } + + void Assign(CameraCapture capture) + { + if (capture == null) + { + source = null; + return; + } + + if (m_cameraCapture.description.m_autoFlipY && autoFlipY) + { + autoFlipY = false; + + Debug.LogWarning($"Disabled {nameof(CameraCapturePresenter)}.{nameof(CameraCapturePresenter.autoFlipY)}" + + $"because it's already enabled in the sibling {nameof(CameraCapture)} component"); + } + + sourceColorSpace = m_cameraCapture.description.m_colorSpace switch + { + CameraCaptureDescription.ColorSpace.Linear => SourceColorSpace.Linear, + CameraCaptureDescription.ColorSpace.sRGB => SourceColorSpace.sRGB, + _ => throw new ArgumentOutOfRangeException() + }; + + switch (m_mode) + { + case Mode.Color: + source = capture.cameraTexture; + break; + + case Mode.Depth: + Assert.IsTrue(capture.description.m_copyDepth); + source = capture.depthTexture; + break; + + default: + throw new ArgumentOutOfRangeException(); + } + } + } +} diff --git a/Runtime/PipelineAbstraction/CameraCapturePresenter.cs.meta b/Runtime/PipelineAbstraction/CameraCapturePresenter.cs.meta new file mode 100644 index 0000000..43fd937 --- /dev/null +++ b/Runtime/PipelineAbstraction/CameraCapturePresenter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a76b687eeb2f3eb4ab0f3cdb0e21cb99 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/DepthCopy.cs b/Runtime/PipelineAbstraction/DepthCopy.cs new file mode 100644 index 0000000..6b755e1 --- /dev/null +++ b/Runtime/PipelineAbstraction/DepthCopy.cs @@ -0,0 +1,105 @@ +using UnityEngine; +using UnityEngine.Assertions; +using UnityEngine.Rendering; + +namespace Disguise.RenderStream +{ + /// + /// Copies a camera's depth into a texture. + /// + class DepthCopy + { +#if UNITY_PIPELINE_HDRP && HDRP_VERSION_SUPPORTED + public const string ShaderName = "Hidden/Disguise/RenderStream/DepthCopyHDRP"; +#elif UNITY_PIPELINE_URP && URP_VERSION_SUPPORTED + public const string ShaderName = "Hidden/Disguise/RenderStream/DepthCopyURP"; +#else + public const string ShaderName = null; +#endif + + /// + /// Represents the encoding of depth inside the captured depth texture. + /// The modes are equivalent to the depth sampling modes of the Shader Graph's Scene Depth Node: + /// https://docs.unity3d.com/Packages/com.unity.shadergraph@15.0/manual/Scene-Depth-Node.html. + /// + public enum Mode + { + Raw, + Eye, + Linear01 + } + + static int GetPass(Mode mode) + { + return (int)mode; + } + + const string k_profilerTag = "Disguise Depth Copy"; + + public static DepthCopy instance { get; } + + static DepthCopy() + { + instance = new DepthCopy(); + } + + readonly Material m_material; + + DepthCopy() + { +#if !(UNITY_PIPELINE_HDRP && HDRP_VERSION_SUPPORTED) && !(UNITY_PIPELINE_URP && URP_VERSION_SUPPORTED) + Debug.LogError($"No supported render pipeline was found for {nameof(DepthCopy)}."); +#endif + + var shader = Shader.Find(ShaderName); + if (shader != null) + { + m_material = CoreUtils.CreateEngineMaterial(shader); + } + + Assert.IsTrue(shader != null && m_material != null, $"Couldn't load the shader resources for {nameof(DepthCopy)}"); + } + + /// + /// Performs the copy using the SRP's currently active camera and the provided . + /// + /// + /// The texture to which the depth will be written to. + /// The texture to which the depth will be written to. + public void Execute(ScriptableRenderContext context, RenderTexture depthOutput, Mode mode) + { + Assert.IsNotNull(depthOutput); + + ValidatePipeline(); + + var cmd = CommandBufferPool.Get(k_profilerTag); + IssueCommands(cmd, depthOutput, mode); + + context.ExecuteCommandBuffer(cmd); + context.Submit(); + + CommandBufferPool.Release(cmd); + } + + void IssueCommands(CommandBuffer cmd, RenderTexture depthOutput, Mode mode) + { + Assert.IsNotNull(m_material); + + CoreUtils.DrawFullScreen(cmd, m_material, depthOutput, shaderPassId: GetPass(mode)); + } + + void ValidatePipeline() + { + // HDRP cameras always have a depth texture + +#if UNITY_PIPELINE_URP && URP_VERSION_SUPPORTED + var pipeline = GraphicsSettings.currentRenderPipeline as UnityEngine.Rendering.Universal.UniversalRenderPipelineAsset; + Assert.IsNotNull(pipeline); + if (!pipeline.supportsCameraDepthTexture) + { + Debug.LogError($"Can't copy camera depth because the Depth Texture option isn't enabled in the current {nameof(UnityEngine.Rendering.Universal.UniversalRenderPipelineAsset)}"); + } +#endif + } + } +} diff --git a/Runtime/PipelineAbstraction/DepthCopy.cs.meta b/Runtime/PipelineAbstraction/DepthCopy.cs.meta new file mode 100644 index 0000000..2835862 --- /dev/null +++ b/Runtime/PipelineAbstraction/DepthCopy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 90f48b6ac2eaf96479ccc578bfcf2e3a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/Disguise.RenderStream.PipelineAbstraction.asmdef b/Runtime/PipelineAbstraction/Disguise.RenderStream.PipelineAbstraction.asmdef new file mode 100644 index 0000000..001445f --- /dev/null +++ b/Runtime/PipelineAbstraction/Disguise.RenderStream.PipelineAbstraction.asmdef @@ -0,0 +1,29 @@ +{ + "name": "Disguise.RenderStream.PipelineAbstraction", + "rootNamespace": "", + "references": [ + "Unity.RenderPipelines.Core.Runtime", + "Unity.RenderPipelines.HighDefinition.Runtime", + "Unity.RenderPipelines.Universal.Runtime" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [ + { + "name": "com.unity.render-pipelines.high-definition", + "expression": "13.1.8", + "define": "HDRP_VERSION_SUPPORTED" + }, + { + "name": "com.unity.render-pipelines.universal", + "expression": "13.1.8", + "define": "URP_VERSION_SUPPORTED" + } + ], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Runtime/PipelineAbstraction/Disguise.RenderStream.PipelineAbstraction.asmdef.meta b/Runtime/PipelineAbstraction/Disguise.RenderStream.PipelineAbstraction.asmdef.meta new file mode 100644 index 0000000..9f7edfe --- /dev/null +++ b/Runtime/PipelineAbstraction/Disguise.RenderStream.PipelineAbstraction.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 690973e5dff7e66418d830800339e71f +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/Presenter.cs b/Runtime/PipelineAbstraction/Presenter.cs new file mode 100644 index 0000000..dd791c5 --- /dev/null +++ b/Runtime/PipelineAbstraction/Presenter.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections; +using UnityEngine; +using UnityEngine.Experimental.Rendering; +using UnityEngine.Rendering; + +namespace Disguise.RenderStream +{ + /// + /// + /// Blits a texture to the local screen. + /// A number of strategies are available to handle + /// the size and aspect ratio differences between the two surfaces. + /// + /// + /// + /// and are responsible for + /// adjusting the mouse coordinates to account for the blit. + /// + /// + /// + /// Assumes that the local screen is the main display. + /// Modify this class for local multi-monitor specifics. + /// Doesn't support HDR display output. + /// + /// + [ExecuteAlways] + class Presenter : MonoBehaviour + { + /// + /// The color space of the 's texture. + /// + public enum SourceColorSpace + { + /// + /// Blit directly without any color space conversions. + /// + Unspecified = -2, + + /// + /// Detect the color space based on the 's texture's . + /// sRGB formats are assumed to contain sRGB data, while other formats are assumed to contain linear data. + /// + Auto = -1, + + /// + /// sRGB color primaries + linear transfer function + /// + Linear = CameraCaptureDescription.ColorSpace.Linear, + + /// + /// sRGB color primaries + sRGB transfer function + /// + sRGB = CameraCaptureDescription.ColorSpace.sRGB, + } + + const string k_profilerTag = "Disguise Presenter"; + const string k_profilerClearTag = "Disguise Presenter Clear"; + + [SerializeField] + RenderTexture m_source; + + [SerializeField] + SourceColorSpace m_sourceColorSpace = SourceColorSpace.Auto; + + [SerializeField] + BlitStrategy.Strategy m_strategy = BlitStrategy.Strategy.Fill; + + [SerializeField] + bool m_autoFlipY = true; + + [SerializeField] + bool m_clearScreen = true; + + Coroutine m_FrameLoop; + + /// + /// Describes how to handle the size and aspect ratio differences between the and the screen. + /// + public BlitStrategy.Strategy strategy + { + get => m_strategy; + set => m_strategy = value; + } + + /// + /// On platforms such as DX12 the texture needs to be flipped before being presented to the screen. + /// + public bool autoFlipY + { + get => m_autoFlipY; + set => m_autoFlipY = value; + } + + /// + /// The texture to present. Can be any 2D texture. + /// + public RenderTexture source + { + get => m_source; + set => m_source = value; + } + + /// + /// The color space of the texture. + /// should manage most cases. + /// + public SourceColorSpace sourceColorSpace + { + get => m_sourceColorSpace; + set => m_sourceColorSpace = value; + } + + /// + /// When Unity has no onscreen cameras the screen might never be cleared. + /// + public bool clearScreen + { + get => m_clearScreen; + set => m_clearScreen = value; + } + + public bool IsValid => m_source != null; + + public Vector2 sourceSize => new Vector2(m_source.width, m_source.height); + + public Vector2 targetSize => new Vector2(Screen.width, Screen.height); + + /// + /// Can override to setup . + /// + protected virtual void OnEnable() + { +#if DISGUISE_UNITY_USE_HDR_DISPLAY + Debug.LogWarning($"{nameof(Presenter)} only supports SDR output, but HDR Display Output is allowed in the Project Settings."); +#endif + + m_FrameLoop = StartCoroutine(FrameLoop()); + } + + protected virtual void OnDisable() + { + // When no Cameras are rendering to the screen the previous frame + // will remain until the next clear. Force a clear to avoid this: + if (m_clearScreen) + ClearScreen(); + + StopCoroutine(m_FrameLoop); + m_FrameLoop = null; + } + + protected virtual void Update() + { + OnBeginFrame(); + } + + /// + /// Get the destination UV transformations to pass to the API. + /// + /// + /// When true, the return value isn't adjusted for the graphics API's UV representation. + /// This is useful for UI which only needs a CPU representation of the bounds. + /// + public ScaleBias GetScaleBias(bool skipAutoFlip) + { + var scaleBias = BlitStrategy.DoStrategy(m_strategy, sourceSize, targetSize); + + if (autoFlipY && !skipAutoFlip && SystemInfo.graphicsUVStartsAtTop) + scaleBias = ScaleBias.FlipY(scaleBias); + + return scaleBias; + } + + /// + /// Resolves the color space conversion to apply based on + /// the source texture and the main display's backbuffer. + /// + BlitExtended.ColorSpaceConversion GetColorSpaceConversion() + { + if (m_sourceColorSpace == SourceColorSpace.Unspecified) + return BlitExtended.ColorSpaceConversion.None; + + var sourceDescriptor = m_sourceColorSpace switch + { + SourceColorSpace.Auto => SRGBConversions.GetAutoDescriptor(m_source), + SourceColorSpace.Linear => new SRGBConversions.Descriptor(SRGBConversions.Space.Linear, SRGBConversions.GetTextureFormat(m_source)), + SourceColorSpace.sRGB => new SRGBConversions.Descriptor(SRGBConversions.Space.sRGB, SRGBConversions.GetTextureFormat(m_source)), + _ => throw new ArgumentOutOfRangeException() + }; + + var mainDisplayDescriptor = SRGBConversions.GetDisplayDescriptor(Display.main); + var conversion = SRGBConversions.GetConversion(sourceDescriptor, mainDisplayDescriptor); + return BlitExtended.GetSRGBConversion(conversion); + } + + void IssueCommands(CommandBuffer cmd) + { + const RenderTexture mainDisplay = default; + CoreUtils.SetRenderTarget(cmd, mainDisplay); + + var srcScaleBias = ScaleBias.Identity; + var dstScaleBias = GetScaleBias(false); + + BlitExtended.Instance.BlitQuad(cmd, m_source, GetColorSpaceConversion(), srcScaleBias, dstScaleBias); + } + + static void ClearScreen() + { + var cmd = CommandBufferPool.Get(k_profilerClearTag); + + const RenderTexture mainDisplay = default; + CoreUtils.SetRenderTarget(cmd, mainDisplay); + cmd.ClearRenderTarget(false, true, Color.black); + + Graphics.ExecuteCommandBuffer(cmd); + + CommandBufferPool.Release(cmd); + } + + void OnBeginFrame() + { + if (clearScreen) + ClearScreen(); + } + + IEnumerator FrameLoop() + { + while (true) + { + yield return new WaitForEndOfFrame(); + + if (!IsValid) + continue; + + var cmd = CommandBufferPool.Get(k_profilerTag); + IssueCommands(cmd); + + Graphics.ExecuteCommandBuffer(cmd); + + CommandBufferPool.Release(cmd); + } + } + } +} diff --git a/Runtime/PipelineAbstraction/Presenter.cs.meta b/Runtime/PipelineAbstraction/Presenter.cs.meta new file mode 100644 index 0000000..73df481 --- /dev/null +++ b/Runtime/PipelineAbstraction/Presenter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e11750ce543afca48962fc383425bc33 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/SRGBConversions.cs b/Runtime/PipelineAbstraction/SRGBConversions.cs new file mode 100644 index 0000000..d5e9105 --- /dev/null +++ b/Runtime/PipelineAbstraction/SRGBConversions.cs @@ -0,0 +1,195 @@ +using System; +using UnityEngine; +using UnityEngine.Experimental.Rendering; + +namespace Disguise.RenderStream +{ + /// + /// + /// Makes explicit the hardware and software linear/sRGB conversion rules: + /// https://learn.microsoft.com/en-us/windows/win32/direct3d10/d3d10-graphics-programming-guide-resources-data-conversion + /// + /// + /// The following operations will trigger a hardware conversion: + /// * Copying between float and sRGB textures + /// * Shader sampling from an sRGB texture + /// * Shader output into an sRGB texture + /// + /// + /// Assumes only blitting between textures, never copying which is more complex. + /// Blitting will trigger a hardware conversion between _UNORM and _SRGB textures for example, while copying will not. + /// + /// + static class SRGBConversions + { + /// + /// Describes the color space of data inside a texture + /// + public enum Space + { + /// + /// sRGB color primaries + linear transfer function + /// + Linear, + + /// + /// sRGB color primaries + sRGB transfer function + /// + sRGB + } + + /// + /// Represents a software color space conversion + /// + public enum Conversion + { + /// + /// No hardware or software conversion + /// + None, + + /// + /// to applied by the hardware during a blit operation + /// + HardwareLinearToSRGB, + + /// + /// to applied by the hardware during a blit operation + /// + HardwareSRGBToLinear, + + /// + /// to applied by the shader code during a blit operation + /// + SoftwareLinearToSRGB, + + /// + /// to applied in by the shader code during a blit operation + /// + SoftwareSRGBToLinear + } + + /// + /// Represents the texture format + /// + public enum Texture + { + /// + /// An sRGB texture (ex ) + /// + sRGB, + + /// + /// Any non-sRGB texture + /// + Other + } + + /// + /// Represents the a texture and its contents + /// + public readonly struct Descriptor + { + public Space colorSpace { get; } + public Texture textureFormat { get; } + + public Descriptor(Space colorSpace, Texture textureFormat) + { + if (colorSpace == Space.Linear && textureFormat == Texture.sRGB) + { + throw new ArgumentException($"{nameof(SRGBConversions)}: The combination of sRGB texture format" + + $"and linear color space is not supported"); + } + + this.colorSpace = colorSpace; + this.textureFormat = textureFormat; + } + } + + public static Texture GetTextureFormat(RenderTexture rt) + { + return GetTextureFormat(rt.graphicsFormat); + } + + public static Texture GetTextureFormat(GraphicsFormat format) + { + return GraphicsFormatUtility.IsSRGBFormat(format) + ? Texture.sRGB + : Texture.Other; + } + + public static Texture GetTextureFormat(Display display) + { + return display.requiresSrgbBlitToBackbuffer + ? Texture.Other + : Texture.sRGB; + } + + /// + /// Returns the descriptor for a RenderTexture generated by Unity during frame rendering. + /// An sRGB texture is assumed to contain data. + /// Any other texture is assumed to contain data. + /// + public static Descriptor GetAutoDescriptor(RenderTexture rt) + { + return GetAutoDescriptor(rt.graphicsFormat); + } + + /// + public static Descriptor GetAutoDescriptor(GraphicsFormat format) + { + var textureFormat = GetTextureFormat(format); + + return textureFormat switch + { + Texture.sRGB => new Descriptor(Space.sRGB, textureFormat), + Texture.Other => new Descriptor(Space.Linear, textureFormat), + _ => throw new ArgumentOutOfRangeException() + }; + } + + public static Descriptor GetDisplayDescriptor(Display display) + { + var textureFormat = GetTextureFormat(display); + return new Descriptor(Space.sRGB, textureFormat); + } + + /// + /// The software conversion to apply inside a blit shader to ensure color consistency between and . + /// + /// Invalid descriptor. + public static Conversion GetConversion(Descriptor src, Descriptor dst) + { + (Space srcColor, Texture srcTex, Space dstColor, Texture dstTex) x = (src.colorSpace, src.textureFormat, dst.colorSpace, dst.textureFormat); + + return x switch + { + // Shader sampler color space: Linear + { srcColor: Space.sRGB, srcTex: Texture.sRGB, dstColor: Space.sRGB, dstTex: Texture.sRGB } => Conversion.None, + { srcColor: Space.sRGB, srcTex: Texture.sRGB, dstColor: Space.sRGB, dstTex: Texture.Other } => Conversion.SoftwareLinearToSRGB, + { srcColor: Space.sRGB, srcTex: Texture.sRGB, dstColor: Space.Linear, dstTex: Texture.sRGB } => throw new InvalidOperationException(), + { srcColor: Space.sRGB, srcTex: Texture.sRGB, dstColor: Space.Linear, dstTex: Texture.Other } => Conversion.HardwareSRGBToLinear, + + // Shader sampler color space: sRGB + { srcColor: Space.sRGB, srcTex: Texture.Other, dstColor: Space.sRGB, dstTex: Texture.sRGB } => Conversion.SoftwareSRGBToLinear, + { srcColor: Space.sRGB, srcTex: Texture.Other, dstColor: Space.sRGB, dstTex: Texture.Other } => Conversion.None, + { srcColor: Space.sRGB, srcTex: Texture.Other, dstColor: Space.Linear, dstTex: Texture.sRGB } => throw new InvalidOperationException(), + { srcColor: Space.sRGB, srcTex: Texture.Other, dstColor: Space.Linear, dstTex: Texture.Other } => Conversion.SoftwareSRGBToLinear, + + // Shader sampler color space: Linear + { srcColor: Space.Linear, srcTex: Texture.Other, dstColor: Space.sRGB, dstTex: Texture.sRGB } => Conversion.HardwareLinearToSRGB, + { srcColor: Space.Linear, srcTex: Texture.Other, dstColor: Space.sRGB, dstTex: Texture.Other } => Conversion.SoftwareLinearToSRGB, + { srcColor: Space.Linear, srcTex: Texture.Other, dstColor: Space.Linear, dstTex: Texture.sRGB } => throw new InvalidOperationException(), + { srcColor: Space.Linear, srcTex: Texture.Other, dstColor: Space.Linear, dstTex: Texture.Other } => Conversion.None, + + // Invalid source descriptor + { srcColor: Space.Linear, srcTex: Texture.sRGB, dstColor: Space.sRGB, dstTex: Texture.sRGB } => throw new InvalidOperationException(), + { srcColor: Space.Linear, srcTex: Texture.sRGB, dstColor: Space.sRGB, dstTex: Texture.Other } => throw new InvalidOperationException(), + { srcColor: Space.Linear, srcTex: Texture.sRGB, dstColor: Space.Linear, dstTex: Texture.sRGB } => throw new InvalidOperationException(), + { srcColor: Space.Linear, srcTex: Texture.sRGB, dstColor: Space.Linear, dstTex: Texture.Other } => throw new InvalidOperationException(), + + _ => throw new ArgumentOutOfRangeException() + }; + } + } +} diff --git a/Runtime/PipelineAbstraction/SRGBConversions.cs.meta b/Runtime/PipelineAbstraction/SRGBConversions.cs.meta new file mode 100644 index 0000000..fbc08b7 --- /dev/null +++ b/Runtime/PipelineAbstraction/SRGBConversions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 31dc12b3e1b957e45a34d5664229a329 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/Shaders.meta b/Runtime/PipelineAbstraction/Shaders.meta new file mode 100644 index 0000000..c9ac1a7 --- /dev/null +++ b/Runtime/PipelineAbstraction/Shaders.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ac874ce0a7134e44cbb5d4926a3ba47a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/Shaders/BlitExtendedCommon.cginc b/Runtime/PipelineAbstraction/Shaders/BlitExtendedCommon.cginc new file mode 100644 index 0000000..6692915 --- /dev/null +++ b/Runtime/PipelineAbstraction/Shaders/BlitExtendedCommon.cginc @@ -0,0 +1,81 @@ +#ifndef BLIT_EXTENDED_COMMON_INCLUDED +#define BLIT_EXTENDED_COMMON_INCLUDED + +// Based on Blit.hlsl from com.unity.render-pipelines.core + +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl" + +SamplerState sampler_LinearClamp; + +TEXTURE2D(_BlitTexture); + +uniform float4 _BlitScaleBias; +uniform float4 _BlitScaleBiasRt; + +struct Attributes +{ + uint vertexID : SV_VertexID; + UNITY_VERTEX_INPUT_INSTANCE_ID +}; + +struct Varyings +{ + float4 positionCS : SV_Position; + float2 texcoord : TEXCOORD0; + UNITY_VERTEX_OUTPUT_STEREO +}; + +Varyings Vert(Attributes input) +{ + Varyings output; + UNITY_SETUP_INSTANCE_ID(input); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); + + float4 pos = GetFullScreenTriangleVertexPosition(input.vertexID); + float2 uv = GetFullScreenTriangleTexCoord(input.vertexID); + + output.positionCS = pos; + output.texcoord = uv * _BlitScaleBias.xy + _BlitScaleBias.zw; + + return output; +} + +Varyings VertQuad(Attributes input) +{ + Varyings output; + UNITY_SETUP_INSTANCE_ID(input); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); + + float4 pos = GetQuadVertexPosition(input.vertexID); + float2 uv = GetQuadTexCoord(input.vertexID); + + output.positionCS = pos * float4(_BlitScaleBiasRt.x, _BlitScaleBiasRt.y, 1, 1) + float4(_BlitScaleBiasRt.z, _BlitScaleBiasRt.w, 0, 0); + output.positionCS.xy = output.positionCS.xy * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f); //convert to -1..1 + output.texcoord = uv * _BlitScaleBias.xy + _BlitScaleBias.zw; + return output; +} + +float4 FragBlit(Varyings input) +{ + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); + return SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, input.texcoord.xy); +} + +float4 FragNoConversion(Varyings input) : SV_Target +{ + return FragBlit(input); +} + +float4 FragBlitLinearToSRGB(Varyings input) : SV_Target +{ + return LinearToSRGB(FragBlit(input)); +} + +float4 FragBlitSRGBToLinear(Varyings input) : SV_Target +{ + return SRGBToLinear(FragBlit(input)); +} + +#endif diff --git a/Runtime/PipelineAbstraction/Shaders/BlitExtendedCommon.cginc.meta b/Runtime/PipelineAbstraction/Shaders/BlitExtendedCommon.cginc.meta new file mode 100644 index 0000000..7c0ff4e --- /dev/null +++ b/Runtime/PipelineAbstraction/Shaders/BlitExtendedCommon.cginc.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a4c81bfb2186b4b448e618368d08778c +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/Shaders/BlitExtendedHDRP.shader b/Runtime/PipelineAbstraction/Shaders/BlitExtendedHDRP.shader new file mode 100644 index 0000000..1230cbd --- /dev/null +++ b/Runtime/PipelineAbstraction/Shaders/BlitExtendedHDRP.shader @@ -0,0 +1,87 @@ +Shader "Hidden/Disguise/RenderStream/BlitExtendedHDRP" +{ + HLSLINCLUDE + #include "BlitExtendedCommon.cginc" + ENDHLSL + + SubShader + { + PackageRequirements + { + "com.unity.render-pipelines.high-definition" : "13.1.8" + } + + Tags + { + "RenderPipeline" = "HDRenderPipeline" + } + + // Fullscreen triangle: No color space conversion + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment FragNoConversion + ENDHLSL + } + + // Fullscreen triangle: Linear to sRGB + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment FragBlitLinearToSRGB + ENDHLSL + } + + // Fullscreen triangle: sRGB to Linear + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment FragBlitSRGBToLinear + ENDHLSL + } + + // Quad: No color space conversion + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex VertQuad + #pragma fragment FragNoConversion + ENDHLSL + } + + // Quad: Linear to sRGB + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex VertQuad + #pragma fragment FragBlitLinearToSRGB + ENDHLSL + } + + // Quad: sRGB to Linear + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex VertQuad + #pragma fragment FragBlitSRGBToLinear + ENDHLSL + } + } + + Fallback Off +} diff --git a/Runtime/PipelineAbstraction/Shaders/BlitExtendedHDRP.shader.meta b/Runtime/PipelineAbstraction/Shaders/BlitExtendedHDRP.shader.meta new file mode 100644 index 0000000..254b297 --- /dev/null +++ b/Runtime/PipelineAbstraction/Shaders/BlitExtendedHDRP.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 27ce52b0f1f704046b4aaa29dfc3f98a +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/Shaders/BlitExtendedURP.shader b/Runtime/PipelineAbstraction/Shaders/BlitExtendedURP.shader new file mode 100644 index 0000000..27f866d --- /dev/null +++ b/Runtime/PipelineAbstraction/Shaders/BlitExtendedURP.shader @@ -0,0 +1,87 @@ +Shader "Hidden/Disguise/RenderStream/BlitExtendedURP" +{ + HLSLINCLUDE + #include "BlitExtendedCommon.cginc" + ENDHLSL + + SubShader + { + PackageRequirements + { + "com.unity.render-pipelines.universal" : "13.1.8" + } + + Tags + { + "RenderPipeline" = "UniversalRenderPipeline" + } + + // Fullscreen triangle: No color space conversion + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment FragNoConversion + ENDHLSL + } + + // Fullscreen triangle: Linear to sRGB + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment FragBlitLinearToSRGB + ENDHLSL + } + + // Fullscreen triangle: sRGB to Linear + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment FragBlitSRGBToLinear + ENDHLSL + } + + // Quad: No color space conversion + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex VertQuad + #pragma fragment FragNoConversion + ENDHLSL + } + + // Quad: Linear to sRGB + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex VertQuad + #pragma fragment FragBlitLinearToSRGB + ENDHLSL + } + + // Quad: sRGB to Linear + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex VertQuad + #pragma fragment FragBlitSRGBToLinear + ENDHLSL + } + } + + Fallback Off +} diff --git a/Runtime/PipelineAbstraction/Shaders/BlitExtendedURP.shader.meta b/Runtime/PipelineAbstraction/Shaders/BlitExtendedURP.shader.meta new file mode 100644 index 0000000..47eda0e --- /dev/null +++ b/Runtime/PipelineAbstraction/Shaders/BlitExtendedURP.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 020876b469453ed4a92d720223686660 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/Shaders/DepthCopyCommon.cginc b/Runtime/PipelineAbstraction/Shaders/DepthCopyCommon.cginc new file mode 100644 index 0000000..24b056b --- /dev/null +++ b/Runtime/PipelineAbstraction/Shaders/DepthCopyCommon.cginc @@ -0,0 +1,49 @@ +#ifndef DEPTH_COPY_COMMON_INCLUDED +#define DEPTH_COPY_COMMON_INCLUDED + +struct Attributes +{ + uint vertexID : SV_VertexID; + UNITY_VERTEX_INPUT_INSTANCE_ID +}; + +struct Varyings +{ + float4 positionCS : SV_Position; + float2 texcoord : TEXCOORD0; + UNITY_VERTEX_OUTPUT_STEREO +}; + +Varyings Vert(Attributes input) +{ + Varyings output; + UNITY_SETUP_INSTANCE_ID(input); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); + output.positionCS = GetFullScreenTriangleVertexPosition(input.vertexID); + output.texcoord = GetFullScreenTriangleTexCoord(input.vertexID); + return output; +} + +float2 FragUV(Varyings input) +{ + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); + return input.texcoord.xy; + // Up to this point this is minimal screen-space blit boilerplate +} + +float FragRaw(Varyings input) : SV_Target +{ + return SceneDepth_Raw(FragUV(input)); +} + +float FragEye(Varyings input) : SV_Target +{ + return SceneDepth_Eye(FragUV(input)); +} + +float FragLinear01(Varyings input) : SV_Target +{ + return SceneDepth_Linear01(FragUV(input)); +} + +#endif diff --git a/Runtime/PipelineAbstraction/Shaders/DepthCopyCommon.cginc.meta b/Runtime/PipelineAbstraction/Shaders/DepthCopyCommon.cginc.meta new file mode 100644 index 0000000..45c4fc9 --- /dev/null +++ b/Runtime/PipelineAbstraction/Shaders/DepthCopyCommon.cginc.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 333910e3cfaa75e459fd6022b530a01e +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/Shaders/DepthCopyHDRP.shader b/Runtime/PipelineAbstraction/Shaders/DepthCopyHDRP.shader new file mode 100644 index 0000000..655a1c0 --- /dev/null +++ b/Runtime/PipelineAbstraction/Shaders/DepthCopyHDRP.shader @@ -0,0 +1,65 @@ +Shader "Hidden/Disguise/RenderStream/DepthCopyHDRP" +{ + HLSLINCLUDE + #define REQUIRE_DEPTH_TEXTURE + + #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" + #include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl" + + // Define for DepthCopyNodes.cginc + #define GetDepthForDisguise(uv) SampleCameraDepth(uv) + + // Defines depth operations + #include "DepthCopyNodes.cginc" + + // Contains vertex and fragment functions + #include "DepthCopyCommon.cginc" + ENDHLSL + + SubShader + { + PackageRequirements + { + "com.unity.render-pipelines.high-definition" : "13.1.8" + } + + Tags + { + "RenderPipeline" = "HDRenderPipeline" + } + + // Raw + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment FragRaw + ENDHLSL + } + + // Eye + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment FragEye + ENDHLSL + } + + // Linear01 + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment FragLinear01 + ENDHLSL + } + } + Fallback Off +} diff --git a/Runtime/PipelineAbstraction/Shaders/DepthCopyHDRP.shader.meta b/Runtime/PipelineAbstraction/Shaders/DepthCopyHDRP.shader.meta new file mode 100644 index 0000000..dfc52ee --- /dev/null +++ b/Runtime/PipelineAbstraction/Shaders/DepthCopyHDRP.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 7fdc0bfa484fe8345a2cd6fc532da2c7 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/Shaders/DepthCopyNodes.cginc b/Runtime/PipelineAbstraction/Shaders/DepthCopyNodes.cginc new file mode 100644 index 0000000..96364cd --- /dev/null +++ b/Runtime/PipelineAbstraction/Shaders/DepthCopyNodes.cginc @@ -0,0 +1,31 @@ +#ifndef DEPTH_COPY_NODES_INCLUDED +#define DEPTH_COPY_NODES_INCLUDED + +// Based on Shader Graph's SceneDepthNode.cs +// GetDepthForDisguise should be pre-defined + +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" + +float SceneDepth_Linear01(float2 uv) +{ + return Linear01Depth(GetDepthForDisguise(uv.xy), _ZBufferParams); +} + +float SceneDepth_Raw(float2 uv) +{ + return GetDepthForDisguise(uv.xy); +} + +float SceneDepth_Eye(float2 uv) +{ + if (unity_OrthoParams.w == 1.0) + { + return LinearEyeDepth(ComputeWorldSpacePosition(uv.xy, GetDepthForDisguise(uv.xy), UNITY_MATRIX_I_VP), UNITY_MATRIX_V); + } + else + { + return LinearEyeDepth(GetDepthForDisguise(uv.xy), _ZBufferParams); + } +} + +#endif diff --git a/Runtime/PipelineAbstraction/Shaders/DepthCopyNodes.cginc.meta b/Runtime/PipelineAbstraction/Shaders/DepthCopyNodes.cginc.meta new file mode 100644 index 0000000..2a1c2b2 --- /dev/null +++ b/Runtime/PipelineAbstraction/Shaders/DepthCopyNodes.cginc.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7974433405d6ef04a9c33bf7ccd2e2bf +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/Shaders/DepthCopyURP.shader b/Runtime/PipelineAbstraction/Shaders/DepthCopyURP.shader new file mode 100644 index 0000000..87b845a --- /dev/null +++ b/Runtime/PipelineAbstraction/Shaders/DepthCopyURP.shader @@ -0,0 +1,66 @@ +Shader "Hidden/Disguise/RenderStream/DepthCopyURP" +{ + HLSLINCLUDE + #define REQUIRE_DEPTH_TEXTURE + + #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl" + + // Define for DepthCopyNodes.cginc + #define GetDepthForDisguise(uv) SampleSceneDepth(uv) + + // Defines depth operations + #include "DepthCopyNodes.cginc" + + // Contains vertex and fragment functions + #include "DepthCopyCommon.cginc" + ENDHLSL + + SubShader + { + PackageRequirements + { + "com.unity.render-pipelines.universal" : "13.1.8" + } + + Tags + { + "RenderPipeline" = "UniversalRenderPipeline" + } + + // Raw + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment FragRaw + ENDHLSL + } + + // Eye + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment FragEye + ENDHLSL + } + + // Linear01 + Pass + { + Cull Off ZTest Always ZWrite Off Blend Off + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment FragLinear01 + ENDHLSL + } + } + Fallback Off +} diff --git a/Runtime/PipelineAbstraction/Shaders/DepthCopyURP.shader.meta b/Runtime/PipelineAbstraction/Shaders/DepthCopyURP.shader.meta new file mode 100644 index 0000000..838b62d --- /dev/null +++ b/Runtime/PipelineAbstraction/Shaders/DepthCopyURP.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 457eb6ecc0e90a748984ef968f1b6dd9 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/UGUIInputForPresenter.cs b/Runtime/PipelineAbstraction/UGUIInputForPresenter.cs new file mode 100644 index 0000000..7b89229 --- /dev/null +++ b/Runtime/PipelineAbstraction/UGUIInputForPresenter.cs @@ -0,0 +1,82 @@ +using UnityEngine; +using UnityEngine.EventSystems; + +namespace Disguise.RenderStream +{ + /// + /// Attaches to this 's + /// to convert the mouse coordinates to the specified . + /// + class UGUIInputForPresenter : BaseInput + { + BaseInputModule m_InputModule; + + [SerializeField] + Vector2 m_Scale = Vector2.one; + + [SerializeField] + Vector2 m_Offset = Vector2.zero; + + [SerializeField] + Presenter m_Presenter; + + public Vector2 Scale + { + get => m_Scale; + set => m_Scale = value; + } + + public Vector2 Offset + { + get => m_Offset; + set => m_Offset = value; + } + + public Presenter Presenter + { + get => m_Presenter; + set => m_Presenter = value; + } + + protected override void OnEnable() + { + m_InputModule = GetComponent(); + m_InputModule.inputOverride = this; + } + + protected override void OnDisable() + { + if (m_InputModule != null && m_InputModule.inputOverride == this) + m_InputModule.inputOverride = null; + } + + void LateUpdate() + { + if (m_Presenter != null && m_Presenter.IsValid) + { + var scaleBias = m_Presenter.GetScaleBias(true); + var uiPosOnScreen = scaleBias.Bias * m_Presenter.targetSize; + var uiSizeOnScreen = scaleBias.Scale * m_Presenter.targetSize; + + Offset = new Vector2(uiPosOnScreen.x, uiPosOnScreen.y); + Scale = m_Presenter.sourceSize / uiSizeOnScreen; + } + } + + public override Vector2 mousePosition + { + get + { + var truePosition = Input.mousePosition; + var presenterPosition = truePosition; + + presenterPosition.x -= Offset.x; + presenterPosition.y -= Offset.y; + presenterPosition.x *= Scale.x; + presenterPosition.y *= Scale.y; + + return presenterPosition; + } + } + } +} diff --git a/Runtime/PipelineAbstraction/UGUIInputForPresenter.cs.meta b/Runtime/PipelineAbstraction/UGUIInputForPresenter.cs.meta new file mode 100644 index 0000000..059f846 --- /dev/null +++ b/Runtime/PipelineAbstraction/UGUIInputForPresenter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e6337adc892966248b78be116a3e4fd8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PipelineAbstraction/UITKInputForPresenter.cs b/Runtime/PipelineAbstraction/UITKInputForPresenter.cs new file mode 100644 index 0000000..acfcedb --- /dev/null +++ b/Runtime/PipelineAbstraction/UITKInputForPresenter.cs @@ -0,0 +1,79 @@ +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UIElements; + +namespace Disguise.RenderStream +{ + /// + /// Attaches to this 's + /// to convert the input coordinates to the specified . + /// + class UITKInputForPresenter : BaseInput + { + UIDocument m_Document; + + [SerializeField] + Vector2 m_Scale = Vector2.one; + + [SerializeField] + Vector2 m_Offset = Vector2.zero; + + [SerializeField] + Presenter m_Presenter; + + public Vector2 Scale + { + get => m_Scale; + set => m_Scale = value; + } + + public Vector2 Offset + { + get => m_Offset; + set => m_Offset = value; + } + + public Presenter Presenter + { + get => m_Presenter; + set => m_Presenter = value; + } + + protected override void OnEnable() + { + m_Document = GetComponent(); + m_Document.panelSettings.SetScreenToPanelSpaceFunction(ScreenToPanelSpaceFunction); + } + + protected override void OnDisable() + { + if (m_Document != null) + m_Document.panelSettings.SetScreenToPanelSpaceFunction(null); + } + + void LateUpdate() + { + if (m_Presenter != null && m_Presenter.IsValid) + { + var scaleBias = m_Presenter.GetScaleBias(true); + var uiPosOnScreen = scaleBias.Bias * m_Presenter.targetSize; + var uiSizeOnScreen = scaleBias.Scale * m_Presenter.targetSize; + + Offset = new Vector2(uiPosOnScreen.x, uiPosOnScreen.y); + Scale = m_Presenter.sourceSize / uiSizeOnScreen; + } + } + + Vector2 ScreenToPanelSpaceFunction(Vector2 truePosition) + { + var presenterPosition = truePosition; + + presenterPosition.x -= Offset.x; + presenterPosition.y -= Offset.y; + presenterPosition.x *= Scale.x; + presenterPosition.y *= Scale.y; + + return presenterPosition; + } + } +} diff --git a/Runtime/PipelineAbstraction/UITKInputForPresenter.cs.meta b/Runtime/PipelineAbstraction/UITKInputForPresenter.cs.meta new file mode 100644 index 0000000..5931235 --- /dev/null +++ b/Runtime/PipelineAbstraction/UITKInputForPresenter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d496f65d1bcadb4fa4f5be6a568116c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PluginEntry.cs b/Runtime/PluginEntry.cs new file mode 100644 index 0000000..44d4796 --- /dev/null +++ b/Runtime/PluginEntry.cs @@ -0,0 +1,875 @@ +#if UNITY_STANDALONE_WIN +#define PLUGIN_AVAILABLE +#endif + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Disguise.RenderStream.Utils; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine; +using UnityEngine.Experimental.Rendering; +using UnityEngine.Rendering; + +namespace Disguise.RenderStream +{ + [Serializable] + sealed class PluginEntry + { + private class Nested + { + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static Nested() { } + + [RuntimeInitializeOnLoadMethod] + static void InitializeGraphics() + { + instance.InitializeGraphics(); + } + + internal static readonly PluginEntry instance = new PluginEntry(); + } + + public static PluginEntry instance { get { return Nested.instance; } } + + /// + /// Returns the that matches a RenderStream . + /// + /// The RenderStream format. + /// When true, an sRGB variant will be picked if it exists. + /// Should match NativeRenderingPlugin::ToDXFormat in the native plugin's DX12Texture.h. + public static GraphicsFormat ToGraphicsFormat(RSPixelFormat fmt, bool sRGB) + { + // All textures are expected in the normalized 0-1 range. + // CUDA interop expects an alpha channel. + return fmt switch + { + RSPixelFormat.RS_FMT_BGRA8 or RSPixelFormat.RS_FMT_BGRX8 => sRGB ? GraphicsFormat.B8G8R8A8_SRGB : GraphicsFormat.B8G8R8A8_UNorm, + RSPixelFormat.RS_FMT_RGBA32F => GraphicsFormat.R32G32B32A32_SFloat, + RSPixelFormat.RS_FMT_RGBA16 => GraphicsFormat.R16G16B16A16_UNorm, + RSPixelFormat.RS_FMT_RGBA8 or RSPixelFormat.RS_FMT_RGBX8 => sRGB ? GraphicsFormat.R8G8B8A8_SRGB : GraphicsFormat.R8G8B8A8_UNorm, + _ => throw new ArgumentOutOfRangeException() + }; + } + + // isolated functions, do not require init prior to use + unsafe delegate void pRegisterLogFunc(logger_t logger); + unsafe delegate void pUnregisterLogFunc(); + + unsafe delegate RS_ERROR pInitialise(int expectedVersionMajor, int expectedVersionMinor); + unsafe delegate RS_ERROR pInitialiseGpGpuWithoutInterop(/*ID3D11Device**/ IntPtr device); + unsafe delegate RS_ERROR pInitialiseGpGpuWithDX11Device(/*ID3D11Device**/ IntPtr device); + unsafe delegate RS_ERROR pInitialiseGpGpuWithDX11Resource(/*ID3D11Resource**/ IntPtr resource); + unsafe delegate RS_ERROR pInitialiseGpGpuWithDX12DeviceAndQueue(/*ID3D12Device**/ IntPtr device, /*ID3D12CommandQueue**/ IntPtr queue); + unsafe delegate RS_ERROR pInitialiseGpGpuWithOpenGlContexts(/*HGLRC**/ IntPtr glContext, /*HDC**/ IntPtr deviceContext); + unsafe delegate RS_ERROR pInitialiseGpGpuWithVulkanDevice(/*VkDevice**/ IntPtr device); + unsafe delegate RS_ERROR pShutdown(); + + // non-isolated functions, these require init prior to use + + unsafe delegate RS_ERROR pUseDX12SharedHeapFlag(out UseDX12SharedHeapFlag flag); + unsafe delegate RS_ERROR pSaveSchema(string assetPath, /*Schema**/ IntPtr schema); // Save schema for project file/custom executable at (assetPath) + unsafe delegate RS_ERROR pLoadSchema(string assetPath, /*Out*/ /*Schema**/ IntPtr schema, /*InOut*/ ref UInt32 nBytes); // Load schema for project file/custom executable at (assetPath) into a buffer of size (nBytes) starting at (schema) + + // workload functions, these require the process to be running inside d3's asset launcher environment + + unsafe delegate RS_ERROR pSetSchema(/*InOut*/ /*Schema**/ IntPtr schema); // Set schema and fill in per-scene hash for use with rs_getFrameParameters + + unsafe delegate RS_ERROR pGetStreams(/*Out*/ /*StreamDescriptions**/ IntPtr streams, /*InOut*/ ref UInt32 nBytes); // Populate streams into a buffer of size (nBytes) starting at (streams) + + unsafe delegate RS_ERROR pAwaitFrameData(int timeoutMs, [Out] out FrameData data); // waits for any asset, any stream to request a frame, provides the parameters for that frame. + unsafe delegate RS_ERROR pSetFollower(int isFollower); // Used to mark this node as relying on alternative mechanisms to distribute FrameData. Users must provide correct CameraResponseData to sendFrame, and call rs_beginFollowerFrame at the start of the frame, where awaitFrame would normally be called. + unsafe delegate RS_ERROR pBeginFollowerFrame(double tTracked); // Pass the engine-distributed tTracked value in, if you have called rs_setFollower(1) otherwise do not call this function. + + unsafe delegate RS_ERROR pGetFrameParameters(UInt64 schemaHash, /*Out*/ /*void**/ IntPtr outParameterData, UInt64 outParameterDataSize); // returns the remote parameters for this frame. + unsafe delegate RS_ERROR pGetFrameImageData(UInt64 schemaHash, /*Out*/ ImageFrameData* outParameterData, UInt64 outParameterDataCount); // returns the remote image data for this frame. + unsafe delegate RS_ERROR pGetFrameImage(Int64 imageId, SenderFrameType frameType, SenderFrameTypeData data); // fills in (data) with the remote image + unsafe delegate RS_ERROR pGetFrameText(UInt64 schemaHash, UInt32 textParamIndex, /*Out*/ /*const char***/ ref IntPtr outTextPtr); // // returns the remote text data (pointer only valid until next rs_awaitFrameData) + + unsafe delegate RS_ERROR pGetFrameCamera(UInt64 streamHandle, /*Out*/ ref CameraData outCameraData); // returns the CameraData for this stream, or RS_ERROR_NOTFOUND if no camera data is available for this stream on this frame + unsafe delegate RS_ERROR pSendFrame(UInt64 streamHandle, SenderFrameType frameType, SenderFrameTypeData data, [In] ref FrameResponseData sendData); // publish a frame buffer which was generated from the associated tracking and timing information. + + unsafe delegate RS_ERROR pReleaseImage(SenderFrameType frameType, SenderFrameTypeData data); + + unsafe delegate RS_ERROR pLogToD3(string str); + unsafe delegate RS_ERROR pSendProfilingData(/*ProfilingEntry**/ IntPtr entries, int count); + unsafe delegate RS_ERROR pSetNewStatusMessage(string msg); + + pRegisterLogFunc m_registerLoggingFunc = null; + pRegisterLogFunc m_registerErrorLoggingFunc = null; + pRegisterLogFunc m_registerVerboseLoggingFunc = null; + + pUnregisterLogFunc m_unregisterLoggingFunc = null; + pUnregisterLogFunc m_unregisterErrorLoggingFunc = null; + pUnregisterLogFunc m_unregisterVerboseLoggingFunc = null; + + pInitialise m_initialise = null; + pInitialiseGpGpuWithoutInterop m_initialiseGpGpuWithoutInterop = null; + pInitialiseGpGpuWithDX11Device m_initialiseGpGpuWithDX11Device = null; + pInitialiseGpGpuWithDX11Resource m_initialiseGpGpuWithDX11Resource = null; + pInitialiseGpGpuWithDX12DeviceAndQueue m_initialiseGpGpuWithDX12DeviceAndQueue = null; + pInitialiseGpGpuWithOpenGlContexts m_initialiseGpGpuWithOpenGlContexts = null; + pInitialiseGpGpuWithVulkanDevice m_initialiseGpGpuWithVulkanDevice = null; + + pShutdown m_shutdown = null; + + pUseDX12SharedHeapFlag m_useDX12SharedHeapFlag = null; + pSaveSchema m_saveSchema = null; + pLoadSchema m_loadSchema = null; + + pSetSchema m_setSchema = null; + pGetStreams m_getStreams = null; + + pAwaitFrameData m_awaitFrameData = null; + pSetFollower m_setFollower = null; + pBeginFollowerFrame m_beginFollowerFrame = null; + + pGetFrameParameters m_getFrameParameters = null; + pGetFrameImageData m_getFrameImageData = null; + pGetFrameImage m_getFrameImage = null; + pGetFrameText m_getFrameText = null; + + pGetFrameCamera m_getFrameCamera = null; + pSendFrame m_sendFrame = null; + + pReleaseImage m_releaseImage = null; + + pLogToD3 m_logToD3 = null; + pSendProfilingData m_sendProfilingData = null; + pSetNewStatusMessage m_setNewStatusMessage = null; + + GraphicsDeviceType m_GraphicsDeviceType; + + public IntPtr rs_getFrameImage_ptr; + public IntPtr rs_sendFrame_ptr; + + // Static wrapper to support delegate marshalling to native in IL2CPP + [AOT.MonoPInvokeCallback(typeof(logger_t))] + static void logInfo(string message) + { + Debug.Log(message); + } + + // Static wrapper to support delegate marshalling to native in IL2CPP + [AOT.MonoPInvokeCallback(typeof(logger_t))] + static void logError(string message) + { + Debug.LogError(message); + } + + void logToD3(string logString, string stackTrace, LogType type) + { + if (m_logToD3 == null) + return; + + string prefix = ""; + switch(type) + { + case LogType.Error: + prefix = "!!!!! "; + break; + case LogType.Assert: + prefix = "!!!!! ASSERT: "; + break; + case LogType.Warning: + prefix = "!!! "; + break; + case LogType.Exception: + prefix = "!!!!! Exception: "; + break; + } + + string trace = String.IsNullOrEmpty(stackTrace) ? "" : "\nTrace: " + stackTrace; + + m_logToD3(prefix + logString + trace); + } + + void setNewStatusMessage(string message) + { + m_setNewStatusMessage?.Invoke(message); + } + + ManagedSchema schemaToManagedSchema(Schema cSchema) + { + ManagedSchema schema = new ManagedSchema(); + schema.channels = new string[cSchema.channels.nChannels]; + for (int i = 0; i < cSchema.channels.nChannels; ++i) + { + IntPtr channelPtr = Marshal.ReadIntPtr(cSchema.channels.channels, i * Marshal.SizeOf(typeof(IntPtr))); + schema.channels[i] = Marshal.PtrToStringAnsi(channelPtr); + } + schema.scenes = new ManagedRemoteParameters[cSchema.scenes.nScenes]; + for (int i = 0; i < cSchema.scenes.nScenes; ++i) + { + schema.scenes[i] = new ManagedRemoteParameters(); + ManagedRemoteParameters managedParameters = schema.scenes[i]; + RemoteParameters parameters = (RemoteParameters)Marshal.PtrToStructure(cSchema.scenes.scenes + i * Marshal.SizeOf(typeof(RemoteParameters)), typeof(RemoteParameters)); + managedParameters.name = parameters.name; + managedParameters.parameters = new ManagedRemoteParameter[parameters.nParameters]; + for (int j = 0; j < parameters.nParameters; ++j) + { + managedParameters.parameters[j] = new ManagedRemoteParameter(); + ManagedRemoteParameter managedParameter = managedParameters.parameters[j]; + RemoteParameter parameter = (RemoteParameter)Marshal.PtrToStructure(parameters.parameters + j * Marshal.SizeOf(typeof(RemoteParameter)), typeof(RemoteParameter)); + managedParameter.group = parameter.group; + managedParameter.displayName = parameter.displayName; + managedParameter.key = parameter.key; + managedParameter.type = parameter.type; + if (parameter.type == RemoteParameterType.RS_PARAMETER_NUMBER) + { + managedParameter.min = parameter.defaults.numerical_min; + managedParameter.max = parameter.defaults.numerical_max; + managedParameter.step = parameter.defaults.numerical_step; + managedParameter.defaultValue = parameter.defaults.numerical_defaultValue; + } + else if (parameter.type == RemoteParameterType.RS_PARAMETER_TEXT) + { + managedParameter.defaultValue = Marshal.PtrToStringAnsi(parameter.defaults.text_defaultValue); + } + managedParameter.options = new string[parameter.nOptions]; + for (int k = 0; k < parameter.nOptions; ++k) + { + IntPtr optionPtr = Marshal.ReadIntPtr(parameter.options, k * Marshal.SizeOf(typeof(IntPtr))); + managedParameter.options[k] = Marshal.PtrToStringAnsi(optionPtr); + } + managedParameter.dmxOffset = parameter.dmxOffset; + managedParameter.dmxType = parameter.dmxType; + } + managedParameters.hash = parameters.hash; + } + return schema; + } + + public RS_ERROR saveSchema(string assetPath, ref ManagedSchema schema) + { + if (m_saveSchema == null) + return RS_ERROR.RS_NOT_INITIALISED; + + List allocations = new List(); + try + { + Schema cSchema = new Schema(); + cSchema.engineName = "Unity Engine"; + cSchema.engineVersion = Application.unityVersion; + cSchema.info = Application.productName; + cSchema.channels.nChannels = (UInt32)schema.channels.Length; + cSchema.channels.channels = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)) * (int)cSchema.channels.nChannels); + allocations.Add(cSchema.channels.channels); + for (int i = 0; i < cSchema.channels.nChannels; ++i) + { + IntPtr channelPtr = Marshal.StringToHGlobalAnsi(schema.channels[i]); + allocations.Add(channelPtr); + Marshal.WriteIntPtr(cSchema.channels.channels, i * Marshal.SizeOf(typeof(IntPtr)), channelPtr); + } + + cSchema.scenes.nScenes = (UInt32)schema.scenes.Length; + cSchema.scenes.scenes = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(RemoteParameters)) * (int)cSchema.scenes.nScenes); + allocations.Add(cSchema.scenes.scenes); + for (int i = 0; i < cSchema.scenes.nScenes; ++i) + { + ManagedRemoteParameters managedParameters = schema.scenes[i]; + RemoteParameters parameters = new RemoteParameters(); + parameters.name = managedParameters.name; + parameters.nParameters = (UInt32)managedParameters.parameters.Length; + parameters.parameters = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(RemoteParameter)) * (int)parameters.nParameters); + allocations.Add(parameters.parameters); + for (int j = 0; j < parameters.nParameters; ++j) + { + ManagedRemoteParameter managedParameter = managedParameters.parameters[j]; + RemoteParameter parameter = new RemoteParameter(); + parameter.group = managedParameter.group; + parameter.displayName = managedParameter.displayName; + parameter.key = managedParameter.key; + parameter.type = managedParameter.type; + if (parameter.type == RemoteParameterType.RS_PARAMETER_NUMBER) + { + parameter.defaults.numerical_min = managedParameter.min; + parameter.defaults.numerical_max = managedParameter.max; + parameter.defaults.numerical_step = managedParameter.step; + parameter.defaults.numerical_defaultValue = Convert.ToSingle(managedParameter.defaultValue); + } + else if (parameter.type == RemoteParameterType.RS_PARAMETER_TEXT) + { + IntPtr textPtr = Marshal.StringToHGlobalAnsi(Convert.ToString(managedParameter.defaultValue)); + allocations.Add(textPtr); + parameter.defaults.text_defaultValue = textPtr; + } + parameter.nOptions = (UInt32)managedParameter.options.Length; + parameter.options = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)) * (int)parameter.nOptions); + allocations.Add(parameter.options); + for (int k = 0; k < parameter.nOptions; ++k) + { + IntPtr optionPtr = Marshal.StringToHGlobalAnsi(managedParameter.options[k]); + allocations.Add(optionPtr); + Marshal.WriteIntPtr(parameter.options, k * Marshal.SizeOf(typeof(IntPtr)), optionPtr); + } + parameter.dmxOffset = managedParameter.dmxOffset; + parameter.dmxType = managedParameter.dmxType; + Marshal.StructureToPtr(parameter, parameters.parameters + j * Marshal.SizeOf(typeof(RemoteParameter)), false); + } + Marshal.StructureToPtr(parameters, cSchema.scenes.scenes + i * Marshal.SizeOf(typeof(RemoteParameters)), false); + } + + IntPtr schemaPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Schema))); + allocations.Add(schemaPtr); + Marshal.StructureToPtr(cSchema, schemaPtr, false); + RS_ERROR error = m_saveSchema(assetPath, schemaPtr); + if (error == RS_ERROR.RS_ERROR_SUCCESS) + { + cSchema = (Schema)Marshal.PtrToStructure(schemaPtr, typeof(Schema)); + schema = schemaToManagedSchema(cSchema); + } + return error; + } + finally + { + foreach (IntPtr ptr in allocations) + Marshal.FreeHGlobal(ptr); + } + //return RS_ERROR.RS_ERROR_UNSPECIFIED; + } + + public RS_ERROR LoadSchema(string assetPath, out ManagedSchema schema) + { + schema = new ManagedSchema(); + if (m_loadSchema == null) + return RS_ERROR.RS_NOT_INITIALISED; + + IntPtr descMem = IntPtr.Zero; + UInt32 nBytes = 0; + m_loadSchema(assetPath, descMem, ref nBytes); + + const int MAX_TRIES = 3; + int iterations = 0; + + RS_ERROR res = RS_ERROR.RS_ERROR_BUFFER_OVERFLOW; + try + { + do + { + Marshal.FreeHGlobal(descMem); + descMem = Marshal.AllocHGlobal((int)nBytes); + res = m_loadSchema(assetPath, descMem, ref nBytes); + if (res == RS_ERROR.RS_ERROR_SUCCESS) + { + Schema cSchema = (Schema)Marshal.PtrToStructure(descMem, typeof(Schema)); + schema = schemaToManagedSchema(cSchema); + } + + ++iterations; + } while (res == RS_ERROR.RS_ERROR_BUFFER_OVERFLOW && iterations < MAX_TRIES); + } + finally + { + Marshal.FreeHGlobal(descMem); + } + return res; + } + + public RS_ERROR getStreams(out StreamDescription[] streams) + { + streams = null; + + if (m_getStreams == null) + return RS_ERROR.RS_NOT_INITIALISED; + + IntPtr descMem = IntPtr.Zero; + UInt32 nBytes = 0; + m_getStreams(descMem, ref nBytes); + + const int MAX_TRIES = 3; + int iterations = 0; + + RS_ERROR res = RS_ERROR.RS_ERROR_BUFFER_OVERFLOW; + try + { + do + { + Marshal.FreeHGlobal(descMem); + descMem = Marshal.AllocHGlobal((int)nBytes); + res = m_getStreams(descMem, ref nBytes); + if (res == RS_ERROR.RS_ERROR_SUCCESS) + { + StreamDescriptions desc = (StreamDescriptions)Marshal.PtrToStructure(descMem, typeof(StreamDescriptions)); + streams = new StreamDescription[desc.nStreams]; + for (int i = 0; i < desc.nStreams; ++i) + { + IntPtr current = desc.streams + i * Marshal.SizeOf(typeof(StreamDescription)); + streams[i] = (StreamDescription)Marshal.PtrToStructure(current, typeof(StreamDescription)); + } + } + + ++iterations; + } while (res == RS_ERROR.RS_ERROR_BUFFER_OVERFLOW && iterations < MAX_TRIES); + } + finally + { + Marshal.FreeHGlobal(descMem); + } + return res; + } + + public RS_ERROR sendFrame(UInt64 streamHandle, SenderFrameType frameType, SenderFrameTypeData data, ref FrameResponseData sendData) + { + if (m_sendFrame == null) + return RS_ERROR.RS_NOT_INITIALISED; + RS_ERROR error = m_sendFrame(streamHandle, frameType, data, ref sendData); + return error; + } + + public RS_ERROR awaitFrameData(int timeoutMs, out FrameData data) + { + if (m_awaitFrameData == null) + { + data = default; + return RS_ERROR.RS_NOT_INITIALISED; + } + + return m_awaitFrameData(timeoutMs, out data); + //return RS_ERROR.RS_ERROR_UNSPECIFIED; + } + + public RS_ERROR setFollower(int isFollower) + { + if (m_setFollower == null) + return RS_ERROR.RS_NOT_INITIALISED; + + try + { + RS_ERROR error = m_setFollower(isFollower); + return error; + } + finally + { + } + } + + public RS_ERROR beginFollowerFrame(double tTracked) + { + if (m_beginFollowerFrame == null) + return RS_ERROR.RS_NOT_INITIALISED; + + try + { + RS_ERROR error = m_beginFollowerFrame(tTracked); + return error; + } + finally + { + } + } + + public RS_ERROR GetFrameParameters(UInt64 schemaHash, Span outParameterData) + { + if (m_getFrameParameters == null) + return RS_ERROR.RS_NOT_INITIALISED; + + unsafe + { + fixed(float* outDataPtr = outParameterData) + { + RS_ERROR error = m_getFrameParameters(schemaHash, + (IntPtr)outDataPtr, + (ulong)outParameterData.Length * sizeof(float)); + return error; + } + } + } + + public RS_ERROR GetFrameImageData(UInt64 schemaHash, Span outParameterData) + { + if (m_getFrameImageData == null) + return RS_ERROR.RS_NOT_INITIALISED; + + unsafe + { + fixed (ImageFrameData* outDataPtr = outParameterData) + { + RS_ERROR error = m_getFrameImageData(schemaHash, + outDataPtr, + (UInt64)outParameterData.Length); + return error; + } + } + } + + public RS_ERROR getFrameImage(Int64 imageId, ref Texture2D texture) + { + if (m_getFrameImage == null) + return RS_ERROR.RS_NOT_INITIALISED; + + RS_ERROR error = RS_ERROR.RS_ERROR_SUCCESS; + + try + { + SenderFrameTypeData data = new SenderFrameTypeData(); + + switch (GraphicsDeviceType) + { + case GraphicsDeviceType.Direct3D11: + data.dx11_resource = texture.GetNativeTexturePtr(); + error = m_getFrameImage(imageId, SenderFrameType.RS_FRAMETYPE_DX11_TEXTURE, data); + break; + + case GraphicsDeviceType.Direct3D12: + data.dx12_resource = texture.GetNativeTexturePtr(); + error = m_getFrameImage(imageId, SenderFrameType.RS_FRAMETYPE_DX12_TEXTURE, data); + break; + } + + return error; + } + finally + { + } + //return RS_ERROR.RS_ERROR_UNSPECIFIED; + } + + public RS_ERROR getFrameText(UInt64 schemaHash, UInt32 textParamIndex, ref string text) + { + if (m_getFrameText == null) + return RS_ERROR.RS_NOT_INITIALISED; + + try + { + IntPtr textPtr = IntPtr.Zero; + RS_ERROR error = m_getFrameText(schemaHash, textParamIndex, ref textPtr); + if (error == RS_ERROR.RS_ERROR_SUCCESS) + text = Marshal.PtrToStringAnsi(textPtr); + return error; + } + finally + { + } + //return RS_ERROR.RS_ERROR_UNSPECIFIED; + } + + public RS_ERROR getFrameCamera(UInt64 streamHandle, ref CameraData outCameraData) + { + if (m_getFrameCamera == null) + return RS_ERROR.RS_NOT_INITIALISED; + + return m_getFrameCamera(streamHandle, ref outCameraData); + //return RS_ERROR.RS_ERROR_UNSPECIFIED; + } + + public RS_ERROR useDX12SharedHeapFlag(out UseDX12SharedHeapFlag flag) + { + return m_useDX12SharedHeapFlag(out flag); + } + + public GraphicsDeviceType GraphicsDeviceType => m_GraphicsDeviceType; + + bool GraphicsDeviceTypeIsSupported => m_GraphicsDeviceType is GraphicsDeviceType.Direct3D11 or GraphicsDeviceType.Direct3D12; + +#if PLUGIN_AVAILABLE + + [DllImport("kernel32.dll")] + static extern IntPtr LoadLibraryEx(string lpLibFileName, IntPtr fileHandle, int flags); + + [DllImport("kernel32.dll")] + static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); + + [DllImport("kernel32.dll", CharSet = CharSet.Ansi)] + static extern bool FreeLibrary(IntPtr hModule); + + private void free() + { +#if UNITY_EDITOR + UnityEditor.EditorApplication.quitting -= free; + UnityEditor.AssemblyReloadEvents.beforeAssemblyReload -= free; +#else + Application.quitting -= free; +#endif + + if (functionsLoaded) + { + if (m_logToD3 != null) + Application.logMessageReceivedThreaded -= logToD3; + + if (m_unregisterErrorLoggingFunc != null) + m_unregisterErrorLoggingFunc(); + if (m_unregisterLoggingFunc != null) + m_unregisterLoggingFunc(); + + RS_ERROR error = m_shutdown(); + if (error != RS_ERROR.RS_ERROR_SUCCESS) + Debug.LogError(string.Format("Failed to shutdown: {0}", error)); + functionsLoaded = false; + Debug.Log("Shut down RenderStream"); + } + + if (d3RenderStreamDLL != IntPtr.Zero) + { + FreeLibrary(d3RenderStreamDLL); + d3RenderStreamDLL = IntPtr.Zero; + Debug.Log("Unloaded RenderStream"); + } + + if (handleReference.IsAllocated) + handleReference.Free(); + } + + public bool IsAvailable + { + get + { + return functionsLoaded && GraphicsDeviceTypeIsSupported; + } + } +#else + private void free() {} + public bool IsAvailable { get { return false; } } +#endif + + const int LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010; + const int LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100; + const int LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200; + const int LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400; + const int LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800; + + const string _dllName = "d3renderstream"; + + const int RENDER_STREAM_VERSION_MAJOR = 1; + const int RENDER_STREAM_VERSION_MINOR = 30; + + bool functionsLoaded = false; + IntPtr d3RenderStreamDLL = IntPtr.Zero; + GCHandle handleReference; // Everything is run under coroutines with odd lifetimes, so store a reference to GCHandle + + string name; + + // https://answers.unity.com/questions/16804/retrieving-project-name.html?childToView=478633#answer-478633 + public string GetProjectName() + { + string[] s = Application.dataPath.Split('/'); + if (s.Length >= 2) + { + string projectName = s[s.Length - 2]; + return projectName; + } + return "UNKNOWN UNITY PROJECT"; + } + + private bool LoadFn(ref T fn, string fnName) where T : Delegate + { + fn = DelegateBuilder(d3RenderStreamDLL, fnName); + if (fn == null) + { + Debug.LogError(string.Format("Failed load function \"{0}\" from {1}.dll", fnName, _dllName)); + return false; + } + return true; + } + + private PluginEntry() + { +#if PLUGIN_AVAILABLE + m_GraphicsDeviceType = SystemInfo.graphicsDeviceType; + if (!GraphicsDeviceTypeIsSupported) + { + throw new InvalidOperationException($"Unsupported GraphicsDeviceType: {PluginEntry.instance.GraphicsDeviceType}. Only Direct3D11 and Direct3D12 are supported."); + } + + var result = RegistryWrapper.ReadRegKey( + RegistryWrapper.HKEY_CURRENT_USER, + @"Software\d3 Technologies\d3 Production Suite", + "exe path", + out var d3ExePath); + + if (result == RegistryWrapper.ReadRegKeyResult.OpenFailed) + { + Debug.LogError(string.Format("Failed to find path to {0}.dll. d3 Not installed?", _dllName)); + return; + } + + if (result != RegistryWrapper.ReadRegKeyResult.Success) + { + Debug.LogError(string.Format("Failed to find path to {0}.dll. Error: {1}", _dllName, result)); + return; + } + + d3ExePath = d3ExePath.Replace(@"\\", @"\"); + int endSeparator = d3ExePath.LastIndexOf(Path.DirectorySeparatorChar); + if (endSeparator != d3ExePath.Length - 1) + d3ExePath = d3ExePath.Substring(0, endSeparator + 1); + + string libPath = d3ExePath + _dllName + ".dll"; + d3RenderStreamDLL = LoadWin32Library(libPath); + if (d3RenderStreamDLL == IntPtr.Zero) + { + Debug.LogError(string.Format("Failed to load {0}.dll from {1}", _dllName, d3ExePath)); + return; + } + + functionsLoaded = true; + + functionsLoaded &= LoadFn(ref m_registerLoggingFunc, "rs_registerLoggingFunc"); + functionsLoaded &= LoadFn(ref m_registerErrorLoggingFunc, "rs_registerErrorLoggingFunc"); + functionsLoaded &= LoadFn(ref m_registerVerboseLoggingFunc, "rs_registerVerboseLoggingFunc"); + + functionsLoaded &= LoadFn(ref m_unregisterLoggingFunc, "rs_unregisterLoggingFunc"); + functionsLoaded &= LoadFn(ref m_unregisterErrorLoggingFunc, "rs_unregisterErrorLoggingFunc"); + functionsLoaded &= LoadFn(ref m_unregisterVerboseLoggingFunc, "rs_unregisterVerboseLoggingFunc"); + + functionsLoaded &= LoadFn(ref m_initialise, "rs_initialise"); + functionsLoaded &= LoadFn(ref m_initialiseGpGpuWithoutInterop, "rs_initialiseGpGpuWithoutInterop"); + functionsLoaded &= LoadFn(ref m_initialiseGpGpuWithDX11Device, "rs_initialiseGpGpuWithDX11Device"); + functionsLoaded &= LoadFn(ref m_initialiseGpGpuWithDX11Resource, "rs_initialiseGpGpuWithDX11Resource"); + functionsLoaded &= LoadFn(ref m_initialiseGpGpuWithDX12DeviceAndQueue, "rs_initialiseGpGpuWithDX12DeviceAndQueue"); + functionsLoaded &= LoadFn(ref m_initialiseGpGpuWithOpenGlContexts, "rs_initialiseGpGpuWithOpenGlContexts"); + functionsLoaded &= LoadFn(ref m_initialiseGpGpuWithVulkanDevice, "rs_initialiseGpGpuWithVulkanDevice"); + functionsLoaded &= LoadFn(ref m_shutdown, "rs_shutdown"); + + functionsLoaded &= LoadFn(ref m_useDX12SharedHeapFlag, "rs_useDX12SharedHeapFlag"); + functionsLoaded &= LoadFn(ref m_saveSchema, "rs_saveSchema"); + functionsLoaded &= LoadFn(ref m_loadSchema, "rs_loadSchema"); + + functionsLoaded &= LoadFn(ref m_setSchema, "rs_setSchema"); + + functionsLoaded &= LoadFn(ref m_getStreams, "rs_getStreams"); + + functionsLoaded &= LoadFn(ref m_awaitFrameData, "rs_awaitFrameData"); + functionsLoaded &= LoadFn(ref m_setFollower, "rs_setFollower"); + functionsLoaded &= LoadFn(ref m_beginFollowerFrame, "rs_beginFollowerFrame"); + + functionsLoaded &= LoadFn(ref m_getFrameParameters, "rs_getFrameParameters"); + functionsLoaded &= LoadFn(ref m_getFrameImageData, "rs_getFrameImageData"); + functionsLoaded &= LoadFn(ref m_getFrameImage, "rs_getFrameImage"); + functionsLoaded &= LoadFn(ref m_getFrameText, "rs_getFrameText"); + + functionsLoaded &= LoadFn(ref m_getFrameCamera, "rs_getFrameCamera"); + functionsLoaded &= LoadFn(ref m_sendFrame, "rs_sendFrame"); + + functionsLoaded &= LoadFn(ref m_releaseImage, "rs_releaseImage"); + + functionsLoaded &= LoadFn(ref m_logToD3, "rs_logToD3"); + functionsLoaded &= LoadFn(ref m_sendProfilingData, "rs_sendProfilingData"); + functionsLoaded &= LoadFn(ref m_setNewStatusMessage, "rs_setNewStatusMessage"); + + rs_getFrameImage_ptr = GetProcAddress(d3RenderStreamDLL, "rs_getFrameImage"); + Debug.Assert(rs_getFrameImage_ptr != IntPtr.Zero, "Failed to get rs_getFrameImage function pointer"); + + rs_sendFrame_ptr = GetProcAddress(d3RenderStreamDLL, "rs_sendFrame"); + Debug.Assert(rs_sendFrame_ptr != IntPtr.Zero, "Failed to get rs_sendFrame_ptr function pointer"); + + if (!functionsLoaded) + { + Debug.LogError(string.Format("One or more functions failed load from {0}.dll", _dllName)); + return; + } + + // There is an issue with these logging callbacks sometimes throwing inside of the dll which can cause all kinds of problems + // exception consistentency is questionable, often the same exception can be seen at the same point in time + // however periodically a minor difference may occur where the exception is not thrown where expected or even at all + + if (m_registerLoggingFunc != null) + m_registerLoggingFunc(logInfo); + if (m_registerErrorLoggingFunc != null) + m_registerErrorLoggingFunc(logError); + + if (m_logToD3 != null) + Application.logMessageReceivedThreaded += logToD3; + + RS_ERROR error = m_initialise(RENDER_STREAM_VERSION_MAJOR, RENDER_STREAM_VERSION_MINOR); + if (error == RS_ERROR.RS_ERROR_INCOMPATIBLE_VERSION) + Debug.LogError(string.Format("Unsupported RenderStream library, expected version {0}.{1}", RENDER_STREAM_VERSION_MAJOR, RENDER_STREAM_VERSION_MINOR)); + else if (error != RS_ERROR.RS_ERROR_SUCCESS) + Debug.LogError(string.Format("Failed to initialise: {0}", error)); + + Debug.Log("Loaded RenderStream"); + +#if UNITY_EDITOR + UnityEditor.EditorApplication.quitting += free; + UnityEditor.AssemblyReloadEvents.beforeAssemblyReload += free; +#else + Application.quitting += free; +#endif + + name = GetProjectName(); +#else + Debug.LogError(string.Format("{0}.dll is only available on Windows", _dllName)); +#endif + } + + ~PluginEntry() + { + free(); + } + + internal void InitializeGraphics() + { + switch (GraphicsDeviceType) + { + case GraphicsDeviceType.Direct3D11: + Texture2D texture = new Texture2D(1, 1); + var error = m_initialiseGpGpuWithDX11Resource(texture.GetNativeTexturePtr()); + if (error != RS_ERROR.RS_ERROR_SUCCESS) + Debug.LogError(string.Format("Failed to initialise GPU interop: {0}", error)); + break; + + case GraphicsDeviceType.Direct3D12: + + if (!NativeRenderingPlugin.IsInitialized()) + Debug.LogError("Failed to initialise NativeRenderingPlugin (for DX12 support)"); + + var device = NativeRenderingPlugin.GetD3D12Device(); + var commandQueue = NativeRenderingPlugin.GetD3D12CommandQueue(); + + if (device == IntPtr.Zero) + Debug.LogError("Failed to initialise DX12 device"); + + if (commandQueue == IntPtr.Zero) + Debug.LogError(string.Format("Failed to initialise DX12 command queue")); + + error = m_initialiseGpGpuWithDX12DeviceAndQueue(device, commandQueue); + if (error != RS_ERROR.RS_ERROR_SUCCESS) + Debug.LogError(string.Format("Failed to initialise GPU interop: {0}", error)); + + break; + } + } + + static IntPtr LoadWin32Library(string dllFilePath) + { + System.IntPtr moduleHandle = IntPtr.Zero ; +#if PLUGIN_AVAILABLE + moduleHandle = LoadLibraryEx(dllFilePath, IntPtr.Zero, LOAD_IGNORE_CODE_AUTHZ_LEVEL | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_SEARCH_USER_DIRS); + if (moduleHandle == IntPtr.Zero) + { + // I'm gettin last dll error + int errorCode = Marshal.GetLastWin32Error(); + Debug.LogError(string.Format("There was an error during dll loading : {0}, error - {1}", dllFilePath, errorCode)); + } +#endif + return moduleHandle; + } + + static T DelegateBuilder(IntPtr loadedDLL, string functionName) where T : Delegate + { + IntPtr pAddressOfFunctionToCall = IntPtr.Zero; +#if PLUGIN_AVAILABLE + pAddressOfFunctionToCall = GetProcAddress(loadedDLL, functionName); + if (pAddressOfFunctionToCall == IntPtr.Zero) + { + return null; + } +#endif + T functionDelegate = Marshal.GetDelegateForFunctionPointer(pAddressOfFunctionToCall, typeof(T)) as T; + return functionDelegate; + } + } + +#if !UNITY_2022_2_OR_NEWER + static class PluginExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe Span AsSpan(this NativeArray arr) where T : unmanaged => + new(arr.GetUnsafePtr(), arr.Length); + } +#endif +} diff --git a/Runtime/PluginEntry.cs.meta b/Runtime/PluginEntry.cs.meta new file mode 100644 index 0000000..713a65f --- /dev/null +++ b/Runtime/PluginEntry.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ea2a06b158bfbbe42b621620e9db5c04 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ScratchTextureManager.cs b/Runtime/ScratchTextureManager.cs new file mode 100644 index 0000000..cfb498d --- /dev/null +++ b/Runtime/ScratchTextureManager.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.Diagnostics; +using UnityEngine; +using Debug = UnityEngine.Debug; + +namespace Disguise.RenderStream +{ + /// + /// A simpler version of that doesn't need to manage texture lifetime. + /// + abstract class ScratchTextureManager + { + readonly Dictionary m_Items = new Dictionary(); + + protected string m_Name; + + public void Clear() + { + DebugLog($"Cleared textures"); + + foreach (var texture in m_Items.Values) + { + DestroyTexture(texture); + } + + m_Items.Clear(); + } + + public TTexture Get(Texture2DDescriptor descriptor) + { + if (m_Items.TryGetValue(descriptor, out var item)) + { + return item; + } + + DebugLog($"Created texture: {descriptor}"); + + var texture = CreateTexture(descriptor); + m_Items.Add(descriptor, texture); + return texture; + } + + protected abstract TTexture CreateTexture(Texture2DDescriptor descriptor); + + protected abstract void DestroyTexture(TTexture texture); + + [Conditional("DISGUISE_VERBOSE_LOGGING")] + void DebugLog(string message) + { + Debug.Log($"{m_Name}: {message}"); + } + } + + class ScratchTexture2DManager : ScratchTextureManager + { + static ScratchTexture2DManager s_Instance; + + public static ScratchTexture2DManager Instance + { + get + { + if (s_Instance == null) + s_Instance = new ScratchTexture2DManager(); + return s_Instance; + } + } + + ScratchTexture2DManager() + { + m_Name = nameof(ScratchTexture2DManager); + } + + protected override Texture2D CreateTexture(Texture2DDescriptor descriptor) + { + return DisguiseTextures.CreateTexture(descriptor.Width, descriptor.Height, descriptor.Format, !descriptor.Linear, null); + } + + protected override void DestroyTexture(Texture2D texture) + { +#if UNITY_EDITOR + UnityEngine.Object.DestroyImmediate(texture); +#else + UnityEngine.Object.Destroy(texture); +#endif + } + } +} diff --git a/Runtime/ScratchTextureManager.cs.meta b/Runtime/ScratchTextureManager.cs.meta new file mode 100644 index 0000000..b992c04 --- /dev/null +++ b/Runtime/ScratchTextureManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a180ecb58f9f2e147861578a057a70fd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/TemporaryTexture2DManager.cs b/Runtime/TemporaryTexture2DManager.cs new file mode 100644 index 0000000..641d43e --- /dev/null +++ b/Runtime/TemporaryTexture2DManager.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Disguise.RenderStream.Utils; +using UnityEngine; +using UnityEngine.PlayerLoop; +using UnityEngine.Pool; +using Debug = UnityEngine.Debug; + +namespace Disguise.RenderStream +{ + struct Texture2DDescriptor : IEquatable + { + public int Width; + public int Height; + public RSPixelFormat Format; + public bool Linear; + + /// + /// Disguise uses a black 1x1 placeholder texture. + /// It's bound initially and during input parameter swapping. + /// We treat it as a persistent texture. + /// + public bool IsPlaceholderTexture => Width == 1 && Height == 1; + + public override bool Equals(object obj) + { + return obj is Texture2DDescriptor other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine + ( + Width, + Height, + (int)Format, + Linear ? 1 : 0 + ); + } + + public bool Equals(Texture2DDescriptor other) + { + return + Width == other.Width && + Height == other.Height && + Format == other.Format && + Linear == other.Linear; + } + + public static bool operator ==(Texture2DDescriptor lhs, Texture2DDescriptor rhs) => lhs.Equals(rhs); + + public static bool operator !=(Texture2DDescriptor lhs, Texture2DDescriptor rhs) => !(lhs == rhs); + + public override string ToString() + { + return $"{Width}x{Height} Format {Format} {(Linear ? "Linear" : "SRGB")}"; + } + } + + /// + /// Provides texture re-use across a frame and texture lifetime management. + /// Similar to . + /// + /// Lifetime doesn't grow during frames where no textures from the pool were used. + /// + class TemporaryTexture2DManager + { + struct FinishFrameRendering { } + + class Item + { + public readonly Texture2D Texture; + public int NumFramesSinceAccess; + + public Item(Texture2D texture) + { + Texture = texture; + NumFramesSinceAccess = 0; + } + + public void MarkAccess() + { + NumFramesSinceAccess = 0; + } + + public void Update() + { + NumFramesSinceAccess++; + } + } + + static TemporaryTexture2DManager s_Instance; + + public static TemporaryTexture2DManager Instance + { + get + { + if (s_Instance == null) + s_Instance = new TemporaryTexture2DManager(); + return s_Instance; + } + } + + // Same delay as in RenderTexture.GetTemporary() + const int k_FramesToWaitBeforeReleasing = 15; + + readonly Dictionary m_Items = new Dictionary(); + bool m_WasAccessedThisFrame; + + TemporaryTexture2DManager() + { + PlayerLoopExtensions.RegisterUpdate(OnFinishFrameRendering); + } + + public Texture2D Get(Texture2DDescriptor descriptor) + { + m_WasAccessedThisFrame = true; + + if (m_Items.TryGetValue(descriptor, out var item)) + { + Debug.Assert(item.Texture != null); + + item.MarkAccess(); + return item.Texture; + } + else + { + var texture = CreateTexture(descriptor); + var newItem = new Item(texture); + m_Items.Add(descriptor, newItem); + return texture; + } + } + + void OnFinishFrameRendering() + { + var texturesToRelease = ListPool.Get(); + + foreach (var (key, item) in m_Items) + { + if (ShouldRelease(key, item)) + { + texturesToRelease.Add(key); + } + else if (m_WasAccessedThisFrame) + { + item.Update(); + } + } + + foreach (var textureToRelease in texturesToRelease) + { + Release(textureToRelease); + } + + ListPool.Release(texturesToRelease); + m_WasAccessedThisFrame = false; + } + + bool ShouldRelease(Texture2DDescriptor descriptor, Item item) + { + return !descriptor.IsPlaceholderTexture && item.NumFramesSinceAccess >= k_FramesToWaitBeforeReleasing; + } + + void Release(Texture2DDescriptor descriptor) + { + if (m_Items.Remove(descriptor, out var item)) + { + DestroyTexture(item.Texture); + + DebugLog($"Released texture: {descriptor}"); + } + else + { + Debug.Assert(false); + } + } + + Texture2D CreateTexture(Texture2DDescriptor descriptor) + { + DebugLog($"Created texture: {descriptor}"); + + return DisguiseTextures.CreateTexture(descriptor.Width, descriptor.Height, descriptor.Format, !descriptor.Linear, null); + } + + void DestroyTexture(Texture2D texture) + { +#if UNITY_EDITOR + UnityEngine.Object.DestroyImmediate(texture); +#else + UnityEngine.Object.Destroy(texture); +#endif + } + + [Conditional("DISGUISE_VERBOSE_LOGGING")] + void DebugLog(string message) + { + Debug.Log($"Texture2DPool: {message}"); + } + } +} diff --git a/Runtime/TemporaryTexture2DManager.cs.meta b/Runtime/TemporaryTexture2DManager.cs.meta new file mode 100644 index 0000000..75cad5e --- /dev/null +++ b/Runtime/TemporaryTexture2DManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd9763cf8fae5e54dbbe86d826f180c3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/UnityDebugWindowPresenter.cs b/Runtime/UnityDebugWindowPresenter.cs new file mode 100644 index 0000000..310d093 --- /dev/null +++ b/Runtime/UnityDebugWindowPresenter.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Disguise.RenderStream.Utils; +using UnityEngine; +using UnityEngine.Assertions; + +namespace Disguise.RenderStream +{ + /// + /// This component together with the prefab of the same name offer drop-in support for presenting any Disguise-related texture to the Unity window. + /// + /// + /// + /// This class has two responsibilities: + /// + /// + /// 1. Generating the remote parameters for each scene - including the texture selection dropdown choices specific to each scene. + /// + /// + /// 2. Presenting a texture to the screen according to and . + /// + /// + class UnityDebugWindowPresenter : MonoBehaviour + { + /// + /// This is a user-friendly subset of . + /// + public enum PresenterResizeStrategies + { + /// + ActualSize, + /// + Stretch, + /// + Fill, + /// + Fit, + /// + Clamp + } + + const string k_NoneTextureLabel = "None"; + + /// + /// The index of the selection in the texture dropdown to present to the screen. + /// The dropdown choices are generated inside , as a concatenated list of: + /// None + Channels (output) + Live textures (input). + /// + public int Selected + { + get => m_Selected; + set => m_Selected = value; + } + + /// + /// The strategy for resizing the selected texture to screen. + /// + public PresenterResizeStrategies ResizeStrategy + { + get => m_ResizeStrategy; + set => m_ResizeStrategy = value; + } + + [SerializeField] + int m_Selected; + + [SerializeField] + PresenterResizeStrategies m_ResizeStrategy = PresenterResizeStrategies.Fit; + + [SerializeField] + CameraCapturePresenter m_OutputPresenter; + + [SerializeField] + Presenter m_InputPresenter; + + CameraCapture[] m_Outputs = {}; + RenderTexture[] m_Inputs = {}; + + static BlitStrategy.Strategy PresenterStrategyToBlitStrategy(PresenterResizeStrategies strategy) => strategy switch + { + PresenterResizeStrategies.ActualSize => BlitStrategy.Strategy.NoResize, + PresenterResizeStrategies.Stretch => BlitStrategy.Strategy.Stretch, + PresenterResizeStrategies.Fill => BlitStrategy.Strategy.Fill, + PresenterResizeStrategies.Fit => BlitStrategy.Strategy.Letterbox, + PresenterResizeStrategies.Clamp => BlitStrategy.Strategy.Clamp, + _ => throw new ArgumentOutOfRangeException() + }; + + /// + /// Instantiates a prefab with a GameObject hierarchy configured to be dropped into a scene. + /// It contains the and components, + /// as well as the necessary and . + /// + /// + public static GameObject LoadPrefab() + { + return Resources.Load(nameof(UnityDebugWindowPresenter)); + } + +#if UNITY_EDITOR + /// + /// Returns the list of remote parameters to control the presenter. + /// The parameters are pre-configured in the prefab used by . + /// + /// + /// The choices for the texture selection dropdown are scene-specific and correspond to a concatenated list of: + /// None + Channels (output) + Live textures (input). + /// + public static List GetManagedRemoteParameters(ManagedSchema schema, ManagedRemoteParameters sceneSchema) + { + var prefab = LoadPrefab(); + var parameters = prefab.GetComponent(); + var managedParameters = parameters.exposedParameters(); + + foreach (var parameter in managedParameters) + { + // Discard the name of the GameObject, keep only the field ex: + // "DisguisePresenter Mode" => "Mode" + parameter.displayName = parameter.displayName.Substring(parameter.displayName.IndexOf(" ") + 1); + + // Generate dropdown choices as a concatenated list of: None + Channels (output) + Live textures (input) + if (parameter.displayName == nameof(Selected)) + { + List options = new List(); + options.Add(k_NoneTextureLabel); + + foreach (var channel in schema.channels) + { + options.Add(channel); + } + + var remoteParameters = FindObjectsByType(FindObjectsSortMode.None); + foreach (var sceneParameter in sceneSchema.parameters) + { + var remoteParams = Array.Find(remoteParameters, rp => sceneParameter.key.StartsWith(rp.prefix)); + var field = new ObjectField(); + field.info = remoteParams.GetMemberInfoFromManagedParameter(sceneParameter); + + if (field.FieldType == typeof(Texture)) + { + options.Add(sceneParameter.displayName); + } + } + + parameter.options = options.ToArray(); + } + } + + return managedParameters; + } +#endif + + /// + /// This class should only be instantiated through . + /// + private UnityDebugWindowPresenter() + { + + } + + void OnEnable() + { + DisguiseRenderStream.SceneLoaded += RefreshInput; + DisguiseRenderStream.StreamsChanged += RefreshOutput; + } + + void OnDisable() + { + DisguiseRenderStream.SceneLoaded -= RefreshInput; + DisguiseRenderStream.StreamsChanged -= RefreshOutput; + } + + void Update() + { + Assert.IsNotNull(m_OutputPresenter); + Assert.IsNotNull(m_InputPresenter); + + if (VirtualIndexToOutputIndex(Selected) is { } outputIndex) + { + m_OutputPresenter.enabled = true; + m_InputPresenter.enabled = false; + m_OutputPresenter.cameraCapture = m_Outputs[outputIndex]; + } + else if (VirtualIndexToInputIndex(Selected) is { } inputIndex) + { + m_InputPresenter.enabled = true; + m_OutputPresenter.enabled = false; + m_InputPresenter.source = m_Inputs[inputIndex]; + } + else + { + m_OutputPresenter.enabled = m_InputPresenter.enabled = false; + return; + } + + m_OutputPresenter.strategy = m_InputPresenter.strategy = PresenterStrategyToBlitStrategy(m_ResizeStrategy); + } + + /// + /// Maps a virtual index like to a real index into . + /// The virtual list is described in . + /// + /// + /// An index into , or null when no output is selected. + /// + int? VirtualIndexToOutputIndex(int virtualIndex) + { + var outputIndex = virtualIndex - 1; + + if (outputIndex < 0 || outputIndex >= m_Outputs.Length) + return null; + + return outputIndex; + } + + /// + /// Maps a virtual index like to a real index into . + /// The virtual list is described in . + /// + /// + /// An index into , or null when no input is selected. + /// + int? VirtualIndexToInputIndex(int virtualIndex) + { + var inputIndex = virtualIndex - 1 - m_Outputs.Length; + + if (inputIndex < 0 || inputIndex >= m_Inputs.Length) + return null; + + return inputIndex; + } + + void RefreshOutput() + { + var channels = DisguiseRenderStream.Instance.Schema.channels; + Array.Resize(ref m_Outputs, channels.Length); + Array.Fill(m_Outputs, null); + + // The list of streams is an unordered subset of the list of channels + foreach (var stream in DisguiseRenderStream.Instance.Streams) + { + var index = Array.IndexOf(channels, stream.Description.channel); + m_Outputs[index] = stream.Capture.Capture; + } + } + + void RefreshInput() + { + // InputTextures are in the same order as in the schema, no sorting required + m_Inputs = DisguiseRenderStream.Instance.InputTextures.ToArray(); + } + } +} diff --git a/Runtime/UnityDebugWindowPresenter.cs.meta b/Runtime/UnityDebugWindowPresenter.cs.meta new file mode 100644 index 0000000..6da0dd6 --- /dev/null +++ b/Runtime/UnityDebugWindowPresenter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2c135780edf6e674ca031a94a2e3b4ee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Util.meta b/Runtime/Util.meta new file mode 100644 index 0000000..d5faffe --- /dev/null +++ b/Runtime/Util.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9a41ff29d841483469126f0e7fac24c1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Util/ApplicationPath.cs b/Runtime/Util/ApplicationPath.cs new file mode 100644 index 0000000..aece7c0 --- /dev/null +++ b/Runtime/Util/ApplicationPath.cs @@ -0,0 +1,43 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Disguise.RenderStream.Utils +{ + /// + /// This is a P/Invoke alternative to the System.Diagnostics.Process API which isn't supported in IL2CPP. + /// + static class ApplicationPath + { + public static string GetExecutablePath() + { + var processHandle = Native.GetCurrentProcess(); + + uint size = Native.UNICODE_STRING_MAX_CHARS; + StringBuilder strBuffer = new StringBuilder((int)size); + + if (Native.QueryFullProcessImageName(processHandle, 0, strBuffer, ref size)) + return strBuffer.ToString(); + + return null; + } + + static class Native + { + const string kerneldll = "kernel32.dll"; + + // MAX_PATH (260) doesn't cover UNC paths + public const int UNICODE_STRING_MAX_CHARS = 32767; + + [DllImport(kerneldll, SetLastError = true)] + public static extern UIntPtr GetCurrentProcess(); + + [DllImport(kerneldll, CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool QueryFullProcessImageName( + UIntPtr hProcess, + uint dwFlags, + StringBuilder lpExeName, + ref uint lpdwSize); + } + } +} diff --git a/Runtime/Util/ApplicationPath.cs.meta b/Runtime/Util/ApplicationPath.cs.meta new file mode 100644 index 0000000..85f363c --- /dev/null +++ b/Runtime/Util/ApplicationPath.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 642413397fc325c49a0b9178f45cd88c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Util/AutoDisposable.cs b/Runtime/Util/AutoDisposable.cs new file mode 100644 index 0000000..d7c5623 --- /dev/null +++ b/Runtime/Util/AutoDisposable.cs @@ -0,0 +1,28 @@ +#if UNITY_EDITOR +using UnityEditor; +#else +using UnityEngine; +#endif + +namespace Disguise.RenderStream.Utils +{ + abstract class AutoDisposable + { + protected AutoDisposable() + { + Register(); + } + + void Register() + { +#if UNITY_EDITOR + EditorApplication.quitting += Dispose; + AssemblyReloadEvents.beforeAssemblyReload += Dispose; +#else + Application.quitting += Dispose; +#endif + } + + protected abstract void Dispose(); + } +} diff --git a/Runtime/Util/AutoDisposable.cs.meta b/Runtime/Util/AutoDisposable.cs.meta new file mode 100644 index 0000000..48f1547 --- /dev/null +++ b/Runtime/Util/AutoDisposable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4209034702abda0428aa6f6428d6927c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Util/DynamicSetterCache.cs b/Runtime/Util/DynamicSetterCache.cs new file mode 100644 index 0000000..972ab3e --- /dev/null +++ b/Runtime/Util/DynamicSetterCache.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; + +namespace Disguise.RenderStream.Utils +{ + /// + /// Contains a dynamically-generated collection of delegates to set a property or field + /// of a given type. + /// + /// + /// + /// This class can be used to avoid boxing operations for calling property or field + /// setters when the parameter type is unknown at compile-time, resulting in better + /// performance. + /// + static class DynamicSetterCache + { + static readonly Dictionary> k_Cache = new(); + + public static Action GetSetter(MemberInfo member) + { + if (!k_Cache.TryGetValue(member, out var setter)) + { + var newMethod = new DynamicMethod($"{typeof(T).FullName}.argument_set_{member.Name}", + returnType: null, + parameterTypes: new[] + { + typeof(object), + typeof(T) + }); + var gen = newMethod.GetILGenerator(); + switch (member) + { + case FieldInfo field: + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(OpCodes.Stfld, field); + gen.Emit(OpCodes.Ret); + + break; + } + case PropertyInfo property: + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(OpCodes.Call, property.GetSetMethod()); + gen.Emit(OpCodes.Ret); + + break; + } + default: + throw new ArgumentOutOfRangeException(nameof(member), member, null); + } + + setter = (Action)newMethod.CreateDelegate(typeof(Action)); + k_Cache.Add(member, setter); + } + + return setter; + } + } +} diff --git a/Runtime/Util/DynamicSetterCache.cs.meta b/Runtime/Util/DynamicSetterCache.cs.meta new file mode 100644 index 0000000..623cff7 --- /dev/null +++ b/Runtime/Util/DynamicSetterCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 686d6a171c6b1d748a33442e10d265f4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Util/MulticastExtensions.cs b/Runtime/Util/MulticastExtensions.cs new file mode 100644 index 0000000..b45e94a --- /dev/null +++ b/Runtime/Util/MulticastExtensions.cs @@ -0,0 +1,104 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using UnityEngine; +using UnityEngine.Assertions; + +namespace Disguise.RenderStream.Utils +{ + static class MulticastExtensions + { + public static void EnableMulticast(this UdpClient udpClient, IPAddress multicastIPAddress, int port, IPAddress adapterAddress, int timeoutMs = 5000) + { + udpClient.Client.SendTimeout = timeoutMs; + udpClient.Client.ReceiveTimeout = timeoutMs; + + // Ask to send the multicast traffic using the specified adapter + udpClient.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastInterface, + adapterAddress.GetAddressBytes()); + + // Allow multiple ClusterDisplay applications to bind on the same address and port. Useful for when running + // multiple nodes locally and unit testing. + // Food for thought: Does it have a performance cost? Do we want to have it configurable or disabled in some + // cases? + udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + + // Bind to receive from the selected adapter on the same port than the port we are sending to (everyone will + // use the same port). + udpClient.Client.Bind(new IPEndPoint(adapterAddress, port)); + + // Join the multicast group + udpClient.JoinMulticastGroup(multicastIPAddress); + + // This is normally true by default but this is required to keep things simple (unit tests working, multiple + // instances on the same computer, ...). So let's get sure it is on. + udpClient.MulticastLoopback = true; + } + + public static (string name, IPAddress address) SelectNetworkInterface(string adapterName) + { + var nics = NetworkInterface.GetAllNetworkInterfaces(); + NetworkInterface bestNic = null; + UnicastIPAddressInformation bestNicAddress = null; + + foreach (var nic in nics) + { + // Check if NIC supports multicast. + // Skip loopback adapter as they cause all sort of problems with multicast... + if (nic.OperationalStatus is not OperationalStatus.Up || + nic.NetworkInterfaceType is NetworkInterfaceType.Loopback || + !nic.SupportsMulticast) + { + continue; + } + + // Check that NIC has multicast addresses + if (nic.GetIPProperties() is not { } ipProperties || !ipProperties.MulticastAddresses.Any()) + { + continue; + } + + // Check that NIC has unicast addresses + if (ipProperties.UnicastAddresses.FirstOrDefault(ip => + ip.Address.AddressFamily == AddressFamily.InterNetwork) is not + { } unicastAddressInfo) + { + continue; + } + + // If we have multiple candidate NICs, try to use the one with the highest reported speed. + if (bestNic == null || nic.Speed > bestNic.Speed) + { + bestNic = nic; + bestNicAddress = unicastAddressInfo; + } + + if (!string.IsNullOrEmpty(adapterName) && nic.Name == adapterName) + { + Debug.Log($"Selecting explicit interface: \"{nic.Name} with ip {unicastAddressInfo.Address}\"."); + return (nic.Name, unicastAddressInfo.Address); + } + } + + // If we reach this point then there was no explicit nic specified or the explicit nic is unavailable; + // use the "best" nic as the automatic one + if (bestNic == null) + { + throw new InvalidOperationException("There are no available interfaces for Cluster Display communication."); + } + + if (!string.IsNullOrEmpty(adapterName)) + { + Debug.LogError($"Unable to use explicit interface {adapterName}. Using \"{bestNic.Name}\" instead."); + } + else + { + Debug.Log($"No explicit interface defined, defaulting to interface: \"{bestNic.Name}\"."); + } + return (bestNic.Name, bestNicAddress.Address); + } + } +} diff --git a/Runtime/Util/MulticastExtensions.cs.meta b/Runtime/Util/MulticastExtensions.cs.meta new file mode 100644 index 0000000..13bd31b --- /dev/null +++ b/Runtime/Util/MulticastExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4100c2fbfd44e7342abc16a4217d7fcb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Util/ObjectField.cs b/Runtime/Util/ObjectField.cs new file mode 100644 index 0000000..1a5ed14 --- /dev/null +++ b/Runtime/Util/ObjectField.cs @@ -0,0 +1,52 @@ +using System; +using System.Reflection; + +namespace Disguise.RenderStream.Utils +{ + class ObjectField + { + public object target; + public MemberInfo info; + + public Type FieldType => + info switch + { + FieldInfo fieldInfo => fieldInfo.FieldType, + PropertyInfo propertyInfo => propertyInfo.PropertyType, + _ => typeof(void) + }; + + public void SetValue(object value) + { + switch (info) + { + case FieldInfo fieldInfo: + fieldInfo.SetValue(target, value); + break; + case PropertyInfo propertyInfo: + propertyInfo.SetValue(target, value); + break; + } + } + + public object GetValue() + { + return info switch + { + FieldInfo fieldInfo => fieldInfo.GetValue(target), + PropertyInfo propertyInfo => propertyInfo.GetValue(target), + _ => null + }; + } + + public void SetValue(T value) + { +#if ENABLE_IL2CPP + SetValue((object)value); +#else + var setter = DynamicSetterCache.GetSetter(info); + setter.Invoke(target, value); +#endif + } + } +} diff --git a/Runtime/Util/ObjectField.cs.meta b/Runtime/Util/ObjectField.cs.meta new file mode 100644 index 0000000..ef2c21f --- /dev/null +++ b/Runtime/Util/ObjectField.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c01abc3ea0db7c3488e4cb9ba8d2b94c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Util/PlayerLoopExtensions.cs b/Runtime/Util/PlayerLoopExtensions.cs new file mode 100644 index 0000000..75e02a7 --- /dev/null +++ b/Runtime/Util/PlayerLoopExtensions.cs @@ -0,0 +1,206 @@ +using System; +using UnityEngine; +using UnityEngine.LowLevel; + +namespace Disguise.RenderStream.Utils +{ + /// + /// A class that contains extension methods used to modify the player loop. + /// + static class PlayerLoopExtensions + { + /// + /// Adds an update callback to the specified subsystem. + /// + /// + /// The subsystem is added to the player loop if it does not already exist. + /// + /// The update callback to register. + /// The index to insert the subsystem if it does not already exist. By default, the subsystem is + /// appended to the end of the subsystem list of the given system. + /// if the update callback was successfully registered; + /// if could not be found in the player loop. + /// The system to add the subsystem to. + /// The system to add the callback to. + /// Thrown if is null. + public static bool RegisterUpdate(PlayerLoopSystem.UpdateFunction update, int index = -1) + { + if (update == null) + throw new ArgumentNullException(nameof(update)); + + var loop = PlayerLoop.GetCurrentPlayerLoop(); + + if (!loop.TryFindSubSystem(out var system)) + { + return false; + } + + if (system.TryFindSubSystem(out var subSystem)) + { + // ensure we do not call the update method multiple times if already registered + subSystem.updateDelegate -= update; + subSystem.updateDelegate += update; + system.TryUpdate(subSystem); + } + else + { + // if the index is invalid append the subsystem + var subSystems = system.subSystemList; + var subSystemCount = subSystems != null ? subSystems.Length : 0; + + if (index < 0 || index > subSystemCount) + index = subSystemCount; + + system.AddSubSystem(index, update); + } + + loop.TryUpdate(system); + PlayerLoop.SetPlayerLoop(loop); + return true; + } + + /// + /// Removes an update callback from the specified subsystem. + /// + /// The update callback to deregister. + /// The system to remove the callback from. + /// Thrown if is null. + public static void DeregisterUpdate(PlayerLoopSystem.UpdateFunction update) + { + if (update == null) + throw new ArgumentNullException(nameof(update)); + + var loop = PlayerLoop.GetCurrentPlayerLoop(); + + if (loop.TryFindSubSystem(out var subSystem)) + { + subSystem.updateDelegate -= update; + loop.TryUpdate(subSystem); + } + + PlayerLoop.SetPlayerLoop(loop); + } + + /// + /// Recursively finds a subsystem of this system by type. + /// + /// The type of subsystem to find. + /// The system to search. + /// The returned subsystem. + /// True if a subsystem with a matching type was found; false otherwise. + public static bool TryFindSubSystem(this PlayerLoopSystem system, out PlayerLoopSystem result) + { + if (system.type == typeof(T)) + { + result = system; + return true; + } + + if (system.subSystemList != null) + { + foreach (var subSystem in system.subSystemList) + { + if (subSystem.TryFindSubSystem(out result)) + { + return true; + } + } + } + + result = default; + return false; + } + + /// + /// Applies changes made to a subsystem to a system. + /// + /// The system to update. + /// The modified subsystem. + /// True if the subsystem was successfully updated; false otherwise. + public static bool TryUpdate(this ref PlayerLoopSystem system, PlayerLoopSystem subSystemToUpdate) + { + if (system.type == subSystemToUpdate.type) + { + system = subSystemToUpdate; + return true; + } + + if (system.subSystemList != null) + { + for (var i = 0; i < system.subSystemList.Length; i++) + { + if (system.subSystemList[i].TryUpdate(subSystemToUpdate)) + { + return true; + } + } + } + + return false; + } + + /// + /// Adds a new subsystem to a system. + /// + /// The type of the subsystem to add. + /// The system to add the subsystem to. + /// The index of the subsystem in the subsystem array. + /// The function called to update the new subsystem. + /// Thrown if is less than zero or larger then the + /// subsystem array length. + public static void AddSubSystem(this ref PlayerLoopSystem system, int index, PlayerLoopSystem.UpdateFunction update) + { + var subSystems = system.subSystemList; + var oldLength = subSystems != null ? subSystems.Length : 0; + + if (index < 0 || index > oldLength) + throw new ArgumentOutOfRangeException(nameof(index), index, "Must be non-negative value no larger than subsystem array length."); + + var newSubSystems = new PlayerLoopSystem[oldLength + 1]; + + for (var i = 0; i < oldLength; i++) + { + if (i < index) + { + newSubSystems[i] = subSystems[i]; + } + else if (i >= index) + { + newSubSystems[i + 1] = subSystems[i]; + } + } + + newSubSystems[index] = new PlayerLoopSystem + { + type = typeof(T), + updateDelegate = update, + }; + + system.subSystemList = newSubSystems; + } + + /// + /// Finds the index of the subsystem in the list of subsystems of the provided system. + /// + /// The type of the subsystem to search for. + /// The system to use for the search. + /// The index of the subsystem if found. Otherwise; -1. + public static int IndexOf(this ref PlayerLoopSystem system) + { + var type = typeof(T); + + if (system.subSystemList != null) + { + for (var i = 0; i < system.subSystemList.Length; ++i) + { + if (type == system.subSystemList[i].type) + { + return i; + } + } + } + + return -1; + } + } +} diff --git a/Runtime/Util/PlayerLoopExtensions.cs.meta b/Runtime/Util/PlayerLoopExtensions.cs.meta new file mode 100644 index 0000000..634bdfc --- /dev/null +++ b/Runtime/Util/PlayerLoopExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 580ede70a280d5a418df4679c7a251f2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Util/RegistryWrapper.cs b/Runtime/Util/RegistryWrapper.cs new file mode 100644 index 0000000..6fded57 --- /dev/null +++ b/Runtime/Util/RegistryWrapper.cs @@ -0,0 +1,91 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Disguise.RenderStream.Utils +{ + /// + /// This is a P/Invoke alternative to the Microsoft.Win32.Registry API which isn't supported across all of our .NET standards. + /// + /// https://www.pinvoke.net/default.aspx/advapi32/regopenkeyex.html was used as reference. + /// + static class RegistryWrapper + { + public static UIntPtr HKEY_LOCAL_MACHINE = new UIntPtr(0x80000002u); + public static UIntPtr HKEY_CURRENT_USER = new UIntPtr(0x80000001u); + + public enum ReadRegKeyResult + { + Success, + OpenFailed, + QueryValueFailed, + TypeNotSupported + } + + // Only supports REG_SZ registry value type + public static ReadRegKeyResult ReadRegKey(UIntPtr rootKey, string keyPath, string valueName, out string value) + { + value = null; + + if (Native.RegOpenKeyEx(rootKey, keyPath, 0, Native.KEY_READ, out var hKey) == Native.ERROR_SUCCESS) + { + try + { + uint size = 1024; + StringBuilder keyBuffer = new StringBuilder((int)size); + + if (Native.RegQueryValueEx(hKey, valueName, IntPtr.Zero, out var type, keyBuffer, ref size) == Native.ERROR_SUCCESS) + { + if (type == Native.REG_SZ) + { + value = keyBuffer.ToString(); + return ReadRegKeyResult.Success; + } + + return ReadRegKeyResult.TypeNotSupported; + } + + return ReadRegKeyResult.QueryValueFailed; + } + finally + { + Native.RegCloseKey(hKey); + } + } + + return ReadRegKeyResult.OpenFailed; + } + + static class Native + { + const string advapidll = "advapi32.dll"; + + public const int ERROR_SUCCESS = 0x0; + + public const int KEY_READ = 0x20019; + + public const uint REG_NONE = 0x00000000; + public const uint REG_SZ = 0x00000001; + + [DllImport(advapidll, CharSet = CharSet.Auto, SetLastError = true)] + public static extern int RegOpenKeyEx( + UIntPtr hKey, + string subKey, + int ulOptions, + int samDesired, + out UIntPtr hkResult); + + [DllImport(advapidll, CharSet = CharSet.Auto, SetLastError = true)] + public static extern int RegQueryValueEx( + UIntPtr hKey, + string lpValueName, + IntPtr lpReserved, + out uint lpType, + StringBuilder lpData, + ref uint lpcbData); + + [DllImport(advapidll, SetLastError = true)] + public static extern int RegCloseKey(UIntPtr hKey); + } + } +} diff --git a/Runtime/Util/RegistryWrapper.cs.meta b/Runtime/Util/RegistryWrapper.cs.meta new file mode 100644 index 0000000..0ec24ab --- /dev/null +++ b/Runtime/Util/RegistryWrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 191567f8d1ce5fb42b395f90070096bb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json new file mode 100644 index 0000000..ef6857e --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "com.disguise.renderstream", + "displayName": "Disguise RenderStream", + "version": "1.0.0", + "unity": "2022.2", + "unityRelease": "6f1", + "description": "Disguise RenderStream protocol integration with Unity.", + "keywords": [ + "disguise", + "renderstream", + "virtual production" + ], + "dependencies": { + "com.unity.render-pipelines.core": "13.1.8", + "com.unity.timeline" : "1.0.0", + "com.unity.ugui": "1.0.0" + } +} diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..d521a16 --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3f826ec5a26a16141aa6169fe605a081 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/readme.md b/readme.md deleted file mode 100644 index d619631..0000000 --- a/readme.md +++ /dev/null @@ -1,39 +0,0 @@ -# RenderStream Unity Plugin - -![alt text](https://download.disguise.one/media/6921/unity.png) - -For more info please refer to our [RenderStream and Unity Help page](https://help.disguise.one/Content/Configuring/Render-engines/RenderStream-Unity.htm) - -A **Demo Unity Project** can be found on the [disguise Resources page](https://download.disguise.one/#resources) - -## Importing the RenderStream Unity Plugin - -* Copy/Import the top-level **DisguiseUnityRenderStream** folder to your Unity Project's **Assets** folder. -* If you get an error RegistryKey/Registry is not part of namespace Microsoft.Win32 go to File > Build Settings > Player Settings... > Other Settings: - * If available **change Api Compatibility level to .NET 4.x.** - * Else, change Scripting runtime version to .NET 3.5 equivalent. - * If there is an error about unsafe code tick allow 'unsafe' code, this is project wide and should not be done unless unity ignores the asmdef for this plugin. - -## Using the RenderStream Unity Plugin - -The act of importing the **DisguiseUnityRenderStream** plugin to your Unity Project's Asset folder, is enough to enable RenderStream in your built executable. - -More control can be added using the included Disguise RenderStream components: - -* **To enable control of GameObject(s) properties**, attach a Disguise RenderStream > **Remote Parameters** component to the appropriate game object for remote parameter control. - * Note: you can enable/disable the exact GameObject properties using the List editor in the Unity Inspector. -* **To add designer timeline control**, attach a Disguise RenderStream > **Time Control** component to a Playable Director - -## Building a RenderStream asset for disguise designer - -To use your Unity Project with disguise designer, you build an executable and import it into your designer project. To do this: -* Ensure Build Settings are set to build a **Windows x86_64** executable -* Copy the build folder to your **RenderStream Projects** folder -* In designer, set up a new RenderStream Layer and point the Asset to your built executable. - -## Notes: - -* Without a disguise instance running and sending data to the Unity camera, if you run your scene the camera will be placed at the world origin and cannot be moved (it's position is overriden every frame). -* Firewall settings can interfere with transport streams. You may see a Windows security dialog pop-up the first time you run the asset. - * Click Enable / Allow on this Windows pop-up to allow RenderStream communication to/from designer. -* If the firewall is the problem check your outbound firewall rules on the Unity machine, and the inbound firewall rules on the disguise machine for either software being specifically blocked.