WaveformWindow.cs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. using UnityEngine;
  2. using UnityEditor;
  3. using System.IO;
  4. using System.Collections.Generic;
  5. using UnityEngine.Rendering;
  6. namespace Cinemachine.Editor
  7. {
  8. internal class WaveformWindow : EditorWindow
  9. {
  10. WaveformGenerator mWaveformGenerator;
  11. Texture2D mScreenshot;
  12. string mScreenshotFilename;
  13. // Controls how frequently (in seconds) the view will update.
  14. // Performance is really bad, so keep this as large as possible.
  15. public static float UpdateInterval = 0.5f;
  16. public static void SetDefaultUpdateInterval() { UpdateInterval = 0.5f; }
  17. //[MenuItem("Window/Waveform Monitor")]
  18. public static void OpenWindow()
  19. {
  20. WaveformWindow window = EditorWindow.GetWindow<WaveformWindow>(false);
  21. window.autoRepaintOnSceneChange = true;
  22. //window.position = new Rect(100, 100, 400, 400);
  23. window.Show(true);
  24. }
  25. private void OnEnable()
  26. {
  27. titleContent = new GUIContent("Waveform", CinemachineSettings.CinemachineLogoTexture);
  28. mWaveformGenerator = new WaveformGenerator();
  29. mScreenshotFilename = Path.GetFullPath(FileUtil.GetUniqueTempPathInProject() + ".png");
  30. ScreenCapture.CaptureScreenshot(mScreenshotFilename);
  31. EditorApplication.update += UpdateScreenshot;
  32. }
  33. private void OnDisable()
  34. {
  35. EditorApplication.update -= UpdateScreenshot;
  36. if (!string.IsNullOrEmpty(mScreenshotFilename) && File.Exists(mScreenshotFilename))
  37. File.Delete(mScreenshotFilename);
  38. mScreenshotFilename = null;
  39. mWaveformGenerator.DestroyBuffers();
  40. if (mScreenshot != null)
  41. DestroyImmediate(mScreenshot);
  42. mScreenshot = null;
  43. }
  44. private void OnGUI()
  45. {
  46. Rect rect = EditorGUILayout.GetControlRect(true);
  47. EditorGUIUtility.labelWidth /= 2;
  48. EditorGUI.BeginChangeCheck();
  49. mWaveformGenerator.m_Exposure = EditorGUI.Slider(
  50. rect, "Exposure", mWaveformGenerator.m_Exposure, 0.01f, 2);
  51. if (EditorGUI.EndChangeCheck())
  52. UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
  53. EditorGUIUtility.labelWidth *= 2;
  54. rect.y += rect.height;
  55. rect.height = position.height - rect.height;
  56. var tex = mWaveformGenerator.Result;
  57. if (tex != null)
  58. GUI.DrawTexture(rect, tex);
  59. }
  60. float mLastUpdateTime = 0;
  61. private void UpdateScreenshot()
  62. {
  63. // Don't do this too often
  64. float now = Time.time;
  65. if (mScreenshot != null && now - mLastUpdateTime < UpdateInterval)
  66. return;
  67. mLastUpdateTime = now;
  68. if (!string.IsNullOrEmpty(mScreenshotFilename) && File.Exists(mScreenshotFilename))
  69. {
  70. byte[] fileData = File.ReadAllBytes(mScreenshotFilename);
  71. if (mScreenshot == null)
  72. mScreenshot = new Texture2D(2, 2);
  73. mScreenshot.LoadImage(fileData); // this will auto-resize the texture dimensions.
  74. mWaveformGenerator.RenderWaveform(mScreenshot);
  75. // Capture the next one
  76. ScreenCapture.CaptureScreenshot(mScreenshotFilename);
  77. }
  78. }
  79. class WaveformGenerator
  80. {
  81. public float m_Exposure = 0.2f;
  82. RenderTexture mOutput;
  83. ComputeBuffer mData;
  84. int mThreadGroupSize;
  85. int mThreadGroupSizeX;
  86. int mThreadGroupSizeY;
  87. ComputeShader mWaveformCompute;
  88. MaterialPropertyBlock mWaveformProperties;
  89. Material mWaveformMaterial;
  90. CommandBuffer mCmd;
  91. static Mesh sFullscreenTriangle;
  92. static Mesh FullscreenTriangle
  93. {
  94. get
  95. {
  96. if (sFullscreenTriangle == null)
  97. {
  98. sFullscreenTriangle = new Mesh { name = "Fullscreen Triangle" };
  99. sFullscreenTriangle.SetVertices(new List<Vector3>
  100. {
  101. new Vector3(-1f, -1f, 0f),
  102. new Vector3(-1f, 3f, 0f),
  103. new Vector3( 3f, -1f, 0f)
  104. });
  105. sFullscreenTriangle.SetIndices(
  106. new [] { 0, 1, 2 }, MeshTopology.Triangles, 0, false);
  107. sFullscreenTriangle.UploadMeshData(false);
  108. }
  109. return sFullscreenTriangle;
  110. }
  111. }
  112. public WaveformGenerator()
  113. {
  114. mWaveformCompute = AssetDatabase.LoadAssetAtPath<ComputeShader>(
  115. ScriptableObjectUtility.CinemachineRealativeInstallPath
  116. + "/Editor/EditorResources/CMWaveform.compute");
  117. mWaveformProperties = new MaterialPropertyBlock();
  118. mWaveformMaterial = new Material(AssetDatabase.LoadAssetAtPath<Shader>(
  119. ScriptableObjectUtility.CinemachineRealativeInstallPath
  120. + "/Editor/EditorResources/CMWaveform.shader"))
  121. {
  122. name = "CMWaveformMaterial",
  123. hideFlags = HideFlags.DontSave
  124. };
  125. mCmd = new CommandBuffer();
  126. }
  127. void CreateBuffers(int width, int height)
  128. {
  129. if (mOutput == null || !mOutput.IsCreated()
  130. || mOutput.width != width || mOutput.height != height)
  131. {
  132. DestroyImmediate(mOutput);
  133. mOutput = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32)
  134. {
  135. anisoLevel = 0,
  136. filterMode = FilterMode.Bilinear,
  137. wrapMode = TextureWrapMode.Clamp,
  138. useMipMap = false
  139. };
  140. mOutput.Create();
  141. }
  142. int count = Mathf.CeilToInt(width / (float)mThreadGroupSizeX) * mThreadGroupSizeX * height;
  143. if (mData == null)
  144. mData = new ComputeBuffer(count, sizeof(uint) << 2);
  145. else if (mData.count < count)
  146. {
  147. mData.Release();
  148. mData = new ComputeBuffer(count, sizeof(uint) << 2);
  149. }
  150. }
  151. public void DestroyBuffers()
  152. {
  153. if (mData != null)
  154. mData.Release();
  155. mData = null;
  156. DestroyImmediate(mOutput);
  157. mOutput = null;
  158. }
  159. public RenderTexture Result { get { return mOutput; } }
  160. public void RenderWaveform(Texture2D source)
  161. {
  162. if (mWaveformMaterial == null)
  163. return;
  164. int width = source.width;
  165. int height = source.height;
  166. int histogramResolution = 256;
  167. mThreadGroupSize = 256;
  168. mThreadGroupSizeX = 16;
  169. mThreadGroupSizeY = 16;
  170. CreateBuffers(width, histogramResolution);
  171. mCmd.Clear();
  172. mCmd.BeginSample("CMWaveform");
  173. var parameters = new Vector4(
  174. width, height,
  175. QualitySettings.activeColorSpace == ColorSpace.Linear ? 1 : 0,
  176. histogramResolution);
  177. // Clear the buffer on every frame
  178. int kernel = mWaveformCompute.FindKernel("KCMWaveformClear");
  179. mCmd.SetComputeBufferParam(mWaveformCompute, kernel, "_WaveformBuffer", mData);
  180. mCmd.SetComputeVectorParam(mWaveformCompute, "_Params", parameters);
  181. mCmd.DispatchCompute(mWaveformCompute, kernel,
  182. Mathf.CeilToInt(width / (float)mThreadGroupSizeX),
  183. Mathf.CeilToInt(histogramResolution / (float)mThreadGroupSizeY), 1);
  184. // Gather all pixels and fill in our waveform
  185. kernel = mWaveformCompute.FindKernel("KCMWaveformGather");
  186. mCmd.SetComputeBufferParam(mWaveformCompute, kernel, "_WaveformBuffer", mData);
  187. mCmd.SetComputeTextureParam(mWaveformCompute, kernel, "_Source", source);
  188. mCmd.SetComputeVectorParam(mWaveformCompute, "_Params", parameters);
  189. mCmd.DispatchCompute(mWaveformCompute, kernel, width,
  190. Mathf.CeilToInt(height / (float)mThreadGroupSize), 1);
  191. // Generate the waveform texture
  192. float exposure = Mathf.Max(0f, m_Exposure);
  193. exposure *= (float)histogramResolution / height;
  194. mWaveformProperties.SetVector(Shader.PropertyToID("_Params"),
  195. new Vector4(width, histogramResolution, exposure, 0f));
  196. mWaveformProperties.SetBuffer(Shader.PropertyToID("_WaveformBuffer"), mData);
  197. mCmd.SetRenderTarget(mOutput);
  198. mCmd.DrawMesh(
  199. FullscreenTriangle, Matrix4x4.identity,
  200. mWaveformMaterial, 0, 0, mWaveformProperties);
  201. mCmd.EndSample("CMWaveform");
  202. Graphics.ExecuteCommandBuffer(mCmd);
  203. }
  204. }
  205. }
  206. }