diff --git a/source/Unity/FrameRecorder.meta b/source/Unity/FrameRecorder.meta new file mode 100644 index 0000000..ebe390b --- /dev/null +++ b/source/Unity/FrameRecorder.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: a08b35c30775e274cacf0ab18c4d82cb +folderAsset: yes +timeCreated: 1491415339 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts.meta b/source/Unity/FrameRecorder/Scripts.meta new file mode 100644 index 0000000..b005691 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: d603226de9e3cc44e89a6c7354da487d +folderAsset: yes +timeCreated: 1491415339 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Editor.meta b/source/Unity/FrameRecorder/Scripts/Editor.meta new file mode 100644 index 0000000..1b08886 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: a6b346140d4dd0c48b4431a7f27d450d +folderAsset: yes +timeCreated: 1491415339 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Editor/ImageRecorderEditor.cs b/source/Unity/FrameRecorder/Scripts/Editor/ImageRecorderEditor.cs new file mode 100644 index 0000000..bbf196f --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Editor/ImageRecorderEditor.cs @@ -0,0 +1,109 @@ +using System; +using UnityEngine; +using UnityEngine.Recorder.FrameRecorder; + +namespace UnityEditor.Recorder.FrameRecorder +{ + public abstract class DefaultImageRecorderSettingsEditor : RecorderSettingsEditor + { + string[] m_Displays; + + protected override void OnEnable() + { + base.OnEnable(); + var displayCount = Display.displays.Length; + m_Displays = new string[displayCount]; + for (int i = 0; i < displayCount; i++) + m_Displays[i] = string.Format("Display {0}", i + 1); + } + + protected override void OnInputGui() + { + var settingsObj = serializedObject.targetObject as ImageRecorderSettings; + + m_LayoutHelper.AddEnumProperty("Source:", serializedObject, () => settingsObj.m_InputType); + + switch (settingsObj.m_InputType) + { + case EImageSourceType.GameDisplay: + { + m_LayoutHelper.indentLevel++; + + m_LayoutHelper.AddEnumProperty("Size:", serializedObject, () => settingsObj.m_SizeMode); + + switch (settingsObj.m_SizeMode) + { + case EImageSizeMode.Dynamic: + break; + case EImageSizeMode.FullScreen: + break; + case EImageSizeMode.Width: + m_LayoutHelper.AddIntProperty("Resolution (width)", serializedObject, () => settingsObj.m_Width); + break; + case EImageSizeMode.Custom: + m_LayoutHelper.AddIntProperty("Resolution (width)", serializedObject, () => settingsObj.m_Width); + m_LayoutHelper.AddIntProperty("Resolution (height)", serializedObject, () => settingsObj.m_Height); + break; + } + + m_LayoutHelper.indentLevel--; + break; + } + case EImageSourceType.RenderTexture: + { + OnCustomInputGui(); + break; + } + + case EImageSourceType.TaggedCamera: + { + m_LayoutHelper.indentLevel++; + m_LayoutHelper.AddStringProperty("Tags:", serializedObject, () => settingsObj.m_CameraTag); + m_LayoutHelper.indentLevel--; + break; + } + + case EImageSourceType.MainCamera: + break; + } + + base.OnInputGui(); + } + + protected override void OnEncodingGui() + { + //base.OnOutputGui(); + //var settingsObj = target as ImageRecorderSettings; + + /* + + switch (settingsObj.m_InputType) + { + case EImageSourceType.GameDisplay: + { + m_LayoutHelper.AddBoolProperty("Scale image size", serializedObject, () => settingsObj.m_ScaleImage); + + if (settingsObj.m_ScaleImage) + { + settingsObj.m_PreserveSourceAspectRatio = true; + m_LayoutHelper.AddIntProperty("Resolution (width)", serializedObject, () => settingsObj.m_Width); + } + break; + } + + case EImageSourceType.MainCamera: + case EImageSourceType.TaggedCamera: + { + settingsObj.m_PreserveSourceAspectRatio = true; + m_LayoutHelper.AddIntProperty("Resolution (width)", serializedObject, () => settingsObj.m_Width); + break; + } + } + */ + } + + protected virtual void OnCustomInputGui() + { + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Editor/ImageRecorderEditor.cs.meta b/source/Unity/FrameRecorder/Scripts/Editor/ImageRecorderEditor.cs.meta new file mode 100644 index 0000000..9ec164b --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Editor/ImageRecorderEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 34dd54c4c05f9c7429eb3f50bcdac0f4 +timeCreated: 1491415343 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Editor/RecorderClipEditor.cs b/source/Unity/FrameRecorder/Scripts/Editor/RecorderClipEditor.cs new file mode 100644 index 0000000..d4bb744 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Editor/RecorderClipEditor.cs @@ -0,0 +1,157 @@ +using System; +using System.Resources; +using Assets.Unity.FrameRecorder.Scripts.Editor; +using UnityEngine.Recorder.FrameRecorder.Utilities; +using UnityEngine; +using UnityEngine.Recorder.FrameRecorder; +using UnityEngine.Recorder.FrameRecorder.Timeline; +using UnityEngine.Timeline; + +namespace UnityEditor.Recorder.FrameRecorder.Timeline +{ + [CustomEditor(typeof(FrameRecorderClip), true)] + public class RecorderClipEditor : Editor, IRecorderSelectorTarget + { + RecorderSettingsEditor m_SettingsEditor; + TimelineAsset m_Timeline; + RecorderSelector m_recorderSelector; + + public void OnEnable() + { + m_recorderSelector = new RecorderSelector(this); + var shot = this.target as FrameRecorderClip; + var editorType = RecorderSettingsEditor.FindEditorForRecorder(shot.recorderType); + if (editorType != null) + { + m_SettingsEditor = Editor.CreateEditor(shot.m_Settings, editorType) as RecorderSettingsEditor; + } + } + + public override void OnInspectorGUI() + { + if (target == null) + return; + + m_recorderSelector.OnGui(); + + if (m_SettingsEditor != null) + { + m_SettingsEditor.showBounds = false; + m_Timeline = FindTimelineAsset(); + + PushTimelineIntoRecorder(); + + using (new EditorGUI.DisabledScope(EditorApplication.isPlaying)) + { + EditorGUILayout.Separator(); + + using (new EditorGUI.DisabledScope(true)) + { + GUILayout.BeginHorizontal(); + GUILayout.Label("Settings ID:"); + EditorGUILayout.TextField((m_SettingsEditor.target as FrameRecorderSettings).m_UniqueID); + GUILayout.EndHorizontal(); + } + + + m_SettingsEditor.OnInspectorGUI(); + + EditorGUILayout.Separator(); + + PushRecorderIntoTimeline(); + + serializedObject.Update(); + } + } + } + + public string recorderCategory + { + get + { + var shot = this.target as FrameRecorderClip; + return shot.m_RecorderCategory; + } + + set + { + var shot = this.target as FrameRecorderClip; + if (shot.m_RecorderCategory != value) + { + shot.m_RecorderCategory = value; + m_SettingsEditor = null; + shot.recorderType = null; + } + } + } + + public string selectedRecorder + { + get + { + var shot = this.target as FrameRecorderClip; + return shot.m_RecorderTypeName; + } + } + + public void SetRecorder(Type newRecorderType) + { + var clip = this.target as FrameRecorderClip; + + if (newRecorderType == null || (m_SettingsEditor != null && m_SettingsEditor.target != null && clip.recorderType == newRecorderType)) + return; + + clip.recorderType = newRecorderType; + + var editorType = RecorderSettingsEditor.FindEditorForRecorder(clip.recorderType); + if (editorType != null) + { + var assetGuid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(clip)); + clip.m_Settings = RecordersInventory.CreateRecorderSettings(clip.recorderType, assetGuid, "TmLn-Clip:" + assetGuid); + m_SettingsEditor = CreateEditor(clip.m_Settings, editorType) as RecorderSettingsEditor; + } + else + Debug.LogError(string.Format("No editor class declared for recorder of type " + newRecorderType.FullName)); + } + + TimelineAsset FindTimelineAsset() + { + if (!AssetDatabase.Contains(target)) + return null; + + var path = AssetDatabase.GetAssetPath(target); + var objs = AssetDatabase.LoadAllAssetsAtPath(path); + + foreach (var obj in objs) + { + if (obj != null && AssetDatabase.IsMainAsset(obj)) + return obj as TimelineAsset; + } + return null; + } + + void PushTimelineIntoRecorder() + { + if (m_Timeline == null) + return; + + var settings = m_SettingsEditor.target as FrameRecorderSettings; + settings.m_DurationMode = DurationMode.Indefinite; + + // Time + settings.m_FrameRate = m_Timeline.editorSettings.fps; + } + + void PushRecorderIntoTimeline() + { + if (m_Timeline == null) + return; + + var settings = m_SettingsEditor.target as FrameRecorderSettings; + settings.m_DurationMode = DurationMode.Indefinite; + + // Time + m_Timeline.editorSettings.fps = (float)settings.m_FrameRate; + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Editor/RecorderClipEditor.cs.meta b/source/Unity/FrameRecorder/Scripts/Editor/RecorderClipEditor.cs.meta new file mode 100644 index 0000000..a5cae5f --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Editor/RecorderClipEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c7fe8472aebc45f4b93027b60a4003b3 +timeCreated: 1491415344 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Editor/RecorderSelector.cs b/source/Unity/FrameRecorder/Scripts/Editor/RecorderSelector.cs new file mode 100644 index 0000000..bb2c19a --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Editor/RecorderSelector.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEditor; +using UnityEngine; +using UnityEngine.Recorder.FrameRecorder; + +namespace Assets.Unity.FrameRecorder.Scripts.Editor +{ + interface IRecorderSelectorTarget + { + string recorderCategory { get; set; } + string selectedRecorder { get; } + void SetRecorder(Type newRecorderType); + } + + class RecorderSelector + { + IRecorderSelectorTarget m_target; + string[] categoryRecorders; + + public RecorderSelector(IRecorderSelectorTarget target) + { + m_target = target; + } + + int GetCategoryIndex() + { + var categories = RecordersInventory.availableCategories; + for (int i = 0; i < categories.Length; i++) + if (categories[i] == m_target.recorderCategory) + return i; + + if (categories.Length > 0) + return 0; + else + return -1; + } + + bool SetCategoryFromIndex(int index) + { + if (index >= 0) + { + m_target.recorderCategory = RecordersInventory.availableCategories[index]; + categoryRecorders = RecordersInventory.recordersByCategory[m_target.recorderCategory] + .Select(x => x.displayName) + .ToArray(); + } + else + { + m_target.recorderCategory = string.Empty; + categoryRecorders = new string[0]; + } + + return index >= 0; + } + + int GetRecorderIndex() + { + if (!RecordersInventory.recordersByCategory.ContainsKey(m_target.recorderCategory)) + return -1; + + var categoryRecorders = RecordersInventory.recordersByCategory[m_target.recorderCategory]; + for (int i = 0; i < categoryRecorders.Count; i++) + if (categoryRecorders[i].recorder.AssemblyQualifiedName == m_target.selectedRecorder) + return i; + + + if (categoryRecorders.Count > 0) + return 0; + else + return -1; + } + + Type GetRecorderFromIndex(int index) + { + if (index >= 0) + return RecordersInventory.recordersByCategory[m_target.recorderCategory][index].recorder; + + return null; + } + + public void OnGui() + { + // Group selection + EditorGUILayout.BeginHorizontal(); + SetCategoryFromIndex(EditorGUILayout.Popup("Record what:", GetCategoryIndex(), RecordersInventory.availableCategories)); + EditorGUILayout.EndHorizontal(); + + // Recorder in group selection + EditorGUILayout.BeginHorizontal(); + var newIndex = EditorGUILayout.Popup("Using Recorder:", GetRecorderIndex(), categoryRecorders); + m_target.SetRecorder(GetRecorderFromIndex(newIndex)); + EditorGUILayout.EndHorizontal(); + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Editor/RecorderSelector.cs.meta b/source/Unity/FrameRecorder/Scripts/Editor/RecorderSelector.cs.meta new file mode 100644 index 0000000..26c6322 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Editor/RecorderSelector.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b94e4c4dfdb07cb43b93121a598b6e00 +timeCreated: 1493839035 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Editor/RecorderSettingsEditor.cs b/source/Unity/FrameRecorder/Scripts/Editor/RecorderSettingsEditor.cs new file mode 100644 index 0000000..dc45f2e --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Editor/RecorderSettingsEditor.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using UnityEditor.Recorder.FrameRecorder.Utilities; +using UnityEngine; +using UnityEngine.Recorder.FrameRecorder; +using UnityEngine.Recorder.FrameRecorder.Utilities; + +namespace UnityEditor.Recorder.FrameRecorder +{ + public class RecorderEditorAttribute : Attribute + { + public Type recorderType { get; private set; } + + public RecorderEditorAttribute(Type type) + { + recorderType = type; + } + } + + public abstract class RecorderSettingsEditor : Editor + { + static SortedDictionary m_Editors; + + internal LayoutHelper m_LayoutHelper; + + public virtual Vector2 minSize + { + get { return new Vector2(0, 0); } + } + + protected virtual void OnEnable() + { + m_LayoutHelper = new LayoutHelper(GUILayout.Width(175)); + } + + protected virtual void OnDisable() {} + + protected virtual void Awake() {} + + public bool isValid + { + get { return (target as FrameRecorderSettings).isValid; } + } + + public bool showBounds { get; set; } + + bool m_FoldoutInput = true; + bool m_FoldoutEncoder = true; + bool m_FoldoutTime = true; + bool m_FoldoutBounds = true; + bool m_FoldoutOutput = true; + public override void OnInspectorGUI() + { + if (target == null) + return; + + m_LayoutHelper.indentLevel = 0; + + EditorGUI.BeginChangeCheck(); + serializedObject.Update(); + + OnInputGroupGui(); + OnOutputGroupGui(); + OnEncodingGroupGui(); + OnTimeGroupGui(); + OnBoundsGroupGui(); + OnExtraGroupsGui(); + + var settingsObj = serializedObject.targetObject as FrameRecorderSettings; + m_LayoutHelper.AddBoolProperty("Verbose logging", serializedObject, () => settingsObj.m_Verbose); + + serializedObject.ApplyModifiedProperties(); + EditorGUI.EndChangeCheck(); + } + + protected virtual void OnInputGui() + { + m_LayoutHelper.AddIntProperty("Capture every n'th frame", serializedObject, () => (target as FrameRecorderSettings).m_CaptureEveryNthFrame); + } + + protected virtual void OnOutputGui() + { + } + + protected virtual void OnEncodingGui() + { + } + + protected virtual void OnTimeGui() + { + var settingsObj = serializedObject.targetObject as FrameRecorderSettings; + + m_LayoutHelper.AddEnumProperty("Frame rate mode", serializedObject, () => settingsObj.m_FrameRateMode); + m_LayoutHelper.indentLevel++; + var label = settingsObj.m_FrameRateMode == FrameRateMode.Fixed ? "Frame rate" : "Max frame rate"; + m_LayoutHelper.AddDoubleProperty(label, serializedObject, () => settingsObj.m_FrameRate); + m_LayoutHelper.indentLevel--; + } + + protected virtual void OnBounds() + { + var settingsObj = serializedObject.targetObject as FrameRecorderSettings; + + m_LayoutHelper.AddEnumProperty("Recording Duration", serializedObject, () => settingsObj.m_DurationMode); + + m_LayoutHelper.indentLevel++; + switch (settingsObj.m_DurationMode) + { + case DurationMode.Indefinite: + break; + case DurationMode.SingleFrame: + { + m_LayoutHelper.AddIntProperty("Frame", serializedObject, () => settingsObj.m_StartFrame, "Tooltip"); + settingsObj.m_EndFrame = settingsObj.m_StartFrame; + break; + } + case DurationMode.FrameInterval: + { + EditorGUILayout.BeginHorizontal(); + m_LayoutHelper.AddPropertyLabel("Frames"); + m_LayoutHelper.AddIntProperty(serializedObject, () => settingsObj.m_StartFrame); + m_LayoutHelper.AddIntProperty(serializedObject, () => settingsObj.m_EndFrame); + EditorGUILayout.EndHorizontal(); + break; + } + case DurationMode.TimeInterval: + { + EditorGUILayout.BeginHorizontal(); + m_LayoutHelper.AddPropertyLabel("Time"); + m_LayoutHelper.AddFloatProperty(serializedObject, () => settingsObj.m_StartTime); + m_LayoutHelper.AddFloatProperty(serializedObject, () => settingsObj.m_EndTime); + EditorGUILayout.EndHorizontal(); + break; + } + } + m_LayoutHelper.indentLevel--; + } + + + protected virtual void OnInputGroupGui() + { + m_FoldoutInput = EditorGUILayout.Foldout(m_FoldoutInput, "Input"); + if (m_FoldoutInput) + { + m_LayoutHelper.indentLevel++; + OnInputGui(); + m_LayoutHelper.indentLevel--; + } + } + + protected virtual void OnOutputGroupGui() + { + m_FoldoutOutput = EditorGUILayout.Foldout(m_FoldoutOutput, "Output"); + if (m_FoldoutOutput) + { + m_LayoutHelper.indentLevel++; + OnOutputGui(); + m_LayoutHelper.indentLevel--; + } + } + + protected virtual void OnEncodingGroupGui() + { + m_FoldoutEncoder = EditorGUILayout.Foldout(m_FoldoutEncoder, "Encoding"); + if (m_FoldoutEncoder) + { + m_LayoutHelper.indentLevel++; + OnEncodingGui(); + m_LayoutHelper.indentLevel--; + } + } + + protected virtual void OnTimeGroupGui() + { + m_FoldoutTime = EditorGUILayout.Foldout(m_FoldoutTime, "Time"); + if (m_FoldoutTime) + { + m_LayoutHelper.indentLevel++; + OnTimeGui(); + m_LayoutHelper.indentLevel--; + } + } + + protected virtual void OnBoundsGroupGui() + { + if (showBounds) + { + m_FoldoutBounds = EditorGUILayout.Foldout(m_FoldoutBounds, "Bounds / Limits"); + if (m_FoldoutBounds) + { + m_LayoutHelper.indentLevel++; + OnBounds(); + m_LayoutHelper.indentLevel--; + } + } + } + + protected virtual void OnExtraGroupsGui() + { + + } + + private static void Init() + { + if (m_Editors != null) + return; + + m_Editors = new SortedDictionary(); + foreach (var editor in ClassHelpers.FilterByAttribute()) + { + var attrib = editor.Value[0]; + m_Editors.Add((attrib as RecorderEditorAttribute).recorderType.FullName, editor.Key); + } + } + + public static Type FindEditorForRecorder(Type recorder) + { + Init(); + return m_Editors.ContainsKey(recorder.FullName) ? m_Editors[recorder.FullName] : null; + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Editor/RecorderSettingsEditor.cs.meta b/source/Unity/FrameRecorder/Scripts/Editor/RecorderSettingsEditor.cs.meta new file mode 100644 index 0000000..e6c9309 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Editor/RecorderSettingsEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c2d3ad15b5919624bbfcb237e18fd2ec +timeCreated: 1491415343 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Editor/RecorderWindow.cs b/source/Unity/FrameRecorder/Scripts/Editor/RecorderWindow.cs new file mode 100644 index 0000000..74d433c --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Editor/RecorderWindow.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Assets.Unity.FrameRecorder.Scripts.Editor; +using UnityEngine.Recorder.FrameRecorder.Utilities; +using UnityEngine; +using UnityEngine.Recorder.FrameRecorder; + +namespace UnityEditor.Recorder.FrameRecorder +{ + public class RecorderWindow : EditorWindow, IRecorderSelectorTarget + { + [SerializeField] string m_RecorderTypeName; + [SerializeField] string m_RecorderCategory; + [SerializeField] RecorderSettingsEditor m_SettingsEditor; + bool m_PendingStartRecording; + RecorderSelector m_recorderSelector; + + Type recorderType + { + get { return Type.GetType(m_RecorderTypeName); } + set { m_RecorderTypeName = value == null ? string.Empty : value.AssemblyQualifiedName; } + } + + public static void ShowAndPreselectCategory( string category ) + { + var window = GetWindow(typeof(RecorderWindow), false, "Recorder") as RecorderWindow; + + if( RecordersInventory.recordersByCategory.ContainsKey(category) ) + window.m_RecorderCategory = category; + } + + public void OnEnable() + { + m_recorderSelector = new RecorderSelector(this); + } + + public void OnGUI() + { + if (m_PendingStartRecording && EditorApplication.isPlaying) + DelayedStartRecording(); + + var size = new Vector2(300, 400); + + using (new EditorGUI.DisabledScope(EditorApplication.isPlaying)) + m_recorderSelector.OnGui(); + + if (m_SettingsEditor != null) + { + m_SettingsEditor.showBounds = true; + using (new EditorGUI.DisabledScope(EditorApplication.isPlaying)) + { + EditorGUILayout.Separator(); + + var editorMinSize = m_SettingsEditor.minSize; + if (editorMinSize.x > minSize.x) size.x = editorMinSize.x; + if (editorMinSize.y > minSize.y) size.y = editorMinSize.y; + + m_SettingsEditor.OnInspectorGUI(); + + EditorGUILayout.Separator(); + } + RecordButton(); + } + + minSize = size; + } + + public void OnDestroy() + { + StopRecording(); + UnityHelpers.Destroy(m_SettingsEditor); + } + + void RecordButton() + { + var settings = (FrameRecorderSettings)m_SettingsEditor.target; + var recorderGO = FrameRecorderGOControler.FindRecorder(settings); + + if (recorderGO == null) + { + using (new EditorGUI.DisabledScope(!m_SettingsEditor.isValid)) + { + if (GUILayout.Button("Start Recording")) + StartRecording(); + } + } + else + { + if (GUILayout.Button("Stop Recording")) + StopRecording(); + } + } + + void StartRecording() + { + if (!EditorApplication.isPlaying || EditorApplication.isPlaying) + { + m_PendingStartRecording = true; + EditorApplication.isPlaying = true; + return; + } + else + StartRecording(false); + } + + void DelayedStartRecording() + { + m_PendingStartRecording = false; + StartRecording(true); + } + + void StartRecording(bool autoExitPlayMode) + { + var settings = (FrameRecorderSettings)m_SettingsEditor.target; + var go = FrameRecorderGOControler.HookupRecorder(settings); + var session = new RecordingSession() + { + m_Recorder = RecordersInventory.InstantiateRecorder(recorderType, settings), + m_RecorderGO = go, + m_RecordingStartTS = Time.time / Time.timeScale, + m_FrameIndex = 0 + }; + + var component = go.AddComponent(); + component.session = session; + component.autoExitPlayMode = autoExitPlayMode; + + session.BeginRecording(); + } + + void StopRecording() + { + if (m_SettingsEditor != null) + { + var settings = (FrameRecorderSettings)m_SettingsEditor.target; + if (settings != null) + { + var recorderGO = FrameRecorderGOControler.FindRecorder(settings); + if (recorderGO != null) + { + UnityHelpers.Destroy(recorderGO); + } + } + } + } + + public void SetRecorder(Type newRecorderType) + { + if (newRecorderType == null || (m_SettingsEditor != null && recorderType == newRecorderType)) + return; + + recorderType = newRecorderType; + + var editorType = RecorderSettingsEditor.FindEditorForRecorder(recorderType); + if (editorType != null) + { + if (m_SettingsEditor != null) + { + UnityHelpers.Destroy(m_SettingsEditor.target); + UnityHelpers.Destroy(m_SettingsEditor); + m_SettingsEditor = null; + } + + var settings = RecordersInventory.CreateRecorderSettings(recorderType, "N/A", "RecorderWindow"); + m_SettingsEditor = UnityEditor.Editor.CreateEditor(settings, editorType) as RecorderSettingsEditor; + m_SettingsEditor.hideFlags = HideFlags.DontUnloadUnusedAsset; // <-- this means life time is manually managed by this class!! + } + else + Debug.LogError(string.Format("No editor class declared for recorder of type " + newRecorderType.FullName)); + } + + public string recorderCategory + { + get + { + return m_RecorderCategory; + } + + set + { + if (m_RecorderCategory != value) + { + m_RecorderCategory = value; + m_SettingsEditor = null; + recorderType = null; + } + } + } + + public string selectedRecorder + { + get { return m_RecorderTypeName; } + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Editor/RecorderWindow.cs.meta b/source/Unity/FrameRecorder/Scripts/Editor/RecorderWindow.cs.meta new file mode 100644 index 0000000..94e7993 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Editor/RecorderWindow.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d99962793b726bf4881e4b1719950d8f +timeCreated: 1491415344 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Editor/Utilities.meta b/source/Unity/FrameRecorder/Scripts/Editor/Utilities.meta new file mode 100644 index 0000000..bfe4d8c --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Editor/Utilities.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 2d548afd490f5254c9ca1d8b802ddff3 +folderAsset: yes +timeCreated: 1491415339 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Editor/Utilities/LayoutHelper.cs b/source/Unity/FrameRecorder/Scripts/Editor/Utilities/LayoutHelper.cs new file mode 100644 index 0000000..dd6b4df --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Editor/Utilities/LayoutHelper.cs @@ -0,0 +1,259 @@ +using System; +using System.Linq.Expressions; +using UnityEngine; + +namespace UnityEditor.Recorder.FrameRecorder.Utilities +{ + class LayoutHelper + { + public delegate T OnValueChangedDelegate(T oldValue, T newValue); + + private string[] m_Indents = new[] { "", " ", " ", " ", " "}; + GUILayoutOption m_LableLayoutOption; + + public LayoutHelper(GUILayoutOption lableLayoutOption) + { + m_LableLayoutOption = lableLayoutOption; + } + + int m_IndentLevel; + + + public int indentLevel + { + get { return m_IndentLevel; } + set + { + m_IndentLevel = value; + if (m_IndentLevel < 0) m_IndentLevel = 0; + if (m_IndentLevel >= m_Indents.Length) m_IndentLevel = m_Indents.Length - 1; + } + } + + + public string indentation + { + get { return m_Indents[m_IndentLevel]; } + } + + public void AddPropertyLabel(string text) + { + GUILayout.Label(m_Indents[m_IndentLevel] + text, m_LableLayoutOption); + } + + public void AddPropertyLabel(string label, string tooltip) + { + GUILayout.Label(new GUIContent(m_Indents[m_IndentLevel] + label, tooltip), m_LableLayoutOption); + } + + public bool AddEnumProperty(string label, SerializedObject serObj, Expression> propertySelector, string tooltip = "") + { + var property = serObj.FindProperty(propertySelector); + + var ourRect = EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginProperty(ourRect, GUIContent.none, property); + + AddPropertyLabel(label, tooltip); + + var selectionFromInspector = property.intValue; + var actualSelected = EditorGUILayout.Popup(selectionFromInspector, property.enumDisplayNames); + var changed = actualSelected != property.intValue; + property.intValue = actualSelected; + + EditorGUI.EndProperty(); + EditorGUILayout.EndHorizontal(); + + return changed; + } + + public bool AddFlagEnumProperty(string label, SerializedObject serObj, Expression> propertySelector, string tooltip = "") + { + var property = serObj.FindProperty(propertySelector); + + var ourRect = EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginProperty(ourRect, GUIContent.none, property); + + AddPropertyLabel(label, tooltip); + + var selectionFromInspector = (int)Math.Log(property.intValue, 2); + var actualSelected = 1 << EditorGUILayout.Popup(selectionFromInspector, property.enumDisplayNames); + var changed = actualSelected != property.intValue; + property.intValue = actualSelected; + + EditorGUI.EndProperty(); + EditorGUILayout.EndHorizontal(); + + return changed; + } + + public bool AddEnumProperty(string label, SerializedObject serObj, Expression> propertySelector, string[] values, string tooltip = "") + { + var property = serObj.FindProperty(propertySelector); + + var ourRect = EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginProperty(ourRect, GUIContent.none, property); + + AddPropertyLabel(label, tooltip); + + var selectionFromInspector = property.intValue; + var actualSelected = EditorGUILayout.Popup(selectionFromInspector, values); + var changed = actualSelected != property.intValue; + property.intValue = actualSelected; + + EditorGUI.EndProperty(); + EditorGUILayout.EndHorizontal(); + + return changed; + } + + public bool AddStringProperty(string label, SerializedObject serObj, Expression> propertySelector, string tooltip = "") + { + var property = serObj.FindProperty(propertySelector); + + var ourRect = EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginProperty(ourRect, GUIContent.none, property); + + AddPropertyLabel(label); + + var orgValue = property.stringValue; + var newValue = EditorGUILayout.TextField(new GUIContent("", tooltip), orgValue); + var changed = orgValue != newValue; + property.stringValue = newValue; + + EditorGUI.EndProperty(); + EditorGUILayout.EndHorizontal(); + + return changed; + } + + public bool AddIntProperty(string label, SerializedProperty property, string tooltip = "") + { + var ourRect = EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginProperty(ourRect, GUIContent.none, property); + + AddPropertyLabel(label); + + var orgValue = property.intValue; + var newValue = EditorGUILayout.IntField(new GUIContent("", tooltip), orgValue); + var changed = orgValue != newValue; + property.intValue = newValue; + + EditorGUI.EndProperty(); + EditorGUILayout.EndHorizontal(); + + return changed; + } + + public bool AddIntProperty(string label, SerializedObject serObj, Expression> propertySelector, string tooltip = "") + { + return AddIntProperty(label, serObj.FindProperty(propertySelector), tooltip); + } + + public bool AddIntProperty(SerializedProperty property) + { + var ourRect = EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginProperty(ourRect, GUIContent.none, property); + + var orgValue = property.intValue; + var newValue = EditorGUILayout.IntField(orgValue); + var changed = orgValue != newValue; + property.intValue = newValue; + + EditorGUI.EndProperty(); + EditorGUILayout.EndHorizontal(); + + return changed; + } + + public bool AddFloatProperty(SerializedObject serObj, Expression> propertySelector) + { + return AddFloatProperty(serObj.FindProperty(propertySelector)); + } + + public bool AddFloatProperty(SerializedProperty property) + { + var ourRect = EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginProperty(ourRect, GUIContent.none, property); + + var orgValue = property.floatValue; + var newValue = EditorGUILayout.FloatField(orgValue); + var changed = orgValue != newValue; + property.floatValue = newValue; + + EditorGUI.EndProperty(); + EditorGUILayout.EndHorizontal(); + + return changed; + } + + public bool AddIntProperty(SerializedObject serObj, Expression> propertySelector) + { + return AddIntProperty(serObj.FindProperty(propertySelector)); + } + + public bool AddIntProperty(SerializedProperty parentSerProp, Expression> propertySelector) + { + return AddIntProperty(parentSerProp.FindPropertyRelative(propertySelector)); + } + + public bool AddFloatProperty(string label, SerializedObject serObj, Expression> propertySelector, string tooltip = "") + { + var property = serObj.FindProperty(propertySelector); + + var ourRect = EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginProperty(ourRect, GUIContent.none, property); + + AddPropertyLabel(label); + + var orgValue = property.floatValue; + var newValue = EditorGUILayout.FloatField(new GUIContent("", tooltip), orgValue); + var changed = orgValue != newValue; + property.floatValue = newValue; + + EditorGUI.EndProperty(); + EditorGUILayout.EndHorizontal(); + + return changed; + } + + public bool AddDoubleProperty(string label, SerializedObject serObj, Expression> propertySelector, string tooltip = "") + { + var property = serObj.FindProperty(propertySelector); + + var ourRect = EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginProperty(ourRect, GUIContent.none, property); + + AddPropertyLabel(label); + + var orgValue = property.doubleValue; + var newValue = EditorGUILayout.DoubleField(new GUIContent("", tooltip), orgValue); + var changed = orgValue != newValue; + property.doubleValue = newValue; + + EditorGUI.EndProperty(); + EditorGUILayout.EndHorizontal(); + + return changed; + } + + public bool AddBoolProperty(string label, SerializedObject serObj, Expression> propertySelector, string tooltip = "") + { + var property = serObj.FindProperty(propertySelector); + + var ourRect = EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginProperty(ourRect, GUIContent.none, property); + + AddPropertyLabel(label); + + var orgValue = property.boolValue; + var newValue = EditorGUILayout.Toggle(new GUIContent("", tooltip), orgValue); + var changed = orgValue != newValue; + property.boolValue = newValue; + + EditorGUI.EndProperty(); + EditorGUILayout.EndHorizontal(); + + return changed; + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Editor/Utilities/LayoutHelper.cs.meta b/source/Unity/FrameRecorder/Scripts/Editor/Utilities/LayoutHelper.cs.meta new file mode 100644 index 0000000..b364e4d --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Editor/Utilities/LayoutHelper.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b25434794c2520040b7ec0687028cb7e +timeCreated: 1491415343 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Editor/Utilities/SerializableObjHelper.cs b/source/Unity/FrameRecorder/Scripts/Editor/Utilities/SerializableObjHelper.cs new file mode 100644 index 0000000..cc10492 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Editor/Utilities/SerializableObjHelper.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq.Expressions; + +namespace UnityEditor.Recorder.FrameRecorder.Utilities +{ + public static class SerializableObjHelper + { + public static SerializedProperty FindProperty(this SerializedObject obj, Expression> exp) + { + var body = exp.Body as MemberExpression; + if (body == null) + { + var ubody = (UnaryExpression)exp.Body; + body = ubody.Operand as MemberExpression; + } + + var name = body.Member.Name; + + return obj.FindProperty(name); + } + + public static SerializedProperty FindPropertyRelative(this SerializedProperty obj, Expression> exp) + { + var body = exp.Body as MemberExpression; + if (body == null) + { + var ubody = (UnaryExpression)exp.Body; + body = ubody.Operand as MemberExpression; + } + + var name = body.Member.Name; + + return obj.FindPropertyRelative(name); + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Editor/Utilities/SerializableObjHelper.cs.meta b/source/Unity/FrameRecorder/Scripts/Editor/Utilities/SerializableObjHelper.cs.meta new file mode 100644 index 0000000..0e4c492 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Editor/Utilities/SerializableObjHelper.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b91f080b208afff4d94678e466eeecc4 +timeCreated: 1491415343 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine.meta b/source/Unity/FrameRecorder/Scripts/Engine.meta new file mode 100644 index 0000000..786a3f2 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 465980b32e42e4a439d49f4d4bf0ef31 +folderAsset: yes +timeCreated: 1491417212 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/DataSource.meta b/source/Unity/FrameRecorder/Scripts/Engine/DataSource.meta new file mode 100644 index 0000000..23f9713 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/DataSource.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 6cbec5a6a20c2074e9b44e94a96092d6 +folderAsset: yes +timeCreated: 1491415339 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/DataSource/CameraAsRenderTexture.cs b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/CameraAsRenderTexture.cs new file mode 100644 index 0000000..26171b0 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/CameraAsRenderTexture.cs @@ -0,0 +1,158 @@ +using System; +using UnityEditor; +using UnityEngine.Recorder.FrameRecorder.Utilities; +using UnityEngine.Rendering; + +namespace UnityEngine.Recorder.FrameRecorder.DataSource +{ + public class CameraAsRenderTexture : RenderTextureSource + { + Shader m_shCopy; + Material m_mat_copy; + Mesh m_quad; + CommandBuffer m_cb; + Camera m_Camera; + bool m_cameraChanged; + + protected int m_Width; + protected int m_Height; + protected EImageSizeMode m_SizeMode; + + public Camera TargetCamera + { + get { return m_Camera; } + + set + { + if (m_Camera != value) + { + ReleaseCamera(); + m_Camera = value; + m_cameraChanged = true; + } + } + } + + public Shader CopyShader + { + get + { + if (m_shCopy == null) + { + // TODO: make this Non Editor compatible + // Figure out where this shader should reside. + m_shCopy = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath("55d39f733f4029f4cbc92b5c2279e15b")); + } + return m_shCopy; + } + + set { m_shCopy = value; } + } + + public CameraAsRenderTexture(EImageSizeMode sizeMode, int width, int height) + { + m_SizeMode = sizeMode; + m_Width = width; + m_Height = height; + m_quad = CreateFullscreenQuad(); + } + + public void PrepareNewFrame() + { + // initialize scratch buffer + var newTexture = PrepFrameRenderTexture(); + + // initialize command buffer + if (m_Camera != null && m_cameraChanged || newTexture) + { + if (m_cb != null) + { + m_Camera.RemoveCommandBuffer(CameraEvent.AfterEverything, m_cb); + m_cb.Release(); + } + + // TODO: This should not be here!!! + m_mat_copy = new Material(CopyShader); + if (m_Camera.targetTexture != null) + m_mat_copy.EnableKeyword("OFFSCREEN"); + + var tid = Shader.PropertyToID("_TmpFrameBuffer"); + m_cb = new CommandBuffer { name = "Frame Recorder: copy frame buffer" }; + m_cb.GetTemporaryRT(tid, -1, -1, 0, FilterMode.Bilinear); + m_cb.Blit(BuiltinRenderTextureType.CurrentActive, tid); + m_cb.SetRenderTarget(buffer); + m_cb.DrawMesh(m_quad, Matrix4x4.identity, m_mat_copy, 0, 0); + m_cb.ReleaseTemporaryRT(tid); + m_Camera.AddCommandBuffer(CameraEvent.AfterEverything, m_cb); + + m_cameraChanged = false; + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + ReleaseCamera(); + } + + base.Dispose(disposing); + } + + protected virtual void ReleaseCamera() + { + if (m_cb != null) + { + if (m_Camera != null) + m_Camera.RemoveCommandBuffer(CameraEvent.AfterEverything, m_cb); + + m_cb.Release(); + m_cb = null; + } + + if (m_mat_copy != null) + UnityHelpers.Destroy(m_mat_copy); + } + + bool PrepFrameRenderTexture() + { + var height = (int)(m_Width / m_Camera.aspect); + if (buffer != null) + { + if (buffer.IsCreated() && buffer.width == m_Width && buffer.height == height) + { + return false; + } + + ReleaseBuffer(); + } + + buffer = new RenderTexture(m_Width, height, 0, RenderTextureFormat.ARGB32) + { + wrapMode = TextureWrapMode.Repeat + }; + buffer.Create(); + + return true; + } + + public static Mesh CreateFullscreenQuad() + { + var vertices = new Vector3[4] + { + new Vector3(1.0f, 1.0f, 0.0f), + new Vector3(-1.0f, 1.0f, 0.0f), + new Vector3(-1.0f, -1.0f, 0.0f), + new Vector3(1.0f, -1.0f, 0.0f), + }; + var indices = new[] { 0, 1, 2, 2, 3, 0 }; + + var r = new Mesh + { + vertices = vertices, + triangles = indices + }; + return r; + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/DataSource/CameraAsRenderTexture.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/CameraAsRenderTexture.cs.meta new file mode 100644 index 0000000..57a5acb --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/CameraAsRenderTexture.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 852b7afeb4df2254e81cc4c6d11ca313 +timeCreated: 1491415343 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/DataSource/DisplayAsRenderTexture.cs b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/DisplayAsRenderTexture.cs new file mode 100644 index 0000000..7615391 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/DisplayAsRenderTexture.cs @@ -0,0 +1,36 @@ +using System; + +namespace UnityEngine.Recorder.FrameRecorder.DataSource +{ + public class DisplayAsRenderTexture : CameraAsRenderTexture + { + public int m_DisplayID; + + public void PrepareNewFrame(RecordingSession session) + { + if (TargetCamera != null && TargetCamera.targetDisplay != m_DisplayID) + TargetCamera = null; + + if (TargetCamera == null) + { + var displayGO = new GameObject(); + displayGO.name = "CameraHostGO-" + displayGO.GetInstanceID(); + displayGO.transform.parent = session.m_RecorderGO.transform; + var camera = displayGO.AddComponent(); + camera.clearFlags = CameraClearFlags.Nothing; + camera.cullingMask = 0; + camera.renderingPath = RenderingPath.DeferredShading; + camera.targetDisplay = m_DisplayID; + camera.rect = new Rect(0, 0, 1, 1); + camera.depth = float.MaxValue; + + TargetCamera = camera; + } + + base.PrepareNewFrame(); + } + + public DisplayAsRenderTexture(int width) + : base( EImageSizeMode.Width, width, 0) {} + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/DataSource/DisplayAsRenderTexture.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/DisplayAsRenderTexture.cs.meta new file mode 100644 index 0000000..0486638 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/DisplayAsRenderTexture.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9eef97e3b03eb7d489aba82d5d15fb99 +timeCreated: 1491415343 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/DataSource/GameViewAsRenderTexture.cs b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/GameViewAsRenderTexture.cs new file mode 100644 index 0000000..99440d6 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/GameViewAsRenderTexture.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine.Recorder.FrameRecorder.Utilities; + +namespace UnityEngine.Recorder.FrameRecorder.DataSource +{ + public class GameViewAsRenderTexture : CameraAsRenderTexture + { + GameViewWindow m_GameView; + + public void PrepareNewFrame(RecordingSession session) + { + if (TargetCamera == null) + { + var displayGO = new GameObject(); + displayGO.name = "CameraHostGO-" + displayGO.GetInstanceID(); + displayGO.transform.parent = session.m_RecorderGO.transform; + var camera = displayGO.AddComponent(); + camera.clearFlags = CameraClearFlags.Nothing; + camera.cullingMask = 0; + camera.renderingPath = RenderingPath.DeferredShading; + camera.targetDisplay = 0; + camera.rect = new Rect(0, 0, 1, 1); + camera.depth = float.MaxValue; + + TargetCamera = camera; + } + + base.PrepareNewFrame(); + } + + public GameViewAsRenderTexture( EImageSizeMode sizeMode, int width, int height) + : base(sizeMode, width, height) + { + + } + + public void BeginRecording() + { + m_GameView = new GameViewWindow(); + m_GameView.FullScreenGameWindow( m_SizeMode, m_Width, m_Height ); + m_Width = (int)m_GameView.size.x; + m_Height = (int)m_GameView.size.y; + } + + public void EndRecording() + { + if (m_GameView != null) + m_GameView.RestoreGameWindow(); + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/DataSource/GameViewAsRenderTexture.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/GameViewAsRenderTexture.cs.meta new file mode 100644 index 0000000..dcb25c0 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/GameViewAsRenderTexture.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f1701eab2c131b34ebbfd0f24ee466b2 +timeCreated: 1493928434 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/DataSource/RecorderSource.cs b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/RecorderSource.cs new file mode 100644 index 0000000..4da1a44 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/RecorderSource.cs @@ -0,0 +1,24 @@ +using System; + +namespace UnityEngine.Recorder.FrameRecorder.DataSource +{ + public class RecorderSource : IDisposable + { + public int SourceID { get; set; } + + ~RecorderSource() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + GC.SuppressFinalize(this); + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/DataSource/RecorderSource.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/RecorderSource.cs.meta new file mode 100644 index 0000000..9cae328 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/RecorderSource.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9f956c0b0f15ce74aac4600909d4e55f +timeCreated: 1491415343 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/DataSource/RenderTextureSource.cs b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/RenderTextureSource.cs new file mode 100644 index 0000000..d6e153c --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/RenderTextureSource.cs @@ -0,0 +1,33 @@ +using System; + +namespace UnityEngine.Recorder.FrameRecorder.DataSource +{ + public abstract class RenderTextureSource : RecorderSource + { + public RenderTexture buffer { get; set; } + + public RenderTexture UnlinkBuffer() + { + var temp = buffer; + buffer = null; + return temp; + } + + public void ReleaseBuffer() + { + if (buffer != null) + { + buffer.Release(); + buffer = null; + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + ReleaseBuffer(); + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/DataSource/RenderTextureSource.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/RenderTextureSource.cs.meta new file mode 100644 index 0000000..f7bbb0d --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/DataSource/RenderTextureSource.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 1dd8c8968d3848743a824ed5191b2805 +timeCreated: 1491415343 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderAttribute.cs b/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderAttribute.cs new file mode 100644 index 0000000..7f2c07e --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace UnityEngine.Recorder.FrameRecorder +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class FrameRecorderClassAttribute : Attribute + { + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderAttribute.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderAttribute.cs.meta new file mode 100644 index 0000000..a8b45e1 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderAttribute.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ca62b38426adb1d47ad6cd6c94001b6a +timeCreated: 1491415344 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderGOControler.cs b/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderGOControler.cs new file mode 100644 index 0000000..cd8e9f9 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderGOControler.cs @@ -0,0 +1,63 @@ +using System; + +namespace UnityEngine.Recorder.FrameRecorder +{ + public class FrameRecorderGOControler + { + const string k_HostGoName = "UnityEngine.Recorder.FrameRecorder"; + + public static GameObject GetGameObject() + { + return GameObject.Find(k_HostGoName) ?? new GameObject(k_HostGoName); + } + + public static GameObject GetSettingsRoot() + { + var root = GetGameObject(); + var settingsTr = root.transform.Find("Settings"); + GameObject settingsGO; + if (settingsTr == null) + { + settingsGO = new GameObject("Settings"); + settingsGO.transform.parent = root.transform; + } + else + settingsGO = settingsTr.gameObject; + + return settingsGO; + } + + public static GameObject GetRecordersRoot() + { + var root = GetGameObject(); + var settingsTr = root.transform.Find("Recording"); + GameObject settingsGO; + if (settingsTr == null) + { + settingsGO = new GameObject("Recording"); + settingsGO.transform.parent = root.transform; + } + else + settingsGO = settingsTr.gameObject; + + return settingsGO; + } + + public static GameObject HookupRecorder(FrameRecorderSettings settings) + { + var ctrl = GetRecordersRoot(); + + var recorderGO = new GameObject(settings.m_UniqueID); + recorderGO.transform.parent = ctrl.transform; + + return recorderGO; + } + + public static GameObject FindRecorder(FrameRecorderSettings settings) + { + var ctrl = GetRecordersRoot(); + var go = ctrl.transform.Find(settings.m_UniqueID); + return go == null ? null : go.gameObject; + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderGOControler.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderGOControler.cs.meta new file mode 100644 index 0000000..574b303 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderGOControler.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e378e9c2cad5a7e48a6931f63edd1421 +timeCreated: 1491415344 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderSettings.cs b/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderSettings.cs new file mode 100644 index 0000000..c09b7aa --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderSettings.cs @@ -0,0 +1,86 @@ +using System; +using UnityEditor; +using UnityEngine.Recorder.FrameRecorder.Utilities; + +namespace UnityEngine.Recorder.FrameRecorder +{ + public enum EImageSourceType + { + GameDisplay, + SceneView, + MainCamera, + TaggedCamera, + RenderTexture + } + + public enum FrameRateMode + { + Variable, + Fixed, + } + + public enum DurationMode + { + Indefinite, + SingleFrame, + FrameInterval, + TimeInterval + } + + public class FrameRecorderSettings : MonoBehaviour + { + public string m_UniqueID; + public string m_OwnerAssetID; + public int m_CaptureEveryNthFrame = 1; + public FrameRateMode m_FrameRateMode = FrameRateMode.Fixed; + public double m_FrameRate = 24.0; + public int m_StartFrame; + public int m_EndFrame = 1000; + public float m_StartTime = 0.0f; + public float m_EndTime = 1.0f; + public DurationMode m_DurationMode; + public bool m_Verbose = false; + + public virtual bool isValid + { + get { return m_FrameRate > 0; } + } + + public virtual void OnEnable() + { + GarbageCollect(); + + if (string.IsNullOrEmpty(m_UniqueID)) + m_UniqueID = string.Format("{0}-{1}", GetType().Name, Guid.NewGuid()); + } + + public bool fixedDuration + { + get { return m_DurationMode != DurationMode.Indefinite; } + } + + void GarbageCollect() + { +#if UNITY_EDITOR + if (string.IsNullOrEmpty(m_OwnerAssetID)) + return; + try + { + var guid = new Guid(m_OwnerAssetID); + if (guid == Guid.Empty) + return; + } + catch + { + return; + } + + // Check if it's associated asset, if there was one, still exists. If it does not: cleanup / delete this setting + if (string.IsNullOrEmpty(AssetDatabase.GUIDToAssetPath(m_OwnerAssetID))) + { + UnityHelpers.Destroy(gameObject); + } +#endif + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderSettings.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderSettings.cs.meta new file mode 100644 index 0000000..58679b7 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/FrameRecorderSettings.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 180842267b7175e4c9dedaf1f548db0f +timeCreated: 1491415343 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/ImageRecorderSettings.cs b/source/Unity/FrameRecorder/Scripts/Engine/ImageRecorderSettings.cs new file mode 100644 index 0000000..1aaa156 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/ImageRecorderSettings.cs @@ -0,0 +1,29 @@ +using System; + +namespace UnityEngine.Recorder.FrameRecorder +{ + + public enum EImageSizeMode + { + Dynamic, + FullScreen, + Width, + Custom + } + + public class ImageRecorderSettings : FrameRecorderSettings + { + public EImageSizeMode m_SizeMode = EImageSizeMode.Dynamic; + public int m_Width = 1024; + public int m_Height = 768; + + public EImageSourceType m_InputType = EImageSourceType.GameDisplay; + public int m_ScreenID = 0; + public string m_CameraTag; + + public override bool isValid + { + get { return base.isValid && m_Width > 0 && m_Height > 0; } + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/ImageRecorderSettings.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/ImageRecorderSettings.cs.meta new file mode 100644 index 0000000..d494e3c --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/ImageRecorderSettings.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f3941bff05f4e88479d3c4741b63bbf1 +timeCreated: 1491415344 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Recorder.cs b/source/Unity/FrameRecorder/Scripts/Engine/Recorder.cs new file mode 100644 index 0000000..359842c --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Recorder.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using UnityEngine.Recorder.FrameRecorder.DataSource; + +namespace UnityEngine.Recorder.FrameRecorder +{ + public enum ERecordingSessionStage + { + BeginRecording, + NewFrameStarting, + NewFrameReady, + FrameDone, + EndRecording, + } + + public abstract class Recorder : ScriptableObject + { + double m_OriginalCaptureFrameRate; + + public int recordedFramesCount { get; set; } + + /// + /// Concept: container class for Recorder Source objects that assumes nothing on the contained source object + /// + /// Motivation: let's recorders use any type of object as a source of data to record. + /// + protected struct BoxedSource + { + public System.Object m_Source; + public SortedDictionary> m_StageHandlers; + + public BoxedSource(System.Object source) + { + m_Source = source; + m_StageHandlers = new SortedDictionary>(); + } + + public void SignalNewStage(ERecordingSessionStage stage, RecordingSession session) + { + if (m_StageHandlers.ContainsKey(stage)) + m_StageHandlers[stage](session); + } + } + protected List m_BoxedSources; + + public virtual void Reset() + { + recordedFramesCount = 0; + recording = false; + } + + protected virtual void OnDestroy() + { + } + + public abstract FrameRecorderSettings settings { get; set; } + + // returns true if recording is starting. false if failed to begin recording or was already recording + public virtual bool BeginRecording(RecordingSession session) + { + if (recording) + return false; + + if (settings.m_Verbose) + Debug.Log(string.Format("Recorder {0} starting to record", GetType().Name)); + + m_OriginalCaptureFrameRate = Time.captureFramerate; + var fixedRate = settings.m_FrameRateMode == FrameRateMode.Fixed ? (int)settings.m_FrameRate : m_OriginalCaptureFrameRate; + if (fixedRate != m_OriginalCaptureFrameRate) + { + if (Time.captureFramerate > 0) + Debug.LogWarning(string.Format("Frame Recorder {0} is set to record at a fixed rate and another component has already set a conflicting value for [Time.captureFramerate], new value being applied : {1}!", GetType().Name, fixedRate)); + Time.captureFramerate = (int)fixedRate; + + if (settings.m_Verbose) + Debug.Log("Frame recorder set fixed frame rate to " + fixedRate); + } + + return true; + } + + public virtual void EndRecording(RecordingSession ctx) + { + if (!recording) + return; + recording = false; + + if (Time.captureFramerate != m_OriginalCaptureFrameRate) + { + Time.captureFramerate = (int)m_OriginalCaptureFrameRate; + if (settings.m_Verbose) + Debug.Log("Frame recorder resetting fixed frame rate to original value of " + m_OriginalCaptureFrameRate); + } + + foreach (var source in m_BoxedSources) + { + if (source.m_Source is IDisposable) + (source.m_Source as IDisposable).Dispose(); + } + + Debug.Log(string.Format("{0} recording stopped, total frame count: {1}", GetType().Name, recordedFramesCount)); + } + + public abstract void RecordFrame(RecordingSession ctx); + public virtual void PrepareNewFrame(RecordingSession ctx) + { + } + + public virtual bool SkipFrame(RecordingSession ctx) + { + return !recording || (ctx.m_FrameIndex % settings.m_CaptureEveryNthFrame) != 0; + } + + public bool recording { get; protected set; } + + public void SignalSourcesOfStage(ERecordingSessionStage stage, RecordingSession session) + { + if (m_BoxedSources != null) + { + foreach (var boxedSource in m_BoxedSources) + boxedSource.SignalNewStage(stage, session); + } + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Recorder.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/Recorder.cs.meta new file mode 100644 index 0000000..4bf5c23 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Recorder.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 58fe471fc3009fb4d8cf266a5cc5a7a1 +timeCreated: 1491415343 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/RecorderComponent.cs b/source/Unity/FrameRecorder/Scripts/Engine/RecorderComponent.cs new file mode 100644 index 0000000..91ab7f8 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/RecorderComponent.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections; + +namespace UnityEngine.Recorder.FrameRecorder +{ + [ExecuteInEditMode] + public class RecorderComponent : MonoBehaviour + { + public bool autoExitPlayMode { get; set; } + public RecordingSession session { get; set; } + + public void Update() + { + if (session != null && session.recording) + { + session.m_CurrentFrameStartTS = (Time.time / Time.timeScale) - session.m_RecordingStartTS; + session.m_FrameIndex++; + + session.PrepareNewFrame(); + } + } + + IEnumerator RecordFrame() + { + yield return new WaitForEndOfFrame(); + if (session != null && session.recording) + { + session.RecordFrame(); + + switch (session.m_Recorder.settings.m_DurationMode) + { + case DurationMode.Indefinite: + break; + case DurationMode.SingleFrame: + enabled = false; + break; + case DurationMode.FrameInterval: + if (session.m_FrameIndex >= session.settings.m_EndFrame) + enabled = false; + break; + case DurationMode.TimeInterval: + if (session.m_CurrentFrameStartTS >= session.settings.m_EndTime) + enabled = false; + break; + } + } + } + + public void LateUpdate() + { + if (session != null && session.recording) + { + if (session.m_FrameIndex >= session.settings.m_StartFrame) + { + StartCoroutine(RecordFrame()); + } + } + } + + public void OnDisable() + { + if (session != null) + { + session.Dispose(); + session = null; + +#if UNITY_EDITOR + if (autoExitPlayMode) + UnityEditor.EditorApplication.isPlaying = false; +#endif + } + } + + public void OnDestroy() + { + if (session != null) + session.Dispose(); + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/RecorderComponent.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/RecorderComponent.cs.meta new file mode 100644 index 0000000..db76cf9 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/RecorderComponent.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 6122dab7ff1764c468a03aa29f49ea5b +timeCreated: 1491415343 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/RecordersInventory.cs b/source/Unity/FrameRecorder/Scripts/Engine/RecordersInventory.cs new file mode 100644 index 0000000..b12797b --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/RecordersInventory.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Remoting.Messaging; +using UnityEngine.Recorder.FrameRecorder.Utilities; + +namespace UnityEngine.Recorder.FrameRecorder +{ + public class RecorderInfo + { + public Type recorder; + public Type settings; + public string category; + public string displayName; + + public static RecorderInfo Instantiate(string category, string displayName) + where TRecoder : class + where TSettings : class + { + return new RecorderInfo() + { + recorder = typeof(TRecoder), + settings = typeof(TSettings), + category = category, + displayName = displayName + }; + } + } + + // to be internal once inside unity code base + public static class RecordersInventory + { + internal static SortedDictionary recorders { get; private set; } + + static void Init() + { +#if UNITY_EDITOR + if (RecordersInventory.recorders != null) + return; + + RecordersInventory.recorders = new SortedDictionary(); + var recorders = ClassHelpers.FilterByAttribute(false); + foreach (var recorder in recorders) + AddRecorder(recorder.Key); +#endif + } + +#if UNITY_EDITOR + static SortedDictionary> m_RecordersByCategory; + + public static SortedDictionary> recordersByCategory + { + get + { + Init(); + return m_RecordersByCategory; + } + } + + static string[] m_AvailableCategories; + public static string[] availableCategories + { + get + { + if (m_AvailableCategories == null) + { + m_AvailableCategories = RecordersInventory.ListRecorders() + .GroupBy(x => x.category) + .Select(x => x.Key) + .OrderBy(x => x) + .ToArray(); + } + return m_AvailableCategories; + } + } +#endif + + static bool AddRecorder(Type recorderType) + { + RecorderInfo recInfo = null; + var method = recorderType.GetMethod("GetRecorderInfo", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + if (method != null) + { + try + { + recInfo = method.Invoke(null, null) as RecorderInfo; + + if (recInfo != null) + { + if (recorders == null) + recorders = new SortedDictionary(); + recorders.Add(recInfo.recorder.FullName, recInfo); + +#if UNITY_EDITOR + if (m_RecordersByCategory == null) + m_RecordersByCategory = new SortedDictionary>(); + + if (!m_RecordersByCategory.ContainsKey(recInfo.category)) + m_RecordersByCategory.Add(recInfo.category, new List()); + + m_RecordersByCategory[recInfo.category].Add(recInfo); +#endif + } + + return true; + } + catch (Exception ex) + { + Debug.LogException(ex); + } + } + else + { + Debug.LogError(String.Format("The recorder class '{0}' need to provide method: static RecorderInfo GetRecorderInfo(...)", recorderType.FullName)); + } + + return false; + } + + + public static RecorderInfo GetRecorder() where TRecorder : class + { + return GetRecorder(typeof(TRecorder)); + } + + static RecorderInfo GetRecorder(Type recorderType) + { + Init(); + if (recorders.ContainsKey(recorderType.FullName)) + return recorders[recorderType.FullName]; + +#if UNITY_EDITOR + return null; +#else + if (AddRecorder(recorderType)) + return recorders[recorderType.FullName]; + else + return null +#endif + } + + public static IEnumerable ListRecorders() + { + Init(); + + foreach (var recorderInfo in recorders) + { + yield return recorderInfo.Value; + } + } + + public static Recorder InstantiateRecorder(Type recorderType, FrameRecorderSettings settings) + { + Init(); + var factory = GetRecorder(recorderType); + if (factory != null) + { + var recorder = ScriptableObject.CreateInstance(recorderType) as Recorder; + recorder.Reset(); + recorder.settings = settings; + return recorder; + } + else + throw new ArgumentException("No factory was registered for " + recorderType.Name); + } + + public static FrameRecorderSettings CreateRecorderSettings(Type recorderType, string ownerAssetId, string uniqueId) + { + Init(); + var recorderinfo = GetRecorder(recorderType); + if (recorderinfo != null) + { + var rootRecordingGO = FrameRecorderGOControler.GetSettingsRoot(); + + var transform = rootRecordingGO.transform.Find(uniqueId); + if (transform == null) + return New(recorderinfo.settings, ownerAssetId, uniqueId); + + var settings = (FrameRecorderSettings)transform.GetComponent(recorderinfo.settings); + if (settings == null || settings.GetType() != recorderinfo.settings) + { + UnityHelpers.Destroy(transform.gameObject); + return New(recorderinfo.settings, ownerAssetId, uniqueId); + } + + return settings; + } + else + throw new ArgumentException("No factory was registered for " + recorderType.Name); + } + + public static void DeleteSettings(string uniqueId) + { + var bank = FrameRecorderGOControler.GetSettingsRoot(); + + GameObject settingsGO = null; + var t = bank.transform.Find(uniqueId); + if (t != null) + settingsGO = t.gameObject; + + if (settingsGO == null) + return; + + UnityHelpers.Destroy(settingsGO); + } + + static FrameRecorderSettings New(Type type, string ownerAssetId, string uniqueId) + { + if (!string.IsNullOrEmpty(uniqueId)) + DeleteSettings(uniqueId); + + var bank = FrameRecorderGOControler.GetSettingsRoot(); + + var settingsGO = new GameObject(); + settingsGO.transform.parent = bank.transform; + var settings = (FrameRecorderSettings)settingsGO.AddComponent(type); + settings.m_UniqueID = uniqueId; + settings.m_OwnerAssetID = ownerAssetId; + + settingsGO.name = settings.m_UniqueID; + return settings; + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/RecordersInventory.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/RecordersInventory.cs.meta new file mode 100644 index 0000000..d84eb1b --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/RecordersInventory.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e11d656cf6217cb41ba5443ff354aea7 +timeCreated: 1491415344 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/RecordingSession.cs b/source/Unity/FrameRecorder/Scripts/Engine/RecordingSession.cs new file mode 100644 index 0000000..fede1b7 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/RecordingSession.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using UnityEngine.Recorder.FrameRecorder.Utilities; + +namespace UnityEngine.Recorder.FrameRecorder +{ + public class RecordingSession : IDisposable + { + public Recorder m_Recorder; + public GameObject m_RecorderGO; + public List m_ObjsOfInterest; + public int m_FrameIndex; // count starts at 0. + public double m_CurrentFrameStartTS; + public double m_RecordingStartTS; + + public FrameRecorderSettings settings { get { return m_Recorder.settings; } } + public bool recording { get { return m_Recorder.recording; } } + + public bool BeginRecording() + { + if (!m_Recorder.BeginRecording(this)) + return false; + m_Recorder.SignalSourcesOfStage(ERecordingSessionStage.BeginRecording, this); + return true; + } + + public virtual void EndRecording() + { + m_Recorder.EndRecording(this); + m_Recorder.SignalSourcesOfStage(ERecordingSessionStage.EndRecording, this); + } + + public void RecordFrame() + { + m_Recorder.SignalSourcesOfStage(ERecordingSessionStage.NewFrameReady, this); + if (!m_Recorder.SkipFrame(this)) + { + m_Recorder.RecordFrame(this); + m_Recorder.recordedFramesCount++; + } + m_Recorder.SignalSourcesOfStage(ERecordingSessionStage.FrameDone, this); + } + + public void PrepareNewFrame() + { + m_Recorder.SignalSourcesOfStage(ERecordingSessionStage.NewFrameStarting, this); + m_Recorder.PrepareNewFrame(this); + } + + public void Dispose() + { + if (m_Recorder != null) + { + if (recording) + EndRecording(); + + UnityHelpers.Destroy(m_Recorder); + UnityHelpers.Destroy(m_RecorderGO); + } + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/RecordingSession.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/RecordingSession.cs.meta new file mode 100644 index 0000000..d1b3321 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/RecordingSession.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 061b0c1d04c01f54fa322b11b8662964 +timeCreated: 1491415343 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/RenderTextureRecorder.cs b/source/Unity/FrameRecorder/Scripts/Engine/RenderTextureRecorder.cs new file mode 100644 index 0000000..68ec6be --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/RenderTextureRecorder.cs @@ -0,0 +1,144 @@ +using System.Collections.Generic; +#if UNITY_EDITOR +using UnityEditor; +#endif +using UnityEngine.Recorder.FrameRecorder.DataSource; + +namespace UnityEngine.Recorder.FrameRecorder +{ + public abstract class RenderTextureRecorder : Recorder where T : ImageRecorderSettings + { + [SerializeField] + protected T m_Settings; + public override FrameRecorderSettings settings + { + get { return m_Settings; } + set { m_Settings = (T)value; } + } + + protected string m_OutputFile; + +#if UNITY_EDITOR + public static EditorWindow GetMainGameView() + { + System.Type T = System.Type.GetType("UnityEditor.GameView,UnityEditor"); + System.Reflection.MethodInfo GetMainGameView = T.GetMethod("GetMainGameView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + System.Object Res = GetMainGameView.Invoke(null, null); + return (EditorWindow)Res; + } + +#endif + + public override bool BeginRecording(RecordingSession session) + { + base.BeginRecording(session); + + m_BoxedSources = new List(); + + // Targetting explicit cameras? + if (session.m_ObjsOfInterest != null) + { + foreach (var objOfInterest in session.m_ObjsOfInterest) + { + if (!(objOfInterest is Camera)) + continue; + + var source = new CameraAsRenderTexture( EImageSizeMode.Width, m_Settings.m_Width, 0) + { + TargetCamera = (Camera)objOfInterest + }; + var boxedSource = new BoxedSource(source); + boxedSource.m_StageHandlers.Add(ERecordingSessionStage.NewFrameStarting, (x) => source.PrepareNewFrame()); + + m_BoxedSources.Add(boxedSource); + } + } + + // Targetting a game display? + if (m_Settings.m_InputType == EImageSourceType.GameDisplay) + { + var source = new GameViewAsRenderTexture(m_Settings.m_SizeMode, m_Settings.m_Width, m_Settings.m_Height ); + var boxedSource = new BoxedSource(source); + boxedSource.m_StageHandlers.Add(ERecordingSessionStage.NewFrameStarting, (x) => source.PrepareNewFrame(session) ); + boxedSource.m_StageHandlers.Add(ERecordingSessionStage.BeginRecording, (x) => source.BeginRecording() ); + boxedSource.m_StageHandlers.Add(ERecordingSessionStage.EndRecording, (x) => source.EndRecording() ); + + m_BoxedSources.Add(boxedSource); + + } + + + // Targetting the scene view? +#if UNITY_EDITOR + /* + if (m_Settings.m_InputType == EImageSourceType.SceneView) + { + if (!m_Settings.m_ScaleImage) + { + m_Settings.m_Width = (int)SceneView.currentDrawingSceneView.position.width; + } + + var source = new DisplayAsRenderTexture(m_Settings.m_Width); + var boxedSource = new BoxedSource(source); + boxedSource.m_StageHandlers.Add(ERecordingSessionStage.NewFrameStarting, + (x) => + { + source.m_DisplayID = m_Settings.m_ScreenID; + source.PrepareNewFrame(session); + }); + + m_BoxedSources.Add(boxedSource); + } + */ +#endif + + // Targetting "Main Camera" + if (m_Settings.m_InputType == EImageSourceType.MainCamera) + { + var source = new CameraAsRenderTexture(EImageSizeMode.Width, m_Settings.m_Width, 0); + var boxedSource = new BoxedSource(source); + boxedSource.m_StageHandlers.Add(ERecordingSessionStage.NewFrameStarting, + (x) => + { + source.TargetCamera = Camera.main; + source.PrepareNewFrame(); + }); + + m_BoxedSources.Add(boxedSource); + } + + // Targetting a "tagged Camera" + if (m_Settings.m_InputType == EImageSourceType.TaggedCamera) + { + var source = new CameraAsRenderTexture(EImageSizeMode.Width, m_Settings.m_Width, 0); + var boxedSource = new BoxedSource(source); + boxedSource.m_StageHandlers.Add(ERecordingSessionStage.NewFrameStarting, + (x) => + { + var candidates = GameObject.FindGameObjectsWithTag(m_Settings.m_CameraTag); + if (candidates.Length > 0) + { + foreach (var candidate in candidates) + { + var cam = candidate.GetComponent(); + if (cam != null) + { + source.TargetCamera = cam; + break; + } + } + } + else + { + Debug.LogError("No GameObject found with tag: " + m_Settings.m_CameraTag); + } + source.PrepareNewFrame(); + }); + + m_BoxedSources.Add(boxedSource); + } + + return recording = true; + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/RenderTextureRecorder.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/RenderTextureRecorder.cs.meta new file mode 100644 index 0000000..5318fd3 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/RenderTextureRecorder.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e971ec30698b0bf41bd9afdd5694c8ba +timeCreated: 1491415344 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Timeline.meta b/source/Unity/FrameRecorder/Scripts/Engine/Timeline.meta new file mode 100644 index 0000000..6876fc6 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Timeline.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: d710ed9c46e2cf948ac52d735f4a83f0 +folderAsset: yes +timeCreated: 1491415339 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderClip.cs b/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderClip.cs new file mode 100644 index 0000000..8dff400 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderClip.cs @@ -0,0 +1,72 @@ +using System; +using UnityEditor; +using UnityEngine.Recorder.FrameRecorder.Utilities; +using UnityEngine; +using UnityEngine.Playables; +using UnityEngine.Timeline; + +namespace UnityEngine.Recorder.FrameRecorder.Timeline +{ + public class FrameRecorderClip : PlayableAsset, ITimelineClipAsset + { + [SerializeField] public string m_RecorderTypeName; + [SerializeField] public string m_RecorderCategory; + + public FrameRecorderSettings m_Settings; + Type m_RecorderType; + + void OnEnable() + { + if (string.IsNullOrEmpty(m_RecorderTypeName)) + return; + + var assetGuid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(this)); + m_Settings = RecordersInventory.CreateRecorderSettings(recorderType, assetGuid, "TmLn-Clip:" + assetGuid); + } + + public Type recorderType + { + get + { + if (m_RecorderType == null && !string.IsNullOrEmpty(m_RecorderTypeName)) + m_RecorderType = Type.GetType(m_RecorderTypeName); + return m_RecorderType; + } + set + { + if (m_RecorderType != value) + { + m_RecorderType = value; + m_RecorderTypeName = value != null ? m_RecorderType.AssemblyQualifiedName : string.Empty; + } + } + } + + public ClipCaps clipCaps + { + get { return ClipCaps.None; } + } + + public override Playable CreatePlayable(PlayableGraph graph, GameObject owner) + { + var playable = ScriptPlayable.Create( graph ); + var behaviour = playable.GetBehaviour(); + if (recorderType != null && UnityHelpers.IsPlaying()) + { + behaviour.session = new RecordingSession() + { + m_Recorder = RecordersInventory.InstantiateRecorder(recorderType, m_Settings), + m_RecorderGO = FrameRecorderGOControler.HookupRecorder(m_Settings), + m_RecordingStartTS = Time.time, + m_FrameIndex = 0 + }; + } + return playable; + } + + public virtual void OnDestroy() + { + RecordersInventory.DeleteSettings(m_Settings.m_UniqueID); + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderClip.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderClip.cs.meta new file mode 100644 index 0000000..39e96a4 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderClip.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f1e95aa6d658d694785bfde37c857fff +timeCreated: 1491415344 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderPlayable.cs b/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderPlayable.cs new file mode 100644 index 0000000..91d9f39 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderPlayable.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections; +using UnityEngine.Recorder.FrameRecorder.Utilities; +using UnityEngine.Playables; + +namespace UnityEngine.Recorder.FrameRecorder.Timeline +{ + /// + /// Note: Totally ignores the time info comming from the playable infrastructure. Only conciders scaled time. + /// + public class FrameRecorderPlayable : PlayableBehaviour + { + PlayState m_PlayState = PlayState.Paused; + public RecordingSession session { get; set; } + WaitForEndOfFrameComponent endOfFrameComp; + bool m_FirstOneSkipped; + + public override void OnGraphStart(Playable playable) + { + if (session != null) + { + // does not support multiple starts... + session.BeginRecording(); + m_PlayState = PlayState.Paused; + } + } + + public override void OnGraphStop(Playable playable) + { + if (session != null) + session.EndRecording(); + } + + public override void PrepareFrame(Playable playable, FrameData info) + { + if (session != null && session.recording) + { + session.m_CurrentFrameStartTS = (Time.time / Time.timeScale) - session.m_RecordingStartTS; + session.PrepareNewFrame(); + } + } + + public override void ProcessFrame(Playable playable, FrameData info, object playerData) + { + if (session != null) + { + if (endOfFrameComp == null) + { + endOfFrameComp = session.m_RecorderGO.AddComponent(); + endOfFrameComp.m_playable = this; + } + + if (session.recording) + session.m_FrameIndex++; + } + } + + + public override void OnBehaviourPlay(Playable playable, FrameData info) + { + if (session == null) + return; + + // Assumption: OnPlayStateChanged( PlayState.Playing ) ONLY EVER CALLED ONCE for this type of playable. + session.m_RecordingStartTS = Time.time / Time.timeScale; + m_PlayState = PlayState.Playing; + } + + public override void OnBehaviourPause(Playable playable, FrameData info) + { + if (session == null) + return; + if (session.recording && m_PlayState == PlayState.Playing) + session.EndRecording(); + + m_PlayState = PlayState.Paused; + } + + public void FrameEnded() + { + if (session != null && session.recording) + session.RecordFrame(); + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderPlayable.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderPlayable.cs.meta new file mode 100644 index 0000000..ed9a41b --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderPlayable.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d580900d83be2664c9c035d6e50791fe +timeCreated: 1491415344 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderTrack.cs b/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderTrack.cs new file mode 100644 index 0000000..e2d46b5 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderTrack.cs @@ -0,0 +1,12 @@ +using UnityEngine.Timeline; + +namespace UnityEngine.Recorder.FrameRecorder.Timeline +{ + [System.Serializable] + [TrackClipType(typeof(FrameRecorderClip))] + [TrackMediaType(TimelineAsset.MediaType.Script)] + [TrackColor(0.53f, 0.0f, 0.08f)] + public class FrameRecorderTrack : TrackAsset + { + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderTrack.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderTrack.cs.meta new file mode 100644 index 0000000..b59c9ee --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Timeline/FrameRecorderTrack.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 0e6cf5671577b7344ba25c25b4346ce4 +timeCreated: 1491415343 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Timeline/WaitForEndOfFrameComponent.cs b/source/Unity/FrameRecorder/Scripts/Engine/Timeline/WaitForEndOfFrameComponent.cs new file mode 100644 index 0000000..f36b531 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Timeline/WaitForEndOfFrameComponent.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections; + +namespace UnityEngine.Recorder.FrameRecorder.Timeline +{ + // the purpose of this class is to signal the FrameRecorderPlayable when frame is done. + [ExecuteInEditMode] + class WaitForEndOfFrameComponent : MonoBehaviour + { + [NonSerialized] + public FrameRecorderPlayable m_playable; + + public IEnumerator WaitForEndOfFrame() + { + yield return new WaitForEndOfFrame(); + if(m_playable != null) + m_playable.FrameEnded(); + } + + public void LateUpdate() + { + StartCoroutine(WaitForEndOfFrame()); + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Timeline/WaitForEndOfFrameComponent.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/Timeline/WaitForEndOfFrameComponent.cs.meta new file mode 100644 index 0000000..4a4e1d5 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Timeline/WaitForEndOfFrameComponent.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: fb0550cc97f23464f99f702c09306f21 +timeCreated: 1491590881 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Utilities.meta b/source/Unity/FrameRecorder/Scripts/Engine/Utilities.meta new file mode 100644 index 0000000..4248c8b --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Utilities.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 9706f1322d8993d46b6110692af7e26b +folderAsset: yes +timeCreated: 1491415339 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Utilities/ClassHelpers.cs b/source/Unity/FrameRecorder/Scripts/Engine/Utilities/ClassHelpers.cs new file mode 100644 index 0000000..90f51a7 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Utilities/ClassHelpers.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace UnityEngine.Recorder.FrameRecorder.Utilities +{ + public class ClassHelpers + { + public static IEnumerable> FilterByAttribute(bool inherit = false) + { + var attribType = typeof(T); + foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + { + foreach (var t in a.GetTypes()) + { + var attributes = t.GetCustomAttributes(attribType, inherit); + if (attributes.Length != 0) + yield return new KeyValuePair(t, attributes); + } + } + } + + public static T GetAttribute(Type type) where T : class + { + var attributes = type.GetCustomAttributes(typeof(T), true); + if (attributes.Length == 0) + return null; + else + return attributes[0] as T; + } + + public static T GetAttribute(MethodInfo methofInfo) where T : class + { + var attributes = methofInfo.GetCustomAttributes(typeof(T), true); + + if (attributes.Length == 0) + return null; + else + return attributes[0] as T; + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Utilities/ClassHelpers.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/Utilities/ClassHelpers.cs.meta new file mode 100644 index 0000000..a0c113b --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Utilities/ClassHelpers.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: bb4bca9c0585ca14fbd975b3380f946e +timeCreated: 1491415343 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Utilities/GameViewWindow.cs b/source/Unity/FrameRecorder/Scripts/Engine/Utilities/GameViewWindow.cs new file mode 100644 index 0000000..102e681 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Utilities/GameViewWindow.cs @@ -0,0 +1,153 @@ +using System; +using System.IO; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace UnityEngine.Recorder.FrameRecorder.Utilities +{ + public class GameViewWindow + { + + + public static class LayoutUtility { + + private static MethodInfo _miLoadWindowLayout; + private static MethodInfo _miSaveWindowLayout; + private static MethodInfo _miReloadWindowLayoutMenu; + + private static bool _available; + private static string _layoutsPath; + + static LayoutUtility() { + Type tyWindowLayout = Type.GetType("UnityEditor.WindowLayout,UnityEditor"); + Type tyEditorUtility = Type.GetType("UnityEditor.EditorUtility,UnityEditor"); + Type tyInternalEditorUtility = Type.GetType("UnityEditorInternal.InternalEditorUtility,UnityEditor"); + + if (tyWindowLayout != null && tyEditorUtility != null && tyInternalEditorUtility != null) { + MethodInfo miGetLayoutsPath = tyWindowLayout.GetMethod("GetLayoutsPath", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + _miLoadWindowLayout = tyWindowLayout.GetMethod("LoadWindowLayout", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, null); + _miSaveWindowLayout = tyWindowLayout.GetMethod("SaveWindowLayout", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, null); + _miReloadWindowLayoutMenu = tyInternalEditorUtility.GetMethod("ReloadWindowLayoutMenu", BindingFlags.Public | BindingFlags.Static); + + if (miGetLayoutsPath == null || _miLoadWindowLayout == null || _miSaveWindowLayout == null || _miReloadWindowLayoutMenu == null) + return; + + _layoutsPath = (string)miGetLayoutsPath.Invoke(null, null); + if (string.IsNullOrEmpty(_layoutsPath)) + return; + + _available = true; + } + } + + // Gets a value indicating whether all required Unity API + // functionality is available for usage. + public static bool IsAvailable { + get { return _available; } + } + + // Gets absolute path of layouts directory. + // Returns `null` when not available. + public static string LayoutsPath { + get { return _layoutsPath; } + } + + // Save current window layout to asset file. + // `assetPath` must be relative to project directory. + public static void SaveLayoutToAsset(string assetPath) { + SaveLayout(Path.Combine(Directory.GetCurrentDirectory(), assetPath)); + } + + // Load window layout from asset file. + // `assetPath` must be relative to project directory. + public static void LoadLayoutFromAsset(string assetPath) { + if (_miLoadWindowLayout != null) { + string path = Path.Combine(Directory.GetCurrentDirectory(), assetPath); + _miLoadWindowLayout.Invoke(null, new object[] { path }); + } + } + + // Save current window layout to file. + // `path` must be absolute. + public static void SaveLayout(string path) { + if (_miSaveWindowLayout != null) + _miSaveWindowLayout.Invoke(null, new object[] { path }); + } + + } + + + + Rect m_OrgPosition; + Vector2 m_OrgSize; + Vector2 m_OrgMaxSize; + EImageSizeMode m_SizeMode = EImageSizeMode.Dynamic; + public Vector2 size { get; private set; } + + //The size of the toolbar above the game view, excluding the OS border. + private static int tabHeight = 22; + + static EditorWindow GetMainGameView() + { + EditorApplication.ExecuteMenuItem("Window/Game"); + + System.Type T = System.Type.GetType("UnityEditor.GameView,UnityEditor"); + System.Reflection.MethodInfo GetMainGameView = T.GetMethod("GetMainGameView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + System.Object Res = GetMainGameView.Invoke(null, null); + return (EditorWindow)Res; + } + + public void FullScreenGameWindow( EImageSizeMode sizeMode, int width, int height ) + { + m_SizeMode = sizeMode; + var gameView = GetMainGameView(); + + m_OrgPosition = gameView.position; + m_OrgSize = gameView.minSize; + m_OrgMaxSize = gameView.maxSize; + switch (sizeMode) + { + case EImageSizeMode.Dynamic: + { + size = new Vector2(gameView.position.width, gameView.position.height); + return; + } + case EImageSizeMode.FullScreen: + { + width = Screen.currentResolution.width; + height = Screen.currentResolution.height; + break; + } + case EImageSizeMode.Width: + { + height = (int)((height / (double)Screen.currentResolution.width) * width); + break; + } + case EImageSizeMode.Custom: + break; + } + + var newPos = new Rect(0, 0 - tabHeight, Screen.currentResolution.width, Screen.currentResolution.height + tabHeight); + gameView.position = newPos; + size = new Vector2(width, height); + gameView.minSize = size = new Vector2(width, height + tabHeight);; + gameView.maxSize = gameView.minSize; + gameView.position = newPos; + } + + public void RestoreGameWindow() + { + if (m_SizeMode != EImageSizeMode.Dynamic) + { + var gameView = GetMainGameView(); + gameView.Close(); + gameView = GetMainGameView(); + gameView.minSize = m_OrgSize; + gameView.maxSize = m_OrgMaxSize; + gameView.position = m_OrgPosition; + } + } + + } +} \ No newline at end of file diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Utilities/GameViewWindow.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/Utilities/GameViewWindow.cs.meta new file mode 100644 index 0000000..a8969a3 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Utilities/GameViewWindow.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 1f428d7759a94b145b1aa0a53a11071f +timeCreated: 1493926581 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Utilities/RecordersCache.cs b/source/Unity/FrameRecorder/Scripts/Engine/Utilities/RecordersCache.cs new file mode 100644 index 0000000..e559d1b --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Utilities/RecordersCache.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace UnityEngine.Recorder.FrameRecorder.Utilities +{ + public static class RecordersCache + { + class CachedRecorder + { + public Type type { get; set; } + public string category { get; set; } + } + + static List m_RecordersCache; + + static void Init() + { + if (m_RecordersCache == null) + { + m_RecordersCache = RecordersInventory.recorders + .OrderBy((x) => x.Key) + .Select((x) => x.Value) + .ToList(); + } + } + + public static int GroupedIndexOfRecorder(Type recorder, string category) + { + Init(); + + var skipped = 0; + var any = false; + for (var i = 0; i < m_RecordersCache.Count; i++) + { + if (string.Compare(category, m_RecordersCache[i].category, StringComparison.InvariantCultureIgnoreCase) == 0) + skipped++; + else if (m_RecordersCache[i].recorder == recorder) + return i - skipped; + else + any = true; + } + + if (any) + return 0; + else + return -1; + } + + public static Type RecorderFromGroupedIndex(int index, string category) + { + Init(); + + var filteredIndex = 0; + foreach (var t in m_RecordersCache) + { + if (string.Compare(category, t.category, StringComparison.InvariantCultureIgnoreCase) != 0) + continue; + + if (index == filteredIndex) + return t.recorder; + filteredIndex++; + } + + return null; + } + + public static string[] GetNameOfRecordersInGroup(string category) + { + Init(); + + // count them + int count = 0; + foreach (var item in m_RecordersCache) + if (string.Compare(category, item.category, StringComparison.InvariantCultureIgnoreCase) == 0) + count++; + + // store them + var result = new string[count]; + if (count > 0) + { + count = 0; + foreach (var item in m_RecordersCache) + if (string.Compare(category, item.category, StringComparison.InvariantCultureIgnoreCase) == 0) + result[count++] = item.recorder.FullName; + } + + return result; + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Utilities/RecordersCache.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/Utilities/RecordersCache.cs.meta new file mode 100644 index 0000000..bdf16f4 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Utilities/RecordersCache.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9ea1d95d7f0add84091f7807bcbd2d82 +timeCreated: 1491415343 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Utilities/UnityHelpers.cs b/source/Unity/FrameRecorder/Scripts/Engine/Utilities/UnityHelpers.cs new file mode 100644 index 0000000..7076c98 --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Utilities/UnityHelpers.cs @@ -0,0 +1,31 @@ +using Object = UnityEngine.Object; + +namespace UnityEngine.Recorder.FrameRecorder.Utilities +{ + public static class UnityHelpers + { + public static void Destroy(Object obj) + { + if (obj == null) + return; +#if UNITY_EDITOR + if (UnityEditor.EditorApplication.isPlaying) + Object.Destroy(obj); + else + Object.DestroyImmediate(obj); +#else + Object.Destroy(m_HostGO); +#endif + obj = null; + } + + public static bool IsPlaying() + { +#if UNITY_EDITOR + return UnityEditor.EditorApplication.isPlaying; +#else + return true; +#endif + } + } +} diff --git a/source/Unity/FrameRecorder/Scripts/Engine/Utilities/UnityHelpers.cs.meta b/source/Unity/FrameRecorder/Scripts/Engine/Utilities/UnityHelpers.cs.meta new file mode 100644 index 0000000..232a67e --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Engine/Utilities/UnityHelpers.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 639bab1152ec3984184d25ab38abd226 +timeCreated: 1491415343 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Scripts/Example.meta b/source/Unity/FrameRecorder/Scripts/Example.meta new file mode 100644 index 0000000..70203ac --- /dev/null +++ b/source/Unity/FrameRecorder/Scripts/Example.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: a843bc4482baa5846971389324919dad +folderAsset: yes +timeCreated: 1491415339 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Shaders.meta b/source/Unity/FrameRecorder/Shaders.meta new file mode 100644 index 0000000..01e9cca --- /dev/null +++ b/source/Unity/FrameRecorder/Shaders.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: d55140aa4996ef248b1e815274e1c193 +folderAsset: yes +timeCreated: 1491415339 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/Unity/FrameRecorder/Shaders/TestGrabberShader.shader b/source/Unity/FrameRecorder/Shaders/TestGrabberShader.shader new file mode 100644 index 0000000..7fa1757 --- /dev/null +++ b/source/Unity/FrameRecorder/Shaders/TestGrabberShader.shader @@ -0,0 +1,205 @@ +Shader "Unity/FrameRecorder/TestGrabberShader" { + + Properties{ + } + + CGINCLUDE +#include "UnityCG.cginc" +#pragma multi_compile ___ UNITY_HDR_ON +#pragma multi_compile ___ OFFSCREEN + + sampler2D _TmpFrameBuffer; + sampler2D _CameraGBufferTexture0; + sampler2D _CameraGBufferTexture1; + sampler2D _CameraGBufferTexture2; + sampler2D _CameraGBufferTexture3; + sampler2D_float _CameraDepthTexture; + sampler2D _TmpRenderTarget; + + struct v2f + { + float4 pos : POSITION; + float4 spos : TEXCOORD0; + }; + + v2f vert(appdata_img v) + { + v2f o; + o.pos = o.spos = v.vertex; + return o; + } + + + float2 get_texcoord(v2f i) + { + float2 t = i.spos.xy * 0.5 + 0.5; + return t; + } + + float2 get_texcoord_gb(v2f i) + { + float2 t = i.spos.xy * 0.5 + 0.5; +#if !defined(UNITY_UV_STARTS_AT_TOP) + t.y = 1.0 - t.y; +#endif + return t; + } + + + half4 copy_framebuffer(v2f I) : SV_Target + { + float2 t = get_texcoord(I); +#if !defined(OFFSCREEN) || !defined(UNITY_UV_STARTS_AT_TOP) + t.y = 1.0 - t.y; +#endif + half4 O = tex2D(_TmpFrameBuffer, t); + O.a = 1.0; + return O; + } + + + // g-buffer + struct gbuffer_out + { + half4 diffuse : SV_Target0; // RT0: diffuse color (rgb), occlusion (a) + half4 spec_smoothness : SV_Target1; // RT1: spec color (rgb), smoothness (a) + half4 normal : SV_Target2; // RT2: normal (rgb), --unused, very low precision-- (a) + half4 emission : SV_Target3; // RT3: emission (rgb), --unused-- (a) + }; + gbuffer_out copy_gbuffer(v2f I) + { + float2 t = get_texcoord_gb(I); + gbuffer_out O; + O.diffuse = tex2D(_CameraGBufferTexture0, t); + O.spec_smoothness = tex2D(_CameraGBufferTexture1, t); + O.normal = tex2D(_CameraGBufferTexture2, t); + O.emission = tex2D(_CameraGBufferTexture3, t); +#ifndef UNITY_HDR_ON + O.emission.rgb = -log2(O.emission.rgb); +#endif + return O; + } + + + // depth + float4 copy_depth(v2f I) : SV_Target + { + float4 O = tex2D(_CameraDepthTexture, get_texcoord_gb(I)).rrrr; + return O; + } + + + // render target (for offscreen-recorder) + half4 copy_rendertarget(v2f I) : SV_Target + { + half4 O = tex2D(_TmpRenderTarget, get_texcoord_gb(I)); + return O; + } + + + // albedo, occlusion, specular, smoothness + struct aoss_out + { + half4 albedo : SV_Target0; + half4 occlusion : SV_Target1; + half4 specular : SV_Target2; + half4 smoothness : SV_Target3; + }; + aoss_out copy_aoss(v2f I) + { + float2 t = get_texcoord_gb(I); + half4 ao = tex2D(_CameraGBufferTexture0, t); + half4 ss = tex2D(_CameraGBufferTexture1, t); + + aoss_out O; + O.albedo = half4(ao.rgb, 1.0); + O.occlusion = ao.aaaa; + O.specular = half4(ss.rgb, 1.0); + O.smoothness = ss.aaaa; + return O; + } + + + // normal, emission, depth + struct ned_out + { + half4 normal : SV_Target0; + half4 emission : SV_Target1; + half4 depth : SV_Target2; + }; + ned_out copy_ned(v2f I) + { + float2 t = get_texcoord_gb(I); + half4 normal = tex2D(_CameraGBufferTexture2, t); + half4 emission = tex2D(_CameraGBufferTexture3, t); + half4 depth = tex2D(_CameraDepthTexture, get_texcoord_gb(I)); + + ned_out O; + O.normal = half4(normal.rgb, 1.0); + O.emission = half4(emission.rgb, 1.0); +#ifndef UNITY_HDR_ON + O.emission.rgb = -log2(O.emission.rgb); +#endif + O.depth = depth.rrrr; + return O; + } + ENDCG + + Subshader{ + // Pass 0: framebuffer + Pass{ + Blend Off Cull Off ZTest Off ZWrite Off + CGPROGRAM +#pragma vertex vert +#pragma fragment copy_framebuffer + ENDCG + } + + // Pass 1: g-buffer + Pass{ + Blend Off Cull Off ZTest Off ZWrite Off + CGPROGRAM +#pragma vertex vert +#pragma fragment copy_gbuffer + ENDCG + } + + // Pass 2: depth + Pass{ + Blend Off Cull Off ZTest Off ZWrite Off + CGPROGRAM +#pragma vertex vert +#pragma fragment copy_depth + ENDCG + } + + // Pass 3: render target + Pass{ + Blend Off Cull Off ZTest Off ZWrite Off + CGPROGRAM +#pragma vertex vert +#pragma fragment copy_rendertarget + ENDCG + } + + // Pass 4: albedo, occlusion, specular, smoothness + Pass{ + Blend Off Cull Off ZTest Off ZWrite Off + CGPROGRAM +#pragma vertex vert +#pragma fragment copy_aoss + ENDCG + } + + // Pass 5: normal, emission, depth + Pass{ + Blend Off Cull Off ZTest Off ZWrite Off + CGPROGRAM +#pragma vertex vert +#pragma fragment copy_ned + ENDCG + } + } + + Fallback off +} diff --git a/source/Unity/FrameRecorder/Shaders/TestGrabberShader.shader.meta b/source/Unity/FrameRecorder/Shaders/TestGrabberShader.shader.meta new file mode 100644 index 0000000..8ee58ae --- /dev/null +++ b/source/Unity/FrameRecorder/Shaders/TestGrabberShader.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 55d39f733f4029f4cbc92b5c2279e15b +timeCreated: 1491415345 +licenseType: Free +ShaderImporter: + defaultTextures: [] + userData: + assetBundleName: + assetBundleVariant: