using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEditor;

namespace UnityEngine.Rendering
{
    /// <summary>
    /// Modes for Debugging Probes
    /// </summary>
    [GenerateHLSL]
    public enum DebugProbeShadingMode
    {
        /// <summary>
        /// Based on Spherical Harmonics
        /// </summary>
        SH,
        /// <summary>
        /// Based on Spherical Harmonics first band only (ambient)
        /// </summary>
        SHL0,
        /// <summary>
        /// Based on Spherical Harmonics band zero and one only
        /// </summary>
        SHL0L1,
        /// <summary>
        /// Based on validity
        /// </summary>
        Validity,
        /// <summary>
        /// Based on validity over a dilation threshold
        /// </summary>
        ValidityOverDilationThreshold,
        /// <summary>
        /// Based on rendering layer masks
        /// </summary>
        RenderingLayerMasks,
        /// <summary>
        /// Show in red probes that have been made invalid by adjustment volumes. Important to note that this debug view will only show result for volumes still present in the scene.
        /// </summary>
        InvalidatedByAdjustmentVolumes,
        /// <summary>
        /// Based on size
        /// </summary>
        Size,
        /// <summary>
        /// Based on spherical harmonics sky occlusion
        /// </summary>
        SkyOcclusionSH,
        /// <summary>
        /// Based on shading direction
        /// </summary>
        SkyDirection,
        /// <summary>
        /// Occlusion values per probe
        /// </summary>
        ProbeOcclusion,
    }

    enum ProbeSamplingDebugUpdate
    {
        Never,
        Once,
        Always
    }

    class ProbeSamplingDebugData
    {
        public ProbeSamplingDebugUpdate update = ProbeSamplingDebugUpdate.Never; // When compute buffer should be updated
        public Vector2 coordinates = new Vector2(0.5f, 0.5f);
        public bool forceScreenCenterCoordinates = false; // use screen center instead of mouse position
        public Camera camera = null; // useful in editor when multiple scene tabs are opened
        public bool shortcutPressed = false;
        public GraphicsBuffer positionNormalBuffer; // buffer storing position and normal
    }

    class ProbeVolumeDebug : IDebugData
    {
        public bool drawProbes;
        public bool drawBricks;
        public bool drawCells;
        public bool realtimeSubdivision;
        public int subdivisionCellUpdatePerFrame = 4;
        public float subdivisionDelayInSeconds = 1;
        public DebugProbeShadingMode probeShading;
        public float probeSize = 0.3f;
        public float subdivisionViewCullingDistance = 500.0f;
        public float probeCullingDistance = 200.0f;
        public int maxSubdivToVisualize = ProbeBrickIndex.kMaxSubdivisionLevels;
        public int minSubdivToVisualize = 0;
        public float exposureCompensation;
        public bool drawProbeSamplingDebug = false;
        public float probeSamplingDebugSize = 0.3f;
        public bool debugWithSamplingNoise = false;
        public uint samplingRenderingLayer;
        public bool drawVirtualOffsetPush;
        public float offsetSize = 0.025f;
        public bool freezeStreaming;
        public bool displayCellStreamingScore;
        public bool displayIndexFragmentation;
        public int otherStateIndex = 0;
        public bool verboseStreamingLog;
        public bool debugStreaming = false;
        public bool autoDrawProbes = true;
        public bool isolationProbeDebug = true;
        public byte visibleLayers;

        // NOTE: We should get that from the camera directly instead of storing it as static
        // But we can't access the volume parameters as they are specific to the RP
        public static Vector3 currentOffset;

        static internal int s_ActiveAdjustmentVolumes = 0;

        public ProbeVolumeDebug()
        {
            Init();
        }

        void Init()
        {
            drawProbes = false;
            drawBricks = false;
            drawCells = false;
            realtimeSubdivision = false;
            subdivisionCellUpdatePerFrame = 4;
            subdivisionDelayInSeconds = 1;
            probeShading = DebugProbeShadingMode.SH;
            probeSize = 0.3f;
            subdivisionViewCullingDistance = 500.0f;
            probeCullingDistance = 200.0f;
            maxSubdivToVisualize = ProbeBrickIndex.kMaxSubdivisionLevels;
            minSubdivToVisualize = 0;
            exposureCompensation = 0.0f;
            drawProbeSamplingDebug = false;
            probeSamplingDebugSize = 0.3f;
            drawVirtualOffsetPush = false;
            offsetSize = 0.025f;
            freezeStreaming = false;
            displayCellStreamingScore = false;
            displayIndexFragmentation = false;
            otherStateIndex = 0;
            autoDrawProbes = true;
            isolationProbeDebug = true;
            visibleLayers = 0xFF;
        }

        public Action GetReset() => () => Init();
    }


#if UNITY_EDITOR
    [UnityEditor.InitializeOnLoad]
#endif
    internal class ProbeVolumeDebugColorPreferences
    {
        internal static Func<Color> GetDetailSubdivisionColor;
        internal static Func<Color> GetMediumSubdivisionColor;
        internal static Func<Color> GetLowSubdivisionColor;
        internal static Func<Color> GetVeryLowSubdivisionColor;
        internal static Func<Color> GetSparseSubdivisionColor;
        internal static Func<Color> GetSparsestSubdivisionColor;

        internal static Color s_DetailSubdivision   = new Color32(135, 35,  255, 255);
        internal static Color s_MediumSubdivision   = new Color32(54,  208, 228, 255);
        internal static Color s_LowSubdivision      = new Color32(255, 100, 45,  255);
        internal static Color s_VeryLowSubdivision  = new Color32(52,  87,  255, 255);
        internal static Color s_SparseSubdivision   = new Color32(255, 71,  97,  255);
        internal static Color s_SparsestSubdivision = new Color32(200, 227, 39,  255);

        static ProbeVolumeDebugColorPreferences()
        {
#if UNITY_EDITOR
            GetDetailSubdivisionColor = CoreRenderPipelinePreferences.RegisterPreferenceColor("Adaptive Probe Volumes/Level 0 Subdivision",    s_DetailSubdivision);
            GetMediumSubdivisionColor = CoreRenderPipelinePreferences.RegisterPreferenceColor("Adaptive Probe Volumes/Level 1 Subdivision", s_MediumSubdivision);
            GetLowSubdivisionColor = CoreRenderPipelinePreferences.RegisterPreferenceColor("Adaptive Probe Volumes/Level 2 Subdivision", s_LowSubdivision);
            GetVeryLowSubdivisionColor = CoreRenderPipelinePreferences.RegisterPreferenceColor("Adaptive Probe Volumes/Level 3 Subdivision", s_VeryLowSubdivision);
            GetSparseSubdivisionColor = CoreRenderPipelinePreferences.RegisterPreferenceColor("Adaptive Probe Volumes/Level 4 Subdivision", s_SparseSubdivision);
            GetSparsestSubdivisionColor = CoreRenderPipelinePreferences.RegisterPreferenceColor("Adaptive Probe Volumes/Level 5 Subdivision", s_SparsestSubdivision);
#endif
        }

    }
    public partial class ProbeReferenceVolume
    {
        internal class CellInstancedDebugProbes
        {
            public List<Matrix4x4[]> probeBuffers;
            public List<Matrix4x4[]> offsetBuffers;
            public List<MaterialPropertyBlock> props;
        }

        const int kProbesPerBatch = 511;

        /// <summary>Name of debug panel for Probe Volume</summary>
        public static readonly string k_DebugPanelName = "Probe Volumes";

        internal ProbeVolumeDebug probeVolumeDebug { get; } = new ProbeVolumeDebug();

        /// <summary>Colors that can be used for debug visualization of the brick structure subdivision.</summary>
        public Color[] subdivisionDebugColors { get; } = new Color[ProbeBrickIndex.kMaxSubdivisionLevels];

        Mesh m_DebugMesh;
        Mesh debugMesh {
            get
            {
                if (m_DebugMesh == null)
                {
                    m_DebugMesh = DebugShapes.instance.BuildCustomSphereMesh(0.5f, 9, 8); // (longSubdiv + 1) * latSubdiv + 2 = 82
                    m_DebugMesh.bounds = new Bounds(Vector3.zero, Vector3.one * 9999999.9f); // dirty way of disabling culling (objects spawned at (0.0, 0.0, 0.0) but vertices moved in vertex shader)
                }
                return m_DebugMesh;
            }
        }

        DebugUI.Widget[] m_DebugItems;
        Material m_DebugMaterial;

        // Sample position debug
        Mesh m_DebugProbeSamplingMesh; // mesh with 8 quads, 1 arrow and 2 locators
        Material m_ProbeSamplingDebugMaterial; // Used to draw probe sampling information (quad with weight, arrow, locator)
        Material m_ProbeSamplingDebugMaterial02; // Used to draw probe sampling information (shaded probes)

        Texture m_DisplayNumbersTexture;

        internal static ProbeSamplingDebugData probeSamplingDebugData = new ProbeSamplingDebugData();

        Mesh m_DebugOffsetMesh;
        Material m_DebugOffsetMaterial;
        Material m_DebugFragmentationMaterial;
        Plane[] m_DebugFrustumPlanes = new Plane[6];

        // Scenario blending debug data
        GUIContent[] m_DebugScenarioNames = new GUIContent[0];
        int[] m_DebugScenarioValues = new int[0];
        string m_DebugActiveSceneGUID, m_DebugActiveScenario;
        DebugUI.EnumField m_DebugScenarioField;

        // Field used for the realtime subdivision preview
        internal Dictionary<Bounds, ProbeBrickIndex.Brick[]> realtimeSubdivisionInfo = new ();

        bool m_MaxSubdivVisualizedIsMaxAvailable = false;

        /// <summary>
        /// Obsolete. Render Probe Volume related debug
        /// </summary>
        /// <param name="camera">The <see cref="Camera"/></param>
        /// <param name="exposureTexture">Texture containing the exposure value for this frame.</param>
        [Obsolete("Use the other override to support sampling offset in debug modes.")]
        public void RenderDebug(Camera camera, Texture exposureTexture)
        {
            RenderDebug(camera, null, exposureTexture);
        }
        
        /// <summary>
        /// Render Probe Volume related debug
        /// </summary>
        /// <param name="camera">The <see cref="Camera"/></param>
        /// <param name="options">Options coming from the volume stack.</param>
        /// <param name="exposureTexture">Texture containing the exposure value for this frame.</param>
        public void RenderDebug(Camera camera, ProbeVolumesOptions options, Texture exposureTexture)
        {
            if (camera.cameraType != CameraType.Reflection && camera.cameraType != CameraType.Preview)
            {
                if (options != null)
                    ProbeVolumeDebug.currentOffset = options.worldOffset.value;

                DrawProbeDebug(camera, exposureTexture);
            }
        }

        /// <summary>
        /// Checks if APV sampling debug is enabled
        /// </summary>
        /// <returns>True if APV sampling debug is enabled</returns>
        public bool IsProbeSamplingDebugEnabled()
        {
            return probeSamplingDebugData.update != ProbeSamplingDebugUpdate.Never;
        }

        /// <summary>
        /// Returns the resources used for APV probe sampling debug mode
        /// </summary>
        /// <param name="camera">The camera for which to evaluate the debug mode</param>
        /// <param name="resultBuffer">The buffer that should be filled with position and normal</param>
        /// <param name="coords">The screen space coords to sample the position and normal</param>
        /// <returns>True if the pipeline should write position and normal at coords in resultBuffer</returns>
        public bool GetProbeSamplingDebugResources(Camera camera, out GraphicsBuffer resultBuffer, out Vector2 coords)
        {
            resultBuffer = probeSamplingDebugData.positionNormalBuffer;
            coords = probeSamplingDebugData.coordinates;

            if (!probeVolumeDebug.drawProbeSamplingDebug)
                return false;

#if UNITY_EDITOR
            if (probeSamplingDebugData.camera != camera)
                return false;
#endif

            if (probeSamplingDebugData.update == ProbeSamplingDebugUpdate.Never)
                return false;

            if (probeSamplingDebugData.update == ProbeSamplingDebugUpdate.Once)
            {
                probeSamplingDebugData.update = ProbeSamplingDebugUpdate.Never;
                probeSamplingDebugData.forceScreenCenterCoordinates = false;
            }

            return true;
        }

#if UNITY_EDITOR
        static void SceneGUI(SceneView sceneView)
        {
            // APV debug needs to detect user keyboard and mouse position to update ProbeSamplingPositionDebug
            Event e = Event.current;

            if (e.control && !ProbeReferenceVolume.probeSamplingDebugData.shortcutPressed)
                ProbeReferenceVolume.probeSamplingDebugData.update = ProbeSamplingDebugUpdate.Always;

            if (!e.control && ProbeReferenceVolume.probeSamplingDebugData.shortcutPressed)
                ProbeReferenceVolume.probeSamplingDebugData.update = ProbeSamplingDebugUpdate.Never;

            ProbeReferenceVolume.probeSamplingDebugData.shortcutPressed = e.control;

            if (e.clickCount > 0 && e.button == 0)
            {
                if (ProbeReferenceVolume.probeSamplingDebugData.shortcutPressed)
                    ProbeReferenceVolume.probeSamplingDebugData.update = ProbeSamplingDebugUpdate.Once;
                else
                    ProbeReferenceVolume.probeSamplingDebugData.update = ProbeSamplingDebugUpdate.Never;
            }

            if (ProbeReferenceVolume.probeSamplingDebugData.update == ProbeSamplingDebugUpdate.Never)
                return;

            Vector2 screenCoordinates;

            if (ProbeReferenceVolume.probeSamplingDebugData.forceScreenCenterCoordinates)
                screenCoordinates = new Vector2(sceneView.camera.scaledPixelWidth / 2.0f, sceneView.camera.scaledPixelHeight / 2.0f);
            else
                screenCoordinates = HandleUtility.GUIPointToScreenPixelCoordinate(e.mousePosition);

            if (screenCoordinates.x < 0 || screenCoordinates.x > sceneView.camera.scaledPixelWidth || screenCoordinates.y < 0 || screenCoordinates.y > sceneView.camera.scaledPixelHeight)
                return;

            ProbeReferenceVolume.probeSamplingDebugData.camera = sceneView.camera;
            ProbeReferenceVolume.probeSamplingDebugData.coordinates = screenCoordinates;

            if (e.type != EventType.Repaint && e.type != EventType.Layout)
            {
                var go = HandleUtility.PickGameObject(e.mousePosition, false);
                if (go != null && go.TryGetComponent<MeshRenderer>(out var renderer))
                    instance.probeVolumeDebug.samplingRenderingLayer = renderer.renderingLayerMask;
            }

            SceneView.currentDrawingSceneView.Repaint(); // useful when 'Always Refresh' is not toggled
        }
#endif

        bool TryCreateDebugRenderData()
        {
            if (!GraphicsSettings.TryGetRenderPipelineSettings<ProbeVolumeDebugResources>(out var debugResources))
                return false;

#if !UNITY_EDITOR // On non editor builds, we need to check if the standalone build contains debug shaders
            if (GraphicsSettings.TryGetRenderPipelineSettings<ShaderStrippingSetting>(out var shaderStrippingSetting) && shaderStrippingSetting.stripRuntimeDebugShaders)
                return false;
#endif
            m_DebugMaterial = CoreUtils.CreateEngineMaterial(debugResources.probeVolumeDebugShader);
            m_DebugMaterial.enableInstancing = true;

            // Probe Sampling Debug Mesh : useful to show additional information concerning probe sampling for a specific fragment
            // - Arrow : Debug fragment position and normal
            // - Locator : Debug sampling position
            // - 8 Quads : Debug probes weights
            m_DebugProbeSamplingMesh = debugResources.probeSamplingDebugMesh;
            m_DebugProbeSamplingMesh.bounds = new Bounds(Vector3.zero, Vector3.one * 9999999.9f); // dirty way of disabling culling (objects spawned at (0.0, 0.0, 0.0) but vertices moved in vertex shader)
            m_ProbeSamplingDebugMaterial = CoreUtils.CreateEngineMaterial(debugResources.probeVolumeSamplingDebugShader);
            m_ProbeSamplingDebugMaterial02 = CoreUtils.CreateEngineMaterial(debugResources.probeVolumeDebugShader);
            m_ProbeSamplingDebugMaterial02.enableInstancing = true;

            probeSamplingDebugData.positionNormalBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 2, System.Runtime.InteropServices.Marshal.SizeOf(typeof(Vector4)));

            m_DisplayNumbersTexture = debugResources.numbersDisplayTex;

            m_DebugOffsetMesh = Resources.GetBuiltinResource<Mesh>("pyramid.fbx");
            m_DebugOffsetMesh.bounds = new Bounds(Vector3.zero, Vector3.one * 9999999.9f); // dirty way of disabling culling (objects spawned at (0.0, 0.0, 0.0) but vertices moved in vertex shader)
            m_DebugOffsetMaterial = CoreUtils.CreateEngineMaterial(debugResources.probeVolumeOffsetDebugShader);
            m_DebugOffsetMaterial.enableInstancing = true;
            m_DebugFragmentationMaterial = CoreUtils.CreateEngineMaterial(debugResources.probeVolumeFragmentationDebugShader);

            // Hard-coded colors for now.
            Debug.Assert(ProbeBrickIndex.kMaxSubdivisionLevels == 7); // Update list if this changes.

            subdivisionDebugColors[0] = ProbeVolumeDebugColorPreferences.s_DetailSubdivision;
            subdivisionDebugColors[1] = ProbeVolumeDebugColorPreferences.s_MediumSubdivision;
            subdivisionDebugColors[2] = ProbeVolumeDebugColorPreferences.s_LowSubdivision;
            subdivisionDebugColors[3] = ProbeVolumeDebugColorPreferences.s_VeryLowSubdivision;
            subdivisionDebugColors[4] = ProbeVolumeDebugColorPreferences.s_SparseSubdivision;
            subdivisionDebugColors[5] = ProbeVolumeDebugColorPreferences.s_SparsestSubdivision;
            subdivisionDebugColors[6] = ProbeVolumeDebugColorPreferences.s_DetailSubdivision;

            return true;
        }

        void InitializeDebug()
        {
#if UNITY_EDITOR
            SceneView.duringSceneGui += SceneGUI; // Used to get click and keyboard event on scene view for Probe Sampling Debug
#endif
            if (TryCreateDebugRenderData())
                RegisterDebug();

#if UNITY_EDITOR
            UnityEditor.Lightmapping.lightingDataCleared += OnClearLightingdata;
#endif
        }

        void CleanupDebug()
        {
            UnregisterDebug(true);
            CoreUtils.Destroy(m_DebugMaterial);
            CoreUtils.Destroy(m_ProbeSamplingDebugMaterial);
            CoreUtils.Destroy(m_ProbeSamplingDebugMaterial02);
            CoreUtils.Destroy(m_DebugOffsetMaterial);
            CoreUtils.Destroy(m_DebugFragmentationMaterial);
            CoreUtils.SafeRelease(probeSamplingDebugData?.positionNormalBuffer);

#if UNITY_EDITOR
            UnityEditor.Lightmapping.lightingDataCleared -= OnClearLightingdata;
            SceneView.duringSceneGui -= SceneGUI;
#endif
        }

        void DebugCellIndexChanged<T>(DebugUI.Field<T> field, T value)
        {
            ClearDebugData();
        }

        void RegisterDebug()
        {
            void RefreshDebug<T>(DebugUI.Field<T> field, T value)
            {
                UnregisterDebug(false);
                RegisterDebug();
            }

            const float kProbeSizeMin = 0.05f, kProbeSizeMax = 10.0f;
            const float kOffsetSizeMin = 0.001f, kOffsetSizeMax = 0.1f;

            var widgetList = new List<DebugUI.Widget>();

            widgetList.Add(new DebugUI.RuntimeDebugShadersMessageBox());

            var subdivContainer = new DebugUI.Container()
            {
                displayName = "Subdivision Visualization",
                isHiddenCallback = () =>
                {
#if UNITY_EDITOR
                    return false;
#else
                    return false; // Cells / Bricks visualization is not implemented in a runtime compatible way atm.
#endif
                }
            };
            subdivContainer.children.Add(new DebugUI.BoolField
            {
                displayName = "Display Cells",
                tooltip = "Draw Cells used for loading and streaming.",
                getter = () => probeVolumeDebug.drawCells,
                setter = value => probeVolumeDebug.drawCells = value,
                onValueChanged = RefreshDebug
            });
            subdivContainer.children.Add(new DebugUI.BoolField
            {
                displayName = "Display Bricks",
                tooltip = "Display Subdivision bricks.",
                getter = () => probeVolumeDebug.drawBricks,
                setter = value => probeVolumeDebug.drawBricks = value,
                onValueChanged = RefreshDebug
            });

            subdivContainer.children.Add(new DebugUI.FloatField { displayName = "Debug Draw Distance", tooltip = "How far from the Scene Camera to draw debug visualization for Cells and Bricks. Large distances can impact Editor performance.", getter = () => probeVolumeDebug.subdivisionViewCullingDistance, setter = value => probeVolumeDebug.subdivisionViewCullingDistance = value, min = () => 0.0f });
            widgetList.Add(subdivContainer);

#if UNITY_EDITOR
            var subdivPreviewContainer = new DebugUI.Container()
            {
                displayName = "Subdivision Preview",
                isHiddenCallback = () =>
                {
                    return (!probeVolumeDebug.drawCells && !probeVolumeDebug.drawBricks);
                }
            };
            subdivPreviewContainer.children.Add(new DebugUI.BoolField
            {
                displayName = "Live Updates",
                tooltip = "Enable a preview of Adaptive Probe Volumes data in the Scene without baking. Can impact Editor performance.",
                getter = () => probeVolumeDebug.realtimeSubdivision,
                setter = value => probeVolumeDebug.realtimeSubdivision = value,
            });

            var realtimeSubdivisonChildContainer = new DebugUI.Container()
            {
                isHiddenCallback = () => !probeVolumeDebug.realtimeSubdivision
            };
            realtimeSubdivisonChildContainer.children.Add(new DebugUI.IntField { displayName = "Cell Updates Per Frame", tooltip = "The number of Cells, bricks, and probe positions updated per frame. Higher numbers can impact Editor performance.", getter = () => probeVolumeDebug.subdivisionCellUpdatePerFrame, setter = value => probeVolumeDebug.subdivisionCellUpdatePerFrame = value, min = () => 1, max = () => 100 });
            realtimeSubdivisonChildContainer.children.Add(new DebugUI.FloatField { displayName = "Update Frequency", tooltip = "Delay in seconds between updates to Cell, Brick, and Probe positions if Live Subdivision Preview is enabled.", getter = () => probeVolumeDebug.subdivisionDelayInSeconds, setter = value => probeVolumeDebug.subdivisionDelayInSeconds = value, min = () => 0.1f, max = () => 10 });
            subdivPreviewContainer.children.Add(realtimeSubdivisonChildContainer);
            widgetList.Add(subdivPreviewContainer);
#endif

            widgetList.Add(new DebugUI.RuntimeDebugShadersMessageBox());

            var probeContainer = new DebugUI.Container()
            {
                displayName = "Probe Visualization"
            };

            probeContainer.children.Add(new DebugUI.BoolField { displayName = "Display Probes", tooltip = "Render the debug view showing probe positions. Use the shading mode to determine which type of lighting data to visualize.", getter = () => probeVolumeDebug.drawProbes, setter = value => probeVolumeDebug.drawProbes = value, onValueChanged = RefreshDebug });
            {
                var probeContainerChildren = new DebugUI.Container()
                {
                    isHiddenCallback = () => !probeVolumeDebug.drawProbes
                };

                probeContainerChildren.children.Add(new DebugUI.EnumField
                {
                    displayName = "Probe Shading Mode",
                    tooltip = "Choose which lighting data to show in the probe debug visualization.",
                    getter = () => (int)probeVolumeDebug.probeShading,
                    setter = value => probeVolumeDebug.probeShading = (DebugProbeShadingMode)value,
                    autoEnum = typeof(DebugProbeShadingMode),
                    getIndex = () => (int)probeVolumeDebug.probeShading,
                    setIndex = value => probeVolumeDebug.probeShading = (DebugProbeShadingMode)value,
                });
                probeContainerChildren.children.Add(new DebugUI.FloatField { displayName = "Debug Size", tooltip = "The size of probes shown in the debug view.", getter = () => probeVolumeDebug.probeSize, setter = value => probeVolumeDebug.probeSize = value, min = () => kProbeSizeMin, max = () => kProbeSizeMax });

                var exposureCompensation = new DebugUI.FloatField
                {
                    displayName = "Exposure Compensation",
                    tooltip = "Modify the brightness of probe visualizations. Decrease this number to make very bright probes more visible.",
                    getter = () => probeVolumeDebug.exposureCompensation,
                    setter = value => probeVolumeDebug.exposureCompensation = value,
                    isHiddenCallback = () =>
                    {
                        return probeVolumeDebug.probeShading switch
                        {
                            DebugProbeShadingMode.SH => false,
                            DebugProbeShadingMode.SHL0 => false,
                            DebugProbeShadingMode.SHL0L1 => false,
                            DebugProbeShadingMode.SkyOcclusionSH => false,
                            DebugProbeShadingMode.SkyDirection => false,
                            DebugProbeShadingMode.ProbeOcclusion => false,
                            _ => true
                        };
                    }
                };
                probeContainerChildren.children.Add(exposureCompensation);

                probeContainerChildren.children.Add(new DebugUI.IntField
                {
                    displayName = "Max Subdivisions Displayed",
                    tooltip = "The highest (most dense) probe subdivision level displayed in the debug view.",
                    getter = () => probeVolumeDebug.maxSubdivToVisualize,
                    setter = (v) => probeVolumeDebug.maxSubdivToVisualize = Mathf.Max(0, Mathf.Min(v, GetMaxSubdivision() - 1)),
                    min = () => 0,
                    max = () => Mathf.Max(0, GetMaxSubdivision() - 1),
                });

                probeContainerChildren.children.Add(new DebugUI.IntField
                {
                    displayName = "Min Subdivisions Displayed",
                    tooltip = "The lowest (least dense) probe subdivision level displayed in the debug view.",
                    getter = () => probeVolumeDebug.minSubdivToVisualize,
                    setter = (v) => probeVolumeDebug.minSubdivToVisualize = Mathf.Max(v, 0),
                    min = () => 0,
                    max = () => Mathf.Max(0, GetMaxSubdivision() - 1),
                });

                probeContainer.children.Add(probeContainerChildren);
            }

            probeContainer.children.Add(new DebugUI.BoolField
            {
                displayName = "Debug Probe Sampling",
                tooltip = "Render the debug view displaying how probes are sampled for a selected pixel. Use the viewport overlay 'SelectPixel' button or Ctrl+Click on the viewport to select the debugged pixel",
                getter = () => probeVolumeDebug.drawProbeSamplingDebug,
                setter = value =>
                {
                    probeVolumeDebug.drawProbeSamplingDebug = value;
                    probeSamplingDebugData.update = ProbeSamplingDebugUpdate.Once;
                    probeSamplingDebugData.forceScreenCenterCoordinates = true;
                },
            });

            var drawProbeSamplingDebugChildren = new DebugUI.Container()
            {
                isHiddenCallback = () => !probeVolumeDebug.drawProbeSamplingDebug
            };

            drawProbeSamplingDebugChildren.children.Add(new DebugUI.FloatField { displayName = "Debug Size", tooltip = "The size of gizmos shown in the debug view.", getter = () => probeVolumeDebug.probeSamplingDebugSize, setter = value => probeVolumeDebug.probeSamplingDebugSize = value, min = () => kProbeSizeMin, max = () => kProbeSizeMax });
            drawProbeSamplingDebugChildren.children.Add(new DebugUI.BoolField { displayName = "Debug With Sampling Noise", tooltip = "Enable Sampling Noise for this debug view. It should be enabled for accuracy but it can make results more difficult to read", getter = () => probeVolumeDebug.debugWithSamplingNoise, setter = value => { probeVolumeDebug.debugWithSamplingNoise = value; }, onValueChanged = RefreshDebug });
            probeContainer.children.Add(drawProbeSamplingDebugChildren);

            probeContainer.children.Add(new DebugUI.BoolField
            {
                displayName = "Virtual Offset Debug",
                tooltip = "Enable Virtual Offset debug visualization. Indicates the offsets applied to probe positions. These are used to capture lighting when probes are considered invalid.",
                getter = () => probeVolumeDebug.drawVirtualOffsetPush,
                setter = value =>
                {
                    probeVolumeDebug.drawVirtualOffsetPush = value;

                    if (probeVolumeDebug.drawVirtualOffsetPush && probeVolumeDebug.drawProbes && m_CurrentBakingSet != null)
                    {
                        // If probes are being drawn when enabling offset, automatically scale them down to a reasonable size so the arrows aren't obscured by the probes.
                        var searchDistance = CellSize(0) * MinBrickSize() / ProbeBrickPool.kBrickCellCount * m_CurrentBakingSet.settings.virtualOffsetSettings.searchMultiplier + m_CurrentBakingSet.settings.virtualOffsetSettings.outOfGeoOffset;
                        probeVolumeDebug.probeSize = Mathf.Min(probeVolumeDebug.probeSize, Mathf.Clamp(searchDistance, kProbeSizeMin, kProbeSizeMax));
                    }
                }
            });

            var drawVirtualOffsetDebugChildren = new DebugUI.Container()
            {
                isHiddenCallback = () => !probeVolumeDebug.drawVirtualOffsetPush
            };

            var voOffset = new DebugUI.FloatField
            {
                displayName = "Debug Size",
                tooltip = "Modify the size of the arrows used in the virtual offset debug visualization.",
                getter = () => probeVolumeDebug.offsetSize,
                setter = value => probeVolumeDebug.offsetSize = value,
                min = () => kOffsetSizeMin,
                max = () => kOffsetSizeMax,
                isHiddenCallback = () => !probeVolumeDebug.drawVirtualOffsetPush
            };
            drawVirtualOffsetDebugChildren.children.Add(voOffset);
            probeContainer.children.Add(drawVirtualOffsetDebugChildren);

            probeContainer.children.Add(new DebugUI.FloatField { displayName = "Debug Draw Distance", tooltip = "How far from the Scene Camera to draw probe debug visualizations. Large distances can impact Editor performance.", getter = () => probeVolumeDebug.probeCullingDistance, setter = value => probeVolumeDebug.probeCullingDistance = value, min = () => 0.0f });
            widgetList.Add(probeContainer);

            var adjustmentContainer = new DebugUI.Container()
            {
                displayName = "Probe Adjustment Volumes"
            };
            adjustmentContainer.children.Add(new DebugUI.BoolField
            {
                displayName = "Auto Display Probes",
                tooltip = "When enabled and a Probe Adjustment Volumes is selected, automatically display the probes.",
                getter = () => probeVolumeDebug.autoDrawProbes,
                setter = value => probeVolumeDebug.autoDrawProbes = value,
                onValueChanged = RefreshDebug
            });
            adjustmentContainer.children.Add(new DebugUI.BoolField
            {
                displayName = "Isolate Affected",
                tooltip = "When enabled, only displayed probes in the influence of the currently selected Probe Adjustment Volumes.",
                getter = () => probeVolumeDebug.isolationProbeDebug,
                setter = value => probeVolumeDebug.isolationProbeDebug = value,
                onValueChanged = RefreshDebug
            });
            widgetList.Add(adjustmentContainer);

            var streamingContainer = new DebugUI.Container()
            {
                displayName = "Streaming",
                isHiddenCallback = () => !(gpuStreamingEnabled || diskStreamingEnabled)
            };
            streamingContainer.children.Add(new DebugUI.BoolField { displayName = "Freeze Streaming", tooltip = "Stop Unity from streaming probe data in or out of GPU memory.", getter = () => probeVolumeDebug.freezeStreaming, setter = value => probeVolumeDebug.freezeStreaming = value });
            streamingContainer.children.Add(new DebugUI.BoolField { displayName = "Display Streaming Score", getter = () => probeVolumeDebug.displayCellStreamingScore, setter = value => probeVolumeDebug.displayCellStreamingScore = value });
            streamingContainer.children.Add(new DebugUI.BoolField { displayName = "Maximum cell streaming", tooltip = "Enable streaming as many cells as possible every frame.", getter = () => instance.loadMaxCellsPerFrame, setter = value => instance.loadMaxCellsPerFrame = value});

            var maxCellStreamingContainerChildren = new DebugUI.Container()
            {
                isHiddenCallback = () => instance.loadMaxCellsPerFrame
            };
            maxCellStreamingContainerChildren.children.Add(new DebugUI.IntField { displayName = "Loaded Cells Per Frame", tooltip = "Determines the maximum number of Cells Unity streams per frame. Loading more Cells per frame can impact performance.", getter = () => instance.numberOfCellsLoadedPerFrame, setter = value => instance.SetNumberOfCellsLoadedPerFrame(value), min = () => 1, max = () => kMaxCellLoadedPerFrame });
            streamingContainer.children.Add(maxCellStreamingContainerChildren);

            // Those are mostly for internal dev purpose.
            if (Debug.isDebugBuild)
            {
                streamingContainer.children.Add(new DebugUI.BoolField { displayName = "Display Index Fragmentation", getter = () => probeVolumeDebug.displayIndexFragmentation, setter = value => probeVolumeDebug.displayIndexFragmentation = value });
                var indexDefragContainerChildren = new DebugUI.Container()
                {
                    isHiddenCallback = () => !probeVolumeDebug.displayIndexFragmentation
                };
                indexDefragContainerChildren.children.Add(new DebugUI.Value { displayName = "Index Fragmentation Rate", getter = () => instance.indexFragmentationRate });
                streamingContainer.children.Add(indexDefragContainerChildren);
                streamingContainer.children.Add(new DebugUI.BoolField { displayName = "Verbose Log", getter = () => probeVolumeDebug.verboseStreamingLog, setter = value => probeVolumeDebug.verboseStreamingLog = value });
                streamingContainer.children.Add(new DebugUI.BoolField { displayName = "Debug Streaming", getter = () => probeVolumeDebug.debugStreaming, setter = value => probeVolumeDebug.debugStreaming = value });
            }

            widgetList.Add(streamingContainer);

            if (supportScenarioBlending && m_CurrentBakingSet != null)
            {
                var blendingContainer = new DebugUI.Container() { displayName = "Scenario Blending" };
                blendingContainer.children.Add(new DebugUI.IntField { displayName = "Number Of Cells Blended Per Frame", getter = () => instance.numberOfCellsBlendedPerFrame, setter = value => instance.numberOfCellsBlendedPerFrame = value, min = () => 0 });
                blendingContainer.children.Add(new DebugUI.FloatField { displayName = "Turnover Rate", getter = () => instance.turnoverRate, setter = value => instance.turnoverRate = value, min = () => 0, max = () => 1 });

                void RefreshScenarioNames(string guid)
                {
                    HashSet<string> allScenarios = new();
                    foreach (var set in Resources.FindObjectsOfTypeAll<ProbeVolumeBakingSet>())
                    {
                        if (!set.sceneGUIDs.Contains(guid))
                            continue;
                        foreach (var scenario in set.lightingScenarios)
                            allScenarios.Add(scenario);
                    }

                    allScenarios.Remove(m_CurrentBakingSet.lightingScenario);
                    if (m_DebugActiveSceneGUID == guid && allScenarios.Count + 1 == m_DebugScenarioNames.Length && m_DebugActiveScenario == m_CurrentBakingSet.lightingScenario)
                        return;

                    int i = 0;
                    ArrayExtensions.ResizeArray(ref m_DebugScenarioNames, allScenarios.Count + 1);
                    ArrayExtensions.ResizeArray(ref m_DebugScenarioValues, allScenarios.Count + 1);
                    m_DebugScenarioNames[0] = new GUIContent("None");
                    m_DebugScenarioValues[0] = 0;
                    foreach (var scenario in allScenarios)
                    {
                        i++;
                        m_DebugScenarioNames[i] = new GUIContent(scenario);
                        m_DebugScenarioValues[i] = i;
                    }

                    m_DebugActiveSceneGUID = guid;
                    m_DebugActiveScenario = m_CurrentBakingSet.lightingScenario;
                    m_DebugScenarioField.enumNames = m_DebugScenarioNames;
                    m_DebugScenarioField.enumValues = m_DebugScenarioValues;
                    if (probeVolumeDebug.otherStateIndex >= m_DebugScenarioNames.Length)
                        probeVolumeDebug.otherStateIndex = 0;
                }

                m_DebugScenarioField = new DebugUI.EnumField
                {
                    displayName = "Scenario Blend Target",
                    tooltip = "Select another lighting scenario to blend with the active lighting scenario.",
                    enumNames = m_DebugScenarioNames,
                    enumValues = m_DebugScenarioValues,
                    getIndex = () =>
                    {
                        if (m_CurrentBakingSet == null)
                            return 0;
                        RefreshScenarioNames(GetSceneGUID(SceneManagement.SceneManager.GetActiveScene()));

                        probeVolumeDebug.otherStateIndex = 0;
                        if (!string.IsNullOrEmpty(m_CurrentBakingSet.otherScenario))
                        {
                            for (int i = 1; i < m_DebugScenarioNames.Length; i++)
                            {
                                if (m_DebugScenarioNames[i].text == m_CurrentBakingSet.otherScenario)
                                {
                                    probeVolumeDebug.otherStateIndex = i;
                                    break;
                                }
                            }
                        }
                        return probeVolumeDebug.otherStateIndex;
                    },
                    setIndex = value =>
                    {
                        string other = value == 0 ? null : m_DebugScenarioNames[value].text;
                        m_CurrentBakingSet.BlendLightingScenario(other, m_CurrentBakingSet.scenarioBlendingFactor);
                        probeVolumeDebug.otherStateIndex = value;
                    },
                    getter = () => probeVolumeDebug.otherStateIndex,
                    setter = (value) => probeVolumeDebug.otherStateIndex = value,
                };

                blendingContainer.children.Add(m_DebugScenarioField);
                blendingContainer.children.Add(new DebugUI.FloatField { displayName = "Scenario Blending Factor", tooltip = "Blend between lighting scenarios by adjusting this slider.", getter = () => instance.scenarioBlendingFactor, setter = value => instance.scenarioBlendingFactor = value, min = () => 0.0f, max = () => 1.0f });

                widgetList.Add(blendingContainer);
            }

            if (widgetList.Count > 0)
            {
                m_DebugItems = widgetList.ToArray();
                var panel = DebugManager.instance.GetPanel(k_DebugPanelName, true);
                panel.children.Add(m_DebugItems);
            }

            DebugManager debugManager = DebugManager.instance;
            debugManager.RegisterData(probeVolumeDebug);
        }

        void UnregisterDebug(bool destroyPanel)
        {
            if (destroyPanel)
                DebugManager.instance.RemovePanel(k_DebugPanelName);
            else
                DebugManager.instance.GetPanel(k_DebugPanelName, false).children.Remove(m_DebugItems);
        }

        class RenderFragmentationOverlayPassData
        {
            public Material debugFragmentationMaterial;
            public Rendering.DebugOverlay debugOverlay;
            public int chunkCount;
            public ComputeBuffer debugFragmentationData;
            public TextureHandle colorBuffer;
            public TextureHandle depthBuffer;
        }

        /// <summary>
        /// Render a debug view showing fragmentation of the GPU memory.
        /// </summary>
        /// <param name="renderGraph">The RenderGraph responsible for executing this pass.</param>
        /// <param name="colorBuffer">The color buffer where the overlay will be rendered.</param>
        /// <param name="depthBuffer">The depth buffer used for depth-testing the overlay.</param>
        /// <param name="debugOverlay">The debug overlay manager to orchestrate multiple overlays.</param>
        public void RenderFragmentationOverlay(RenderGraph renderGraph, TextureHandle colorBuffer, TextureHandle depthBuffer, DebugOverlay debugOverlay)
        {
            if (!m_ProbeReferenceVolumeInit || !probeVolumeDebug.displayIndexFragmentation)
                return;

            using (var builder = renderGraph.AddRenderPass<RenderFragmentationOverlayPassData>("APVFragmentationOverlay", out var passData))
            {
                passData.debugOverlay = debugOverlay;
                passData.debugFragmentationMaterial = m_DebugFragmentationMaterial;
                passData.colorBuffer = builder.UseColorBuffer(colorBuffer, 0);
                passData.depthBuffer = builder.UseDepthBuffer(depthBuffer, DepthAccess.ReadWrite);
                passData.debugFragmentationData = m_Index.GetDebugFragmentationBuffer();
                passData.chunkCount = passData.debugFragmentationData.count;

                builder.SetRenderFunc(
                    (RenderFragmentationOverlayPassData data, RenderGraphContext ctx) =>
                    {
                        var mpb = ctx.renderGraphPool.GetTempMaterialPropertyBlock();

                        data.debugOverlay.SetViewport(ctx.cmd);
                        mpb.SetInt("_ChunkCount", data.chunkCount);
                        mpb.SetBuffer("_DebugFragmentation", data.debugFragmentationData);
                        ctx.cmd.DrawProcedural(Matrix4x4.identity, data.debugFragmentationMaterial, 0, MeshTopology.Triangles, 3, 1, mpb);
                        data.debugOverlay.Next();
                    });
            }
        }

        bool ShouldCullCell(Vector3 cellPosition, Transform cameraTransform, Plane[] frustumPlanes)
        {
            var volumeAABB = GetCellBounds(cellPosition);
            var cellSize = MaxBrickSize();

            // We do coarse culling with cell, finer culling later.
            float distanceRoundedUpWithCellSize = Mathf.CeilToInt(probeVolumeDebug.probeCullingDistance / cellSize) * cellSize;

            if (Vector3.Distance(cameraTransform.position, volumeAABB.center) > distanceRoundedUpWithCellSize)
                return true;

            return !GeometryUtility.TestPlanesAABB(frustumPlanes, volumeAABB);
        }

        static Vector4[] s_BoundsArray = new Vector4[16 * 3];

        static void UpdateDebugFromSelection(ref Vector4[] _AdjustmentVolumeBounds, ref int _AdjustmentVolumeCount)
        {
            if (ProbeVolumeDebug.s_ActiveAdjustmentVolumes == 0)
                return;

            #if UNITY_EDITOR
            foreach (var touchup in Selection.GetFiltered<ProbeAdjustmentVolume>(SelectionMode.Unfiltered))
            {
                if (!touchup.isActiveAndEnabled) continue;

                Volume volume = new Volume(Matrix4x4.TRS(touchup.transform.position, touchup.transform.rotation, touchup.GetExtents()), 0, 0);
                volume.CalculateCenterAndSize(out Vector3 center, out var _);

                if (touchup.shape == ProbeAdjustmentVolume.Shape.Sphere)
                {
                    volume.Z.x = float.MaxValue;
                    volume.X.x = touchup.radius;
                }
                else
                {
                    volume.X *= 0.5f;
                    volume.Y *= 0.5f;
                    volume.Z *= 0.5f;
                }

                _AdjustmentVolumeBounds[_AdjustmentVolumeCount * 3 + 0] = new Vector4(center.x, center.y, center.z, volume.Z.x);
                _AdjustmentVolumeBounds[_AdjustmentVolumeCount * 3 + 1] = new Vector4(volume.X.x, volume.X.y, volume.X.z, volume.Z.y);
                _AdjustmentVolumeBounds[_AdjustmentVolumeCount * 3 + 2] = new Vector4(volume.Y.x, volume.Y.y, volume.Y.z, volume.Z.z);

                if (++_AdjustmentVolumeCount == 16)
                    break;
            }
            #endif
        }

        Bounds GetCellBounds(Vector3 cellPosition)
        {
            var cellSize = MaxBrickSize();
            var cellOffset = ProbeOffset() + ProbeVolumeDebug.currentOffset;
            Vector3 cellCenterWS = cellOffset + cellPosition * cellSize + Vector3.one * (cellSize / 2.0f);

            return new Bounds(cellCenterWS, cellSize * Vector3.one);
        }

        bool ShouldCullCell(Vector3 cellPosition, Vector4[] adjustmentVolumeBounds, int adjustmentVolumeCount)
        {
            var cellAABB = GetCellBounds(cellPosition);

            for (int touchup = 0; touchup < adjustmentVolumeCount; touchup++)
            {
                Vector3 center = adjustmentVolumeBounds[touchup * 3 + 0];

                if (adjustmentVolumeBounds[touchup * 3].w == float.MaxValue) // sphere
                {
                    var diameter = adjustmentVolumeBounds[touchup * 3 + 1].x * 2.0f;
                    Bounds bounds = new Bounds(center, new Vector3(diameter, diameter, diameter));

                    if (bounds.Intersects(cellAABB))
                        return false;
                }
                else
                {
                    Volume volume = new Volume();
                    volume.X = adjustmentVolumeBounds[touchup * 3 + 1];
                    volume.Y = adjustmentVolumeBounds[touchup * 3 + 2];
                    volume.Z = new Vector3(adjustmentVolumeBounds[touchup * 3 + 0].w, adjustmentVolumeBounds[touchup * 3 + 1].w, adjustmentVolumeBounds[touchup * 3 + 2].w);

                    volume.corner = center - volume.X - volume.Y - volume.Z;
                    volume.X *= 2.0f;
                    volume.Y *= 2.05f;
                    volume.Z *= 2.0f;

                    if (ProbeVolumePositioning.OBBAABBIntersect(volume, cellAABB, volume.CalculateAABB()))
                        return false;
                }
            }
            return true;
        }

        void DrawProbeDebug(Camera camera, Texture exposureTexture)
        {
            if (!enabledBySRP || !isInitialized)
                return;

            bool drawProbes = probeVolumeDebug.drawProbes;
            bool debugDraw = drawProbes ||
                             probeVolumeDebug.drawVirtualOffsetPush ||
                             probeVolumeDebug.drawProbeSamplingDebug;

            int adjustmentVolumeCount = 0;
            Vector4[] adjustmentVolumeBounds = s_BoundsArray;
            if (!debugDraw && probeVolumeDebug.autoDrawProbes)
            {
                UpdateDebugFromSelection(ref adjustmentVolumeBounds, ref adjustmentVolumeCount);
                drawProbes |= adjustmentVolumeCount != 0;
            }

            if (!debugDraw && !drawProbes)
                return;

            GeometryUtility.CalculateFrustumPlanes(camera, m_DebugFrustumPlanes);

            m_DebugMaterial.shaderKeywords = null;
            if (m_SHBands == ProbeVolumeSHBands.SphericalHarmonicsL1)
                m_DebugMaterial.EnableKeyword("PROBE_VOLUMES_L1");
            else if (m_SHBands == ProbeVolumeSHBands.SphericalHarmonicsL2)
                m_DebugMaterial.EnableKeyword("PROBE_VOLUMES_L2");

            // This is to force the rendering not to draw to the depth pre pass and still behave.
            // They are going to be rendered opaque anyhow, just using the transparent render queue to make sure
            // they properly behave w.r.t fog.
            m_DebugMaterial.renderQueue = (int)RenderQueue.Transparent;
            m_DebugOffsetMaterial.renderQueue = (int)RenderQueue.Transparent;
            m_ProbeSamplingDebugMaterial.renderQueue = (int)RenderQueue.Transparent;
            m_ProbeSamplingDebugMaterial02.renderQueue = (int)RenderQueue.Transparent;

            m_DebugMaterial.SetVector("_DebugEmptyProbeData", APVDefinitions.debugEmptyColor);

            if (probeVolumeDebug.drawProbeSamplingDebug)
            {
                m_ProbeSamplingDebugMaterial.SetInt("_ShadingMode", (int)probeVolumeDebug.probeShading);
                m_ProbeSamplingDebugMaterial.SetInt("_RenderingLayerMask", (int)probeVolumeDebug.samplingRenderingLayer);
                m_ProbeSamplingDebugMaterial.SetVector("_DebugArrowColor", new Vector4(1.0f, 1.0f, 1.0f, 1.0f));
                m_ProbeSamplingDebugMaterial.SetVector("_DebugLocator01Color", new Vector4(1.0f, 1.0f, 1.0f, 1.0f));
                m_ProbeSamplingDebugMaterial.SetVector("_DebugLocator02Color", new Vector4(0.3f, 0.3f, 0.3f, 1.0f));
                m_ProbeSamplingDebugMaterial.SetFloat("_ProbeSize", probeVolumeDebug.probeSamplingDebugSize);
                m_ProbeSamplingDebugMaterial.SetTexture("_NumbersTex", m_DisplayNumbersTexture);
                m_ProbeSamplingDebugMaterial.SetInt("_DebugSamplingNoise", Convert.ToInt32(probeVolumeDebug.debugWithSamplingNoise));
                m_ProbeSamplingDebugMaterial.SetInt("_ForceDebugNormalViewBias", 0); // Add a secondary locator to show intermediate position (with no Anti-Leak) when Anti-Leak is active

                m_ProbeSamplingDebugMaterial.SetBuffer("_positionNormalBuffer", probeSamplingDebugData.positionNormalBuffer);

                Graphics.DrawMesh(m_DebugProbeSamplingMesh, new Vector4(0.0f, 0.0f, 0.0f, 1.0f), Quaternion.identity, m_ProbeSamplingDebugMaterial, 0, camera);
                Graphics.ClearRandomWriteTargets();
            }

            // Sanitize the min max subdiv levels with what is available
            int minAvailableSubdiv = cells.Count > 0 ? GetMaxSubdivision()-1 : 0;
            foreach (var cell in cells.Values)
            {
                minAvailableSubdiv = Mathf.Min(minAvailableSubdiv, cell.desc.minSubdiv);
            }

            int maxSubdivToVisualize = Mathf.Max(0, Mathf.Min(probeVolumeDebug.maxSubdivToVisualize, GetMaxSubdivision() - 1));
            int minSubdivToVisualize = Mathf.Clamp(probeVolumeDebug.minSubdivToVisualize, minAvailableSubdiv, maxSubdivToVisualize);
            m_MaxSubdivVisualizedIsMaxAvailable = maxSubdivToVisualize == GetMaxSubdivision() - 1;

            bool adjustmentCulling = drawProbes && !probeVolumeDebug.drawProbes && probeVolumeDebug.isolationProbeDebug;
            foreach (var cell in cells.Values)
            {
                if (ShouldCullCell(cell.desc.position, camera.transform, m_DebugFrustumPlanes))
                    continue;

                if (adjustmentCulling && ShouldCullCell(cell.desc.position, adjustmentVolumeBounds, adjustmentVolumeCount))
                    continue;

                var debug = CreateInstancedProbes(cell);

                if (debug == null)
                    continue;

                for (int i = 0; i < debug.probeBuffers.Count; ++i)
                {
                    var props = debug.props[i];
                    props.SetInt("_ShadingMode", (int)probeVolumeDebug.probeShading);
                    props.SetFloat("_ExposureCompensation", probeVolumeDebug.exposureCompensation);
                    props.SetFloat("_ProbeSize", probeVolumeDebug.probeSize);
                    props.SetFloat("_CullDistance", probeVolumeDebug.probeCullingDistance);
                    props.SetInt("_MaxAllowedSubdiv", maxSubdivToVisualize);
                    props.SetInt("_MinAllowedSubdiv", minSubdivToVisualize);
                    props.SetFloat("_ValidityThreshold", m_CurrentBakingSet.settings.dilationSettings.dilationValidityThreshold);
                    props.SetInt("_RenderingLayerMask", probeVolumeDebug.visibleLayers);
                    props.SetFloat("_OffsetSize", probeVolumeDebug.offsetSize);
                    props.SetTexture("_ExposureTexture", exposureTexture);

                    if (drawProbes)
                    {
                        m_DebugMaterial.SetVectorArray("_TouchupVolumeBounds", adjustmentVolumeBounds);
                        m_DebugMaterial.SetInt("_AdjustmentVolumeCount", probeVolumeDebug.isolationProbeDebug ? adjustmentVolumeCount : 0);
                        m_DebugMaterial.SetVector("_ScreenSize", new Vector4(camera.pixelWidth, camera.pixelHeight, 1.0f/camera.pixelWidth, 1.0f/camera.pixelHeight));

                        var probeBuffer = debug.probeBuffers[i];
                        m_DebugMaterial.SetInt("_DebugProbeVolumeSampling", 0);
                        m_DebugMaterial.SetBuffer("_positionNormalBuffer", probeSamplingDebugData.positionNormalBuffer);
                        Graphics.DrawMeshInstanced(debugMesh, 0, m_DebugMaterial, probeBuffer, probeBuffer.Length, props, ShadowCastingMode.Off, false, 0, camera, LightProbeUsage.Off, null);
                    }

                    if (probeVolumeDebug.drawProbeSamplingDebug)
                    {
                        var probeBuffer = debug.probeBuffers[i];
                        m_ProbeSamplingDebugMaterial02.SetInt("_DebugProbeVolumeSampling", 1);
                        props.SetInt("_ShadingMode", (int)DebugProbeShadingMode.SH);
                        props.SetFloat("_ProbeSize", probeVolumeDebug.probeSamplingDebugSize);
                        props.SetInt("_DebugSamplingNoise", Convert.ToInt32(probeVolumeDebug.debugWithSamplingNoise));
                        props.SetInt("_RenderingLayerMask", (int)probeVolumeDebug.samplingRenderingLayer);
                        m_ProbeSamplingDebugMaterial02.SetBuffer("_positionNormalBuffer", probeSamplingDebugData.positionNormalBuffer);
                        Graphics.DrawMeshInstanced(debugMesh, 0, m_ProbeSamplingDebugMaterial02, probeBuffer, probeBuffer.Length, props, ShadowCastingMode.Off, false, 0, camera, LightProbeUsage.Off, null);
                    }

                    if (probeVolumeDebug.drawVirtualOffsetPush)
                    {
                        m_DebugOffsetMaterial.SetVectorArray("_TouchupVolumeBounds", adjustmentVolumeBounds);
                        m_DebugOffsetMaterial.SetInt("_AdjustmentVolumeCount", probeVolumeDebug.isolationProbeDebug ? adjustmentVolumeCount : 0);

                        var offsetBuffer = debug.offsetBuffers[i];
                        Graphics.DrawMeshInstanced(m_DebugOffsetMesh, 0, m_DebugOffsetMaterial, offsetBuffer, offsetBuffer.Length, props, ShadowCastingMode.Off, false, 0, camera, LightProbeUsage.Off, null);
                    }
                }
            }
        }

        internal void ResetDebugViewToMaxSubdiv()
        {
            if (m_MaxSubdivVisualizedIsMaxAvailable)
                probeVolumeDebug.maxSubdivToVisualize = GetMaxSubdivision() - 1;
        }

        void ClearDebugData()
        {
            realtimeSubdivisionInfo.Clear();
        }

        CellInstancedDebugProbes CreateInstancedProbes(Cell cell)
        {
            if (cell.debugProbes != null)
                return cell.debugProbes;

            if (HasActiveStreamingRequest(cell))
                return null;
                
            int maxSubdiv = GetMaxSubdivision() - 1;

            if (!cell.data.bricks.IsCreated || cell.data.bricks.Length == 0 || !cell.data.probePositions.IsCreated || !cell.loaded)
                return null;

            List<Matrix4x4[]> probeBuffers = new List<Matrix4x4[]>();
            List<Matrix4x4[]> offsetBuffers = new List<Matrix4x4[]>();
            List<MaterialPropertyBlock> props = new List<MaterialPropertyBlock>();
            var chunks = cell.poolInfo.chunkList;

            Vector4[] texels = new Vector4[kProbesPerBatch];
            float[] layer = new float[kProbesPerBatch];
            float[] validity = new float[kProbesPerBatch];
            float[] dilationThreshold = new float[kProbesPerBatch];
            float[] relativeSize = new float[kProbesPerBatch];
            float[] touchupUpVolumeAction = cell.data.touchupVolumeInteraction.Length > 0 ? new float[kProbesPerBatch] : null;
            Vector4[] offsets = cell.data.offsetVectors.Length > 0 ? new Vector4[kProbesPerBatch] : null;

            List<Matrix4x4> probeBuffer = new List<Matrix4x4>();
            List<Matrix4x4> offsetBuffer = new List<Matrix4x4>();

            var debugData = new CellInstancedDebugProbes();
            debugData.probeBuffers = probeBuffers;
            debugData.offsetBuffers = offsetBuffers;
            debugData.props = props;

            var chunkSizeInProbes = ProbeBrickPool.GetChunkSizeInProbeCount();

            var loc = ProbeBrickPool.ProbeCountToDataLocSize(chunkSizeInProbes);

            float baseThreshold = m_CurrentBakingSet.settings.dilationSettings.dilationValidityThreshold;
            int idxInBatch = 0;
            int globalIndex = 0;
            int brickCount = cell.desc.probeCount / ProbeBrickPool.kBrickProbeCountTotal;
            int bx = 0, by = 0, bz = 0;
            for (int brickIndex = 0; brickIndex < brickCount; ++brickIndex)
            {
                Debug.Assert(bz < loc.z);

                int brickSize = cell.data.bricks[brickIndex].subdivisionLevel;
                int chunkIndex = brickIndex / ProbeBrickPool.GetChunkSizeInBrickCount();
                var chunk = chunks[chunkIndex];
                Vector3Int brickStart = new Vector3Int(chunk.x + bx, chunk.y + by, chunk.z + bz);

                for (int z = 0; z < ProbeBrickPool.kBrickProbeCountPerDim; ++z)
                {
                    for (int y = 0; y < ProbeBrickPool.kBrickProbeCountPerDim; ++y)
                    {
                        for (int x = 0; x < ProbeBrickPool.kBrickProbeCountPerDim; ++x)
                        {
                            Vector3Int texelLoc = new Vector3Int(brickStart.x + x, brickStart.y + y, brickStart.z + z);

                            int probeFlatIndex = chunkIndex * chunkSizeInProbes + (bx + x) + loc.x * ((by + y) + loc.y * (bz + z));
                            var position = cell.data.probePositions[probeFlatIndex] - ProbeOffset(); // Offset is applied in shader

                            probeBuffer.Add(Matrix4x4.TRS(position, Quaternion.identity, Vector3.one * (0.3f * (brickSize + 1))));
                            validity[idxInBatch] = cell.data.validity[probeFlatIndex];
                            dilationThreshold[idxInBatch] =  baseThreshold;
                            texels[idxInBatch] = new Vector4(texelLoc.x, texelLoc.y, texelLoc.z, brickSize);
                            relativeSize[idxInBatch] = (float)brickSize / (float)maxSubdiv;

                            layer[idxInBatch] = Unity.Mathematics.math.asfloat(cell.data.layer.Length > 0 ? cell.data.layer[probeFlatIndex] : 0xFFFFFFFF);

                            if (touchupUpVolumeAction != null)
                            {
                                touchupUpVolumeAction[idxInBatch] = cell.data.touchupVolumeInteraction[probeFlatIndex];
                                dilationThreshold[idxInBatch] = touchupUpVolumeAction[idxInBatch] > 1.0f ? touchupUpVolumeAction[idxInBatch] - 1.0f : baseThreshold;
                            }

                            if (offsets != null)
                            {
                                const float kOffsetThresholdSqr = 1e-6f;

                                var offset = cell.data.offsetVectors[probeFlatIndex];
                                offsets[idxInBatch] = offset;

                                if (offset.sqrMagnitude < kOffsetThresholdSqr)
                                {
                                    offsetBuffer.Add(Matrix4x4.identity);
                                }
                                else
                                {
                                    var orientation = Quaternion.LookRotation(-offset);
                                    var scale = new Vector3(0.5f, 0.5f, offset.magnitude);
                                    offsetBuffer.Add(Matrix4x4.TRS(position + offset, orientation, scale));
                                }
                            }
                            idxInBatch++;

                            if (probeBuffer.Count >= kProbesPerBatch || globalIndex == cell.desc.probeCount - 1)
                            {
                                idxInBatch = 0;
                                MaterialPropertyBlock prop = new MaterialPropertyBlock();

                                prop.SetFloatArray("_Validity", validity);
                                prop.SetFloatArray("_RenderingLayer", layer);
                                prop.SetFloatArray("_DilationThreshold", dilationThreshold);
                                prop.SetFloatArray("_TouchupedByVolume", touchupUpVolumeAction);
                                prop.SetFloatArray("_RelativeSize", relativeSize);
                                prop.SetVectorArray("_IndexInAtlas", texels);

                                if (offsets != null)
                                    prop.SetVectorArray("_Offset", offsets);

                                props.Add(prop);

                                probeBuffers.Add(probeBuffer.ToArray());
                                probeBuffer.Clear();

                                offsetBuffers.Add(offsetBuffer.ToArray());
                                offsetBuffer.Clear();
                            }

                            globalIndex++;
                        }
                    }
                }

                bx += ProbeBrickPool.kBrickProbeCountPerDim;
                if (bx >= loc.x)
                {
                    bx = 0;
                    by += ProbeBrickPool.kBrickProbeCountPerDim;
                    if (by >= loc.y)
                    {
                        by = 0;
                        bz += ProbeBrickPool.kBrickProbeCountPerDim;
                        if (bz >= loc.z)
                        {
                            bx = 0;
                            by = 0;
                            bz = 0;
                        }
                    }
                }
            }

            cell.debugProbes = debugData;

            return debugData;
        }

        void OnClearLightingdata()
        {
            ClearDebugData();
        }
    }
}