diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30bdd7b --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +**/.DS_Store/ +**/.vs/ +**/.idea/ +**/obj/ +**/bin/ +**/Jetbrains* + +**/Library/ +**/Temp/ +**/Build/ +**/Logs/ + +*.sln +*.user +*.csproj +*.userprefs diff --git a/DrawCallState/DrawCallState.shader b/DrawCallState/DrawCallState.shader deleted file mode 100644 index 2a2b8e8..0000000 --- a/DrawCallState/DrawCallState.shader +++ /dev/null @@ -1,52 +0,0 @@ -Shader "DrawCallState/DrawCallState" -{ - Properties - { - [Header(Hardware settings)] - [Enum(UnityEngine.Rendering.CullMode)] HARDWARE_CullMode ("Cull faces", Float) = 2 - [Enum(UnityEngine.Rendering.BlendMode)] HARDWARE_BlendSrc ("Blend Source", Float) = 1 - [Enum(UnityEngine.Rendering.BlendMode)] HARDWARE_BlendDst ("Blend Destination", Float) = 0 - - [Enum(On, 1, Off, 0)] HARDWARE_ZWrite ("Depth write", Float) = 1 - [Enum(UnityEngine.Rendering.CompareFunction)] HARDWARE_ZTest("Depth test", Float) = 4 - - [Header(Hardware stencil)] - HARDWARE_StencilRef ("Stencil REF", Range(0, 255)) = 0 - HARDWARE_ReadMask ("Stencil Read Mask", Range(0, 255)) = 255 - HARDWARE_WriteMask ("Stencil Write Mask", Range(0, 255)) = 255 - - [Enum(UnityEngine.Rendering.CompareFunction)] HARDWARE_StencilComp ("Stencil comparison", Float) = 0 - [Enum(UnityEngine.Rendering.StencilOp)] HARDWARE_StencilPass ("Stencil Pass", Float) = 0 - [Enum(UnityEngine.Rendering.StencilOp)] HARDWARE_StencilFail ("Stencil Fail", Float) = 0 - [Enum(UnityEngine.Rendering.StencilOp)] HARDWARE_StencilZFail ("Stencil Z Fail", Float) = 0 - } - SubShader - { - Tags { "RenderType"="Opaque" "LightMode" = "ForwardBase" "Queue" = "Geometry+50"} - LOD 100 - - Pass - { - Cull [HARDWARE_CullMode] - ZWrite [HARDWARE_ZWrite] - ZTest [HARDWARE_ZTest] - Blend [HARDWARE_BlendSrc] [HARDWARE_BlendDst] - - Stencil - { - Ref [HARDWARE_StencilRef] - Comp [HARDWARE_StencilComp] - - Pass [HARDWARE_StencilPass] - Fail [HARDWARE_StencilFail] - ZFail [HARDWARE_StencilZFail] - } - - CGPROGRAM - - // Write your shader code here - - ENDCG - } - } -} diff --git a/OverdrawMonitor/.vs/OverdrawMonitor/v15/.suo b/OverdrawMonitor/.vs/OverdrawMonitor/v15/.suo deleted file mode 100644 index 6f019d9..0000000 Binary files a/OverdrawMonitor/.vs/OverdrawMonitor/v15/.suo and /dev/null differ diff --git a/OverdrawMonitor/Assets/Demo.unity b/OverdrawMonitor/Assets/Demo.unity new file mode 100644 index 0000000..e978aa9 Binary files /dev/null and b/OverdrawMonitor/Assets/Demo.unity differ diff --git a/OverdrawMonitor/Assets/Demo.unity.meta b/OverdrawMonitor/Assets/Demo.unity.meta new file mode 100644 index 0000000..1ff7197 --- /dev/null +++ b/OverdrawMonitor/Assets/Demo.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1a4b9c6f59ac145fa839aba2060986b2 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/Editor/OverdrawMonitor.Editor.asmdef b/OverdrawMonitor/Assets/OverdrawMonitor/Editor/OverdrawMonitor.Editor.asmdef new file mode 100644 index 0000000..43e4d52 --- /dev/null +++ b/OverdrawMonitor/Assets/OverdrawMonitor/Editor/OverdrawMonitor.Editor.asmdef @@ -0,0 +1,16 @@ +{ + "name": "OverdrawMonitor.Editor", + "references": [ + "GUID:7594097e076d14122ac321968bbd88c0" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [] +} \ No newline at end of file diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/Editor/OverdrawMonitor.Editor.asmdef.meta b/OverdrawMonitor/Assets/OverdrawMonitor/Editor/OverdrawMonitor.Editor.asmdef.meta new file mode 100644 index 0000000..b6334a3 --- /dev/null +++ b/OverdrawMonitor/Assets/OverdrawMonitor/Editor/OverdrawMonitor.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e3bb7e2192ed44d3fa17492fe70682fa +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/Editor/OverdrawMonitorWindow.cs b/OverdrawMonitor/Assets/OverdrawMonitor/Editor/OverdrawMonitorWindow.cs new file mode 100644 index 0000000..3d2d38b --- /dev/null +++ b/OverdrawMonitor/Assets/OverdrawMonitor/Editor/OverdrawMonitorWindow.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +public class OverdrawMonitorWindow : EditorWindow +{ + bool isEnabled => _monitorsGo != null && Application.isPlaying; + GameObject _monitorsGo; + Dictionary _stats; + + [MenuItem("Tools/Overdraw Monitor")] + static void ShowWindow() + { + GetWindow().Show(); + } + + void Init() + { + if (_monitorsGo != null) + throw new Exception("Attempt to start overdraw monitor twice"); + + _monitorsGo = new GameObject("OverdrawMonitor"); + _monitorsGo.hideFlags = HideFlags.HideAndDontSave; + _stats = new Dictionary(); + } + + void TryShutdown() + { + if (_monitorsGo == null) + return; + + DestroyImmediate(_monitorsGo); + _stats = null; + } + + void Update() + { + // Check shutdown if needed + if (!isEnabled) + { + TryShutdown(); + return; + } + + Camera[] activeCameras = Camera.allCameras; + + // Remove monitors for non-active cameras + var monitors = GetAllMonitors(); + foreach (var monitor in monitors) + if (!Array.Exists(activeCameras, c => monitor.targetCamera == c)) + DestroyImmediate(monitor); + + // Add new monitors + monitors = GetAllMonitors(); + foreach (Camera activeCamera in activeCameras) + { + if (!Array.Exists(monitors,m => m.targetCamera == activeCamera)) + { + var monitor = _monitorsGo.AddComponent(); + monitor.SetTargetCamera(activeCamera); + } + } + } + + CameraOverdrawMonitor[] GetAllMonitors() + { + return _monitorsGo.GetComponentsInChildren(true); + } + + void OnGUI() + { + if (Application.isPlaying) + { + int startButtonHeight = 25; + if (GUILayout.Button(isEnabled ? "Stop" : "Start", GUILayout.MaxWidth(100), GUILayout.MaxHeight(startButtonHeight))) + { + if (!isEnabled) + Init(); + else + TryShutdown(); + } + + if (!isEnabled) + return; + + CameraOverdrawMonitor[] monitors = GetAllMonitors(); + + GUILayout.Space(-startButtonHeight); + using (new GUILayout.HorizontalScope()) + { + GUILayout.FlexibleSpace(); + + if (GUILayout.Button("Reset Stats", GUILayout.Width(100), GUILayout.Height(20))) + ResetStats(); + } + + GUILayout.Space(5); + + Vector2Int gameViewResolution = GetGameViewResolution(); + GUILayout.Label($"Screen {gameViewResolution.x}x{gameViewResolution.y}"); + + GUILayout.Space(5); + + foreach (CameraOverdrawMonitor monitor in _stats.Keys.ToArray()) + if (!Array.Exists(monitors, m => monitor)) + _stats.Remove(monitor); + + long gameViewArea = gameViewResolution.x * gameViewResolution.y; + float totalGlobalOverdrawRatio = 0f; + foreach (CameraOverdrawMonitor monitor in monitors) + { + using (new GUILayout.HorizontalScope()) + { + Camera cam = monitor.targetCamera; + GUILayout.Label($"{cam.name} {cam.pixelWidth}x{cam.pixelHeight}"); + + GUILayout.FlexibleSpace(); + + float localOverdrawRatio = monitor.overdrawRatio; + float globalOverdrawRatio = monitor.fragmentsCount / (float)gameViewArea; + totalGlobalOverdrawRatio += globalOverdrawRatio; + + if (!_stats.TryGetValue(monitor, out CameraOverdrawStats monitorStats)) + { + monitorStats = new CameraOverdrawStats(); + _stats.Add(monitor, monitorStats); + } + monitorStats.maxLocalOverdrawRatio = Math.Max(localOverdrawRatio, monitorStats.maxLocalOverdrawRatio); + monitorStats.maxGlobalOverdrawRatio = Math.Max(globalOverdrawRatio, monitorStats.maxGlobalOverdrawRatio); + + GUILayout.Label(FormatResult("Local: {0} / {1} \t Global: {2} / {3}", + localOverdrawRatio, monitorStats.maxLocalOverdrawRatio, globalOverdrawRatio, monitorStats.maxGlobalOverdrawRatio)); + } + } + + GUILayout.Space(5); + + float maxTotalGlobalOverdrawRatio = 0f; + foreach (CameraOverdrawStats stat in _stats.Values) + maxTotalGlobalOverdrawRatio += stat.maxGlobalOverdrawRatio; + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("TOTAL"); + GUILayout.FlexibleSpace(); + GUILayout.Label(FormatResult("Global: {0} / {1}", totalGlobalOverdrawRatio, maxTotalGlobalOverdrawRatio)); + } + } + else + { + GUILayout.Label("Available only in Play mode"); + } + + Repaint(); + } + + void ResetStats() + { + _stats.Clear(); + } + + string FormatResult(string format, params float[] args) + { + var stringArgs = new List(); + foreach (float arg in args) + stringArgs.Add($"{arg:N3}"); + return string.Format(format, stringArgs.ToArray()); + } + + static Vector2Int GetGameViewResolution() + { + var resString = UnityStats.screenRes.Split('x'); + return new Vector2Int(int.Parse(resString[0]), int.Parse(resString[1])); + } +} + +class CameraOverdrawStats +{ + public float maxLocalOverdrawRatio; + public float maxGlobalOverdrawRatio; +} diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/Editor/OverdrawToolWindow.cs.meta b/OverdrawMonitor/Assets/OverdrawMonitor/Editor/OverdrawMonitorWindow.cs.meta similarity index 100% rename from OverdrawMonitor/Assets/OverdrawMonitor/Editor/OverdrawToolWindow.cs.meta rename to OverdrawMonitor/Assets/OverdrawMonitor/Editor/OverdrawMonitorWindow.cs.meta diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/Editor/OverdrawToolWindow.cs b/OverdrawMonitor/Assets/OverdrawMonitor/Editor/OverdrawToolWindow.cs deleted file mode 100644 index e50b31e..0000000 --- a/OverdrawMonitor/Assets/OverdrawMonitor/Editor/OverdrawToolWindow.cs +++ /dev/null @@ -1,41 +0,0 @@ -using UnityEditor; -using UnityEngine; - -/// A simple tool to track the exact amount of overdraw in the game window. This tool -/// only shows the result, to check out how this is implemented. -public class OverdrawToolWindow : EditorWindow -{ - [MenuItem("Tools/Overdraw Tool")] - public static void ShowWindow() - { - var window = GetWindow(); - window.Focus(); - } - - public void OnGUI() - { - using (new GUILayout.HorizontalScope()) - { - if (GUILayout.Button("Start")) - { - OverdrawMonitor.Instance.StartMeasurement(); - OverdrawMonitor.Instance.ResetSampling(); - OverdrawMonitor.Instance.ResetExtreemes(); - } - - if (GUILayout.Button("End")) - { - OverdrawMonitor.Instance.Stop(); - } - } - - using (new GUILayout.HorizontalScope()) - { - GUILayout.Label("Max\n" + OverdrawMonitor.Instance.MaxOverdraw.ToString("0.000")); - GUILayout.FlexibleSpace(); - GUILayout.Label("Average\n" + OverdrawMonitor.Instance.AccumulatedAverageOverdraw.ToString("0.000")); - } - - Repaint(); - } -} \ No newline at end of file diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/Editor/Resources/DebugOverdrawInt.shader b/OverdrawMonitor/Assets/OverdrawMonitor/Editor/Resources/DebugOverdrawInt.shader deleted file mode 100644 index 706a77b..0000000 --- a/OverdrawMonitor/Assets/OverdrawMonitor/Editor/Resources/DebugOverdrawInt.shader +++ /dev/null @@ -1,52 +0,0 @@ -// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' - -Shader "Debug/OverdrawInt" -{ - Properties - { - [Header(Hardware settings)] - [Enum(UnityEngine.Rendering.CullMode)] HARDWARE_CullMode ("Cull faces", Float) = 2 - [Enum(On, 1, Off, 0)] HARDWARE_ZWrite ("Depth write", Float) = 1 - } - SubShader - { - Tags { "RenderType"="Opaque" "LightMode" = "ForwardBase" "Queue" = "Geometry+50"} - LOD 100 - - Pass - { - Cull [HARDWARE_CullMode] - ZWrite [HARDWARE_ZWrite] - Blend One One - - CGPROGRAM - #pragma vertex vert - #pragma fragment frag - #pragma exclude_renderers d3d11_9x - - struct appdata - { - float4 vertex : POSITION; - }; - - struct v2f - { - float4 vertex : SV_POSITION; - }; - - v2f vert (appdata v) - { - v2f o; - o.vertex = UnityObjectToClipPos(v.vertex); - return o; - } - - float4 frag (v2f i) : SV_Target - { - // 1 / 512 = 0.001953125; 1 / 1024 = 0.0009765625 - return 0.0009765625; - } - ENDCG - } - } -} diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/Editor/Resources/OverdrawParallelReduction.compute b/OverdrawMonitor/Assets/OverdrawMonitor/Editor/Resources/OverdrawParallelReduction.compute deleted file mode 100644 index c91dc0c..0000000 --- a/OverdrawMonitor/Assets/OverdrawMonitor/Editor/Resources/OverdrawParallelReduction.compute +++ /dev/null @@ -1,34 +0,0 @@ -#pragma kernel CSMain - -// Defines -#define SIZEX 32 -#define SIZEY 32 -#define GROUPSIZE SIZEX*SIZEY - -groupshared int accumulator[GROUPSIZE]; - -Texture2D Overdraw; -RWStructuredBuffer Output; - -[numthreads(SIZEX,SIZEY,1)] -void CSMain (uint3 gid : SV_GroupID, uint3 inp : SV_DispatchThreadID, uint gtidx : SV_GroupIndex) -{ - accumulator[gtidx] = (int)(Overdraw[inp.xy].x * 1024); - - // Wait for all - GroupMemoryBarrierWithGroupSync(); - - [unroll] - for (uint ix = GROUPSIZE >> 1; ix > 0; ix = ix >> 1) - { - if (gtidx < ix) - { - accumulator[gtidx] = (accumulator[gtidx] + accumulator[gtidx + ix]); - } - GroupMemoryBarrierWithGroupSync(); - } - - if (gtidx != 0) return; - - Output[gid.y * 128 + gid.x] = accumulator[0]; -} diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/OverdrawMonitor.cs b/OverdrawMonitor/Assets/OverdrawMonitor/OverdrawMonitor.cs deleted file mode 100644 index 288eb8f..0000000 --- a/OverdrawMonitor/Assets/OverdrawMonitor/OverdrawMonitor.cs +++ /dev/null @@ -1,278 +0,0 @@ -using System; -using UnityEngine; - -/// This is a singleton component that is responsible for measuring overdraw information -/// on the main camera. You shouldn't add this compoenent manually, but use the Instance getter to -/// access it. -/// -/// The measurements process is done in two passes. First a new camera is created that will render -/// the scene into a texture with high precission, the texture is called overdrawTexture. This texture -/// contains the information how many times a pixel has been overdrawn. After this step a compute shader -/// is used to add up all the pixels in the overdrawTexture and stores the information into this component. -/// -/// We say this tool measures exactly the amount of overdraw, but it does so only in certain cases. In other -/// cases the error margin is very small. This is because of the nature of the compute shaders. Compute -/// shaders should operate in batches in order for them to be efficient. In our case the compute shader -/// batch that sums up the results of the first render pass has a size of 32x32. This means that if the -/// pixel size of the camera is not divisible by 32, then the edge pixels that don't fit in range won't -/// be processed. But since we usually have huge render targets (in comparison to 32x32 pixel blocks) and -/// the error comes from the part of the image that is not important, this is acceptable. -/// -[ExecuteInEditMode] -public class OverdrawMonitor : MonoBehaviour -{ - private static OverdrawMonitor instance; - public static OverdrawMonitor Instance - { - get - { - if (instance == null) - { - instance = GameObject.FindObjectOfType(); - if (instance == null) - { - var go = new GameObject("OverdrawMonitor"); - instance = go.AddComponent(); - } - } - - return instance; - } - } - - private new Camera camera; - private RenderTexture overdrawTexture; - - private ComputeShader computeShader; - - private const int dataSize = 128 * 128; - private int[] inputData = new int[dataSize]; - private int[] resultData = new int[dataSize]; - private ComputeBuffer resultBuffer; - private Shader replacementShader; - - // ========= Results ======== - // Last measurement - /// The number of shaded fragments in the last frame. - public long TotalShadedFragments { get; private set; } - /// The overdraw ration in the last frame. - public float OverdrawRatio { get; private set; } - - // Sampled measurement - /// Number of shaded fragments in the measured time span. - public long IntervalShadedFragments { get; private set; } - /// The average number of shaded fragments in the measured time span. - public float IntervalAverageShadedFragments { get; private set; } - /// The average overdraw in the measured time span. - public float IntervalAverageOverdraw { get; private set; } - public float AccumulatedAverageOverdraw { get { return accumulatedIntervalOverdraw / intervalFrames; } } - - // Extreems - /// The maximum overdraw measured. - public float MaxOverdraw { get; private set; } - - private long accumulatedIntervalFragments; - private float accumulatedIntervalOverdraw; - private long intervalFrames; - - private float intervalTime = 0; - public float SampleTime = 1; - - /// An empty method that can be used to initialize the singleton. - public void Touch() { } - - #region Measurement magic - - public void Awake() - { -#if UNITY_EDITOR - // Since this emulation always turns on by default if on mobile platform. With the emulation - // turned on the tool won't work. - UnityEditor.EditorApplication.ExecuteMenuItem("Edit/Graphics Emulation/No Emulation"); - SubscribeToPlayStateChanged(); -#endif - - if (Application.isPlaying) DontDestroyOnLoad(gameObject); - gameObject.hideFlags = HideFlags.DontSave | HideFlags.HideInInspector; - - // Prepare the camera that is going to render the scene with the initial overdraw data. - replacementShader = Shader.Find("Debug/OverdrawInt"); - - camera = GetComponent(); - if (camera == null) camera = gameObject.AddComponent(); - camera.CopyFrom(Camera.main); - camera.SetReplacementShader(replacementShader, null); - - RecreateTexture(Camera.main); - RecreateComputeBuffer(); - - computeShader = Resources.Load("OverdrawParallelReduction"); - - for (int i = 0; i < inputData.Length; i++) inputData[i] = 0; - } - -#if UNITY_EDITOR - public void SubscribeToPlayStateChanged() - { - UnityEditor.EditorApplication.playmodeStateChanged -= OnPlayStateChanged; - UnityEditor.EditorApplication.playmodeStateChanged += OnPlayStateChanged; - } - - private static void OnPlayStateChanged() - { - if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode && UnityEditor.EditorApplication.isPlaying) - { - if (instance != null) instance.OnDisable(); - } - } -#endif - - private bool disabled = true; - - public void OnEnable() - { - disabled = false; - } - - public void OnDisable() - { - disabled = true; - OnDestroy(); - } - - public void LateUpdate() - { - if (disabled) return; - - Camera main = Camera.main; - camera.CopyFrom(main); - camera.clearFlags = CameraClearFlags.SolidColor; - camera.backgroundColor = Color.black; - camera.targetTexture = overdrawTexture; - camera.SetReplacementShader(replacementShader, null); - - transform.position = main.transform.position; - transform.rotation = main.transform.rotation; - - RecreateTexture(main); - - intervalTime += Time.deltaTime; - if (intervalTime > SampleTime) - { - IntervalShadedFragments = accumulatedIntervalFragments; - IntervalAverageShadedFragments = (float)accumulatedIntervalFragments / intervalFrames; - IntervalAverageOverdraw = (float)accumulatedIntervalOverdraw / intervalFrames; - - intervalTime -= SampleTime; - - accumulatedIntervalFragments = 0; - accumulatedIntervalOverdraw = 0; - intervalFrames = 0; - } - } - - /// Checks if the overdraw texture should be updated. This needs to happen if the main camera - /// configuration changes. - private void RecreateTexture(Camera main) - { - if (overdrawTexture == null) - { - overdrawTexture = new RenderTexture(camera.pixelWidth, camera.pixelHeight, 24, RenderTextureFormat.RFloat); - overdrawTexture.enableRandomWrite = true; - camera.targetTexture = overdrawTexture; - } - - if (main.pixelWidth != overdrawTexture.width || main.pixelHeight != overdrawTexture.height) - { - overdrawTexture.Release(); - overdrawTexture.width = main.pixelWidth; - overdrawTexture.height = main.pixelHeight; - } - } - - private void RecreateComputeBuffer() - { - if (resultBuffer != null) return; - resultBuffer = new ComputeBuffer(resultData.Length, 4); - } - - public void OnDestroy() - { - if (camera != null) - { - camera.targetTexture = null; - } - if (resultBuffer != null) resultBuffer.Release(); - } - - public void OnPostRender() - { - if (disabled) return; - - int kernel = computeShader.FindKernel("CSMain"); - - RecreateComputeBuffer(); - - // Setting up the data - resultBuffer.SetData(inputData); - computeShader.SetTexture(kernel, "Overdraw", overdrawTexture); - computeShader.SetBuffer(kernel, "Output", resultBuffer); - - int xGroups = (overdrawTexture.width / 32); - int yGroups = (overdrawTexture.height / 32); - - // Summing up the fragments - computeShader.Dispatch(kernel, xGroups, yGroups, 1); - resultBuffer.GetData(resultData); - - // Getting the results - TotalShadedFragments = 0; - for (int i = 0; i < resultData.Length; i++) - { - TotalShadedFragments += resultData[i]; - } - - OverdrawRatio = (float)TotalShadedFragments / (xGroups * 32 * yGroups * 32); - - accumulatedIntervalFragments += TotalShadedFragments; - accumulatedIntervalOverdraw += OverdrawRatio; - intervalFrames++; - - if (OverdrawRatio > MaxOverdraw) MaxOverdraw = OverdrawRatio; - } - - #endregion - #region Measurement control methods - - public void StartMeasurement() - { - enabled = true; - camera.enabled = true; - } - - public void Stop() - { - enabled = false; - camera.enabled = false; - } - - public void SetSampleTime(float time) - { - SampleTime = time; - } - - public void ResetSampling() - { - accumulatedIntervalOverdraw = 0; - accumulatedIntervalFragments = 0; - intervalTime = 0; - intervalFrames = 0; - } - - public void ResetExtreemes() - { - MaxOverdraw = 0; - } - - #endregion -} \ No newline at end of file diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/Editor/Resources.meta b/OverdrawMonitor/Assets/OverdrawMonitor/Resources.meta similarity index 100% rename from OverdrawMonitor/Assets/OverdrawMonitor/Editor/Resources.meta rename to OverdrawMonitor/Assets/OverdrawMonitor/Resources.meta diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/Resources/OverdrawParallelReduction.compute b/OverdrawMonitor/Assets/OverdrawMonitor/Resources/OverdrawParallelReduction.compute new file mode 100644 index 0000000..8e7b409 --- /dev/null +++ b/OverdrawMonitor/Assets/OverdrawMonitor/Resources/OverdrawParallelReduction.compute @@ -0,0 +1,34 @@ +#pragma kernel CSMain + +// Defines +#define SIZEX 32 +#define SIZEY 32 +#define GROUPSIZE SIZEX*SIZEY + +groupshared int accumulator[GROUPSIZE]; + +Texture2D Overdraw; +RWStructuredBuffer Output; +int BufferSizeX; + +[numthreads(SIZEX,SIZEY,1)] +void CSMain (uint3 gid : SV_GroupID, uint3 inp : SV_DispatchThreadID, uint gtidx : SV_GroupIndex) +{ + accumulator[gtidx] = (int)(Overdraw[inp.xy].x * GROUPSIZE); + + // Wait for all + GroupMemoryBarrierWithGroupSync(); + + [unroll] + for (uint ix = GROUPSIZE >> 1; ix > 0; ix = ix >> 1) + { + if (gtidx < ix) + accumulator[gtidx] = (accumulator[gtidx] + accumulator[gtidx + ix]); + GroupMemoryBarrierWithGroupSync(); + } + + if (gtidx != 0) + return; + + Output[gid.y * BufferSizeX + gid.x] = accumulator[0]; +} diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/Editor/Resources/OverdrawParallelReduction.compute.meta b/OverdrawMonitor/Assets/OverdrawMonitor/Resources/OverdrawParallelReduction.compute.meta similarity index 100% rename from OverdrawMonitor/Assets/OverdrawMonitor/Editor/Resources/OverdrawParallelReduction.compute.meta rename to OverdrawMonitor/Assets/OverdrawMonitor/Resources/OverdrawParallelReduction.compute.meta diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/Sources.meta b/OverdrawMonitor/Assets/OverdrawMonitor/Sources.meta new file mode 100644 index 0000000..751206a --- /dev/null +++ b/OverdrawMonitor/Assets/OverdrawMonitor/Sources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c065eaf008ca949a499e1ff3ca3d5e3b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/Sources/CameraOverdrawMonitor.cs b/OverdrawMonitor/Assets/OverdrawMonitor/Sources/CameraOverdrawMonitor.cs new file mode 100644 index 0000000..4084d80 --- /dev/null +++ b/OverdrawMonitor/Assets/OverdrawMonitor/Sources/CameraOverdrawMonitor.cs @@ -0,0 +1,126 @@ +// The measurements process is done in two passes. First a new camera is created that will render +// the scene into a texture with high precision, the texture is called overdrawTexture. This texture +// contains the information how many times a pixel has been overdrawn. After this step a compute shader +// is used to add up all the pixels in the overdrawTexture and stores the information into this component. +// +// We say this tool measures exactly the amount of overdraw, but it does so only in certain cases. In other +// cases the error margin is very small. This is because of the nature of the compute shaders. Compute +// shaders should operate in batches in order for them to be efficient. In our case the compute shader +// batch that sums up the results of the first render pass has a size of 32x32. This means that if the +// pixel size of the camera is not divisible by 32, then the edge pixels that don't fit in range won't +// be processed. But since we usually have huge render targets (in comparison to 32x32 pixel blocks) and +// the error comes from the part of the image that is not important, this is acceptable. + +using UnityEngine; + +public class CameraOverdrawMonitor : MonoBehaviour +{ + const int GroupDimension = 32; + const int DataDimension = 128; + const int DataSize = DataDimension * DataDimension; + + public Camera targetCamera => _targetCamera; + + public long fragmentsCount => isActiveAndEnabled ? _fragmentsCount : 0L; + public float overdrawRatio => isActiveAndEnabled ? _overdrawRatio : 0f; + + Camera _targetCamera; + RenderTexture _overdrawTexture; + ComputeShader _computeShader; + ComputeBuffer _resultBuffer; + Shader _replacementShader; + int[] _inputData; + int[] _resultData; + long _fragmentsCount; + float _overdrawRatio; + + void Awake() + { + // Compute shader + _computeShader = Resources.Load("OverdrawParallelReduction"); + + // Replacement shader + _replacementShader = Shader.Find("Debug/OverdrawInt"); + Shader.SetGlobalFloat("OverdrawFragmentWeight", 1f / (GroupDimension * GroupDimension)); + + _inputData = new int[DataSize]; + for (int i = 0; i < _inputData.Length; i++) + _inputData[i] = 0; + + _resultData = new int[DataSize]; + _resultBuffer = new ComputeBuffer(_resultData.Length, 4); + } + + void OnDestroy() + { + ReleaseTexture(); + _resultBuffer?.Release(); + } + + public void SetTargetCamera(Camera targetCamera) + { + _targetCamera = targetCamera; + } + + void ReleaseTexture() + { + if (_overdrawTexture != null) + { + _overdrawTexture.Release(); + _overdrawTexture = null; + } + } + + void LateUpdate() + { + if (_targetCamera == null) + return; + + // Save original params + CameraClearFlags originalClearFlags = _targetCamera.clearFlags; + Color originalClearColor = _targetCamera.backgroundColor; + RenderTexture originalTargetTexture = _targetCamera.targetTexture; + bool originalIsCameraEnabled = _targetCamera.enabled; + + // Recreate texture if needed + if (_overdrawTexture == null || _targetCamera.pixelWidth != _overdrawTexture.width || _targetCamera.pixelHeight != _overdrawTexture.height) + { + ReleaseTexture(); + _overdrawTexture = new RenderTexture(_targetCamera.pixelWidth, _targetCamera.pixelHeight, 24, RenderTextureFormat.RFloat); + } + + // Set replacement params + _targetCamera.clearFlags = CameraClearFlags.SolidColor; + _targetCamera.backgroundColor = Color.clear; + _targetCamera.targetTexture = _overdrawTexture; + _targetCamera.enabled = false; + + // Render + _targetCamera.RenderWithShader(_replacementShader, null); + + // Compute + _resultBuffer.SetData(_inputData); + int kernel = _computeShader.FindKernel("CSMain"); + _computeShader.SetInt("BufferSizeX", DataDimension); + _computeShader.SetTexture(kernel, "Overdraw", _overdrawTexture); + _computeShader.SetBuffer(kernel, "Output", _resultBuffer); + + // Summing up the fragments + int xGroups = _overdrawTexture.width / GroupDimension; + int yGroups = _overdrawTexture.height / GroupDimension; + _computeShader.Dispatch(kernel, xGroups, yGroups, 1); + _resultBuffer.GetData(_resultData); + + // Results + _fragmentsCount = 0; + foreach (int res in _resultData) + _fragmentsCount += res; + _overdrawRatio = fragmentsCount / ((float)_overdrawTexture.width * _overdrawTexture.height); + + // Restore original params + _targetCamera.targetTexture = originalTargetTexture; + _targetCamera.clearFlags = originalClearFlags; + _targetCamera.backgroundColor = originalClearColor; + _targetCamera.enabled = originalIsCameraEnabled; + } +} diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/OverdrawMonitor.cs.meta b/OverdrawMonitor/Assets/OverdrawMonitor/Sources/CameraOverdrawMonitor.cs.meta similarity index 100% rename from OverdrawMonitor/Assets/OverdrawMonitor/OverdrawMonitor.cs.meta rename to OverdrawMonitor/Assets/OverdrawMonitor/Sources/CameraOverdrawMonitor.cs.meta diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/Sources/DebugOverdrawInt.shader b/OverdrawMonitor/Assets/OverdrawMonitor/Sources/DebugOverdrawInt.shader new file mode 100644 index 0000000..4e6b657 --- /dev/null +++ b/OverdrawMonitor/Assets/OverdrawMonitor/Sources/DebugOverdrawInt.shader @@ -0,0 +1,51 @@ +Shader "Debug/OverdrawInt" +{ + Properties + { + [Header(Hardware settings)] + [Enum(UnityEngine.Rendering.CullMode)] HARDWARE_CullMode ("Cull faces", Float) = 2 + [Enum(On, 1, Off, 0)] HARDWARE_ZWrite ("Depth write", Float) = 1 + } + SubShader + { + Tags { "RenderType"="Opaque" "LightMode" = "ForwardBase" "Queue" = "Geometry+50"} + LOD 100 + + Pass + { + Cull [HARDWARE_CullMode] + ZWrite [HARDWARE_ZWrite] + Blend One One + + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma exclude_renderers d3d11_9x + + struct appdata + { + float4 vertex : POSITION; + }; + + struct v2f + { + float4 vertex : SV_POSITION; + }; + + v2f vert (appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + return o; + } + + uniform float OverdrawFragmentWeight; + + float4 frag (v2f i) : SV_Target + { + return OverdrawFragmentWeight; + } + ENDCG + } + } +} diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/Editor/Resources/DebugOverdrawInt.shader.meta b/OverdrawMonitor/Assets/OverdrawMonitor/Sources/DebugOverdrawInt.shader.meta similarity index 100% rename from OverdrawMonitor/Assets/OverdrawMonitor/Editor/Resources/DebugOverdrawInt.shader.meta rename to OverdrawMonitor/Assets/OverdrawMonitor/Sources/DebugOverdrawInt.shader.meta diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/Sources/OverdrawMonitor.asmdef b/OverdrawMonitor/Assets/OverdrawMonitor/Sources/OverdrawMonitor.asmdef new file mode 100644 index 0000000..753dd14 --- /dev/null +++ b/OverdrawMonitor/Assets/OverdrawMonitor/Sources/OverdrawMonitor.asmdef @@ -0,0 +1,3 @@ +{ + "name": "OverdrawMonitor" +} diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/Sources/OverdrawMonitor.asmdef.meta b/OverdrawMonitor/Assets/OverdrawMonitor/Sources/OverdrawMonitor.asmdef.meta new file mode 100644 index 0000000..187e384 --- /dev/null +++ b/OverdrawMonitor/Assets/OverdrawMonitor/Sources/OverdrawMonitor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7594097e076d14122ac321968bbd88c0 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/package.json b/OverdrawMonitor/Assets/OverdrawMonitor/package.json new file mode 100644 index 0000000..8a7ab57 --- /dev/null +++ b/OverdrawMonitor/Assets/OverdrawMonitor/package.json @@ -0,0 +1,6 @@ +{ + "name": "ken48.overdrawmonitor", + "displayName": "Overdraw Monitor", + "version": "1.0.0", + "unity": "2019.1" +} \ No newline at end of file diff --git a/OverdrawMonitor/Assets/OverdrawMonitor/package.json.meta b/OverdrawMonitor/Assets/OverdrawMonitor/package.json.meta new file mode 100644 index 0000000..fc6b7a1 --- /dev/null +++ b/OverdrawMonitor/Assets/OverdrawMonitor/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8e2e1b17e45fe492cb35960bdbf4abd4 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OverdrawMonitor/OverdrawMonitor.sln b/OverdrawMonitor/OverdrawMonitor.sln index 021c85b..417e796 100644 --- a/OverdrawMonitor/OverdrawMonitor.sln +++ b/OverdrawMonitor/OverdrawMonitor.sln @@ -1,26 +1,26 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2017 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OverdrawMonitor", "OverdrawMonitor.csproj", "{A52161F3-317E-5525-A6BD-A25BBAEAE865}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OverdrawMonitor.Editor", "OverdrawMonitor.Editor.csproj", "{2E1B6611-394C-ECEC-9DC7-06E12D394BC9}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A52161F3-317E-5525-A6BD-A25BBAEAE865}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A52161F3-317E-5525-A6BD-A25BBAEAE865}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A52161F3-317E-5525-A6BD-A25BBAEAE865}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A52161F3-317E-5525-A6BD-A25BBAEAE865}.Release|Any CPU.Build.0 = Release|Any CPU - {2E1B6611-394C-ECEC-9DC7-06E12D394BC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2E1B6611-394C-ECEC-9DC7-06E12D394BC9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E1B6611-394C-ECEC-9DC7-06E12D394BC9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2E1B6611-394C-ECEC-9DC7-06E12D394BC9}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp", "Assembly-CSharp.csproj", "{EC5ED580-E73A-34AE-BD42-C20D925FCF24}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp-Editor", "Assembly-CSharp-Editor.csproj", "{47896C88-30AF-3D6A-D336-EE6A9882AF0B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EC5ED580-E73A-34AE-BD42-C20D925FCF24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC5ED580-E73A-34AE-BD42-C20D925FCF24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC5ED580-E73A-34AE-BD42-C20D925FCF24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC5ED580-E73A-34AE-BD42-C20D925FCF24}.Release|Any CPU.Build.0 = Release|Any CPU + {47896C88-30AF-3D6A-D336-EE6A9882AF0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47896C88-30AF-3D6A-D336-EE6A9882AF0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47896C88-30AF-3D6A-D336-EE6A9882AF0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47896C88-30AF-3D6A-D336-EE6A9882AF0B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/OverdrawMonitor/Packages/manifest.json b/OverdrawMonitor/Packages/manifest.json new file mode 100644 index 0000000..dbc7c7f --- /dev/null +++ b/OverdrawMonitor/Packages/manifest.json @@ -0,0 +1,21 @@ +{ + "dependencies": { + "com.unity.2d.sprite": "1.0.0", + "com.unity.2d.tilemap": "1.0.0", + "com.unity.ext.nunit": "1.0.0", + "com.unity.ide.rider": "1.1.0", + "com.unity.ide.vscode": "1.1.0", + "com.unity.package-manager-ui": "2.2.0", + "com.unity.test-framework": "1.0.13", + "com.unity.ugui": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.uielements": "1.0.0" + } +} diff --git a/OverdrawMonitor/ProjectSettings/EditorSettings.asset b/OverdrawMonitor/ProjectSettings/EditorSettings.asset index 094eabc..70cbe1e 100644 Binary files a/OverdrawMonitor/ProjectSettings/EditorSettings.asset and b/OverdrawMonitor/ProjectSettings/EditorSettings.asset differ diff --git a/OverdrawMonitor/ProjectSettings/PresetManager.asset b/OverdrawMonitor/ProjectSettings/PresetManager.asset new file mode 100644 index 0000000..bbadbda Binary files /dev/null and b/OverdrawMonitor/ProjectSettings/PresetManager.asset differ diff --git a/OverdrawMonitor/ProjectSettings/ProjectSettings.asset b/OverdrawMonitor/ProjectSettings/ProjectSettings.asset index 781c93b..d1692dd 100644 Binary files a/OverdrawMonitor/ProjectSettings/ProjectSettings.asset and b/OverdrawMonitor/ProjectSettings/ProjectSettings.asset differ diff --git a/OverdrawMonitor/ProjectSettings/ProjectVersion.txt b/OverdrawMonitor/ProjectSettings/ProjectVersion.txt index ca09a3d..dbb1604 100644 --- a/OverdrawMonitor/ProjectSettings/ProjectVersion.txt +++ b/OverdrawMonitor/ProjectSettings/ProjectVersion.txt @@ -1 +1,2 @@ -m_EditorVersion: 5.6.0f3 +m_EditorVersion: 2019.2.4f1 +m_EditorVersionWithRevision: 2019.2.4f1 (c63b2af89a85) diff --git a/OverdrawMonitor/ProjectSettings/VFXManager.asset b/OverdrawMonitor/ProjectSettings/VFXManager.asset new file mode 100644 index 0000000..4b77776 Binary files /dev/null and b/OverdrawMonitor/ProjectSettings/VFXManager.asset differ diff --git a/OverdrawMonitor/ProjectSettings/XRSettings.asset b/OverdrawMonitor/ProjectSettings/XRSettings.asset new file mode 100644 index 0000000..482590c --- /dev/null +++ b/OverdrawMonitor/ProjectSettings/XRSettings.asset @@ -0,0 +1,10 @@ +{ + "m_SettingKeys": [ + "VR Device Disabled", + "VR Device User Alert" + ], + "m_SettingValues": [ + "False", + "False" + ] +} \ No newline at end of file