/******************************************************************************/
/*
  Project   - MudBun
  Publisher - Long Bunny Labs
              http://LongBunnyLabs.com
  Author    - Ming-Lun "Allen" Chou
              http://AllenChou.net
*/
/******************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;

using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Rendering;
using UnityEngine.XR;

namespace MudBun
{
  [ExecuteInEditMode]
  public abstract class MudRendererBase : MonoBehaviour
  {
    #region Events

    public virtual void InvokeOnMeshGenerated(Mesh mesh) { }

    #endregion

    //-------------------------------------------------------------------------

    #region Enums & Structs

    public enum HardwareModeEnum
    {
      [InspectorName("GPU")] Gpu, 
      // [InspectorName("CPU")] Cpu, 
    }

    public enum RenderModeEnum
    {
      FlatMesh = 0,
      SmoothMesh = 1,
      CircleSplats = 2,
      QuadSplats = 3,
      Decal = 4,
      /*
      [HideInInspector] RayMarchedSurface = 5,
      [HideInInspector] RayTracedVoxels = 6,
      */
    }

    public enum RenderMaterialModeEnum
    {
      Static,
      Dynamic, 
    }

    public enum RayTracedVoxelModeEnum
    {
      FlatCubes, 
      FacetedCubes, 
      FlatSpheres, 
      SmoothSpheres, 
      Custom = 100, 
    }

    public enum RayTracedVoxelPaddingModeEnum
    {
      None, 
      ByDistance, 
      Full, 
    }

    public enum RenderModeCategoryEnum
    {
      Unknown = -1, 
      Mesh, 
      Splats, 
      Decal, 
      /*
      RayMarchedSurface, 
      RayTracedVoxels, 
      */
    }

    public enum MeshingModeEnum
    {
      MarchingCubes, 
      DualQuads, 
      SurfaceNets, 
      DualContouring, 
    }

    public enum ComputeModeEnum
    {
      Auto, 
      Manual, 
      ManualNoRender, 
      EveryFrame, 
      TimeSliced
    }

    public enum TimeSliceModeEnum
    {
      ByFramesAutoOffset, 
      ByFramesManualOffset, 
      ByPeriodAutoOffset, 
      ByPeriodManualOffset, 
    }

    public struct Const
    {
      public struct KernelIndex
      {
        public int ClearVoxelHashTable;
        public int ClearAutoSmoothVertDataTable;
        public int ClearVoxelCache;
        public int RegisterTopNodes;
        public int UpdateBranchingIndirectDispatchArgs;
        public int AllocateChildNodes;
        public int UpdateVoxelIndirectDispatchArgs;

        public int GenerateFlatMarchingCubesMesh;
        public int GenerateSmoothMarchingCubesMesh;
        public int GenerateMarchingCubesSplats;
        public int GenerateFlatMarchingCubesMesh2d;
        public int GenerateSmoothMarchingCubesMesh2d;
        public int GenerateMarchingCubesSplats2d;
        public int UpdateMarchingCubesAutoSmoothIndirectDispatchArgs;
        public int MarchingCubesUpdateAutoSmooth;
        public int MarchingCubesComputeAutoSmooth;

        public int GenerateDualQuads;
        public int GenerateDualQuads2d;
        public int UpdateDualMeshingIndirectDispatchArgs;
        public int DualMeshingFlatMeshNormal;
        public int DualMeshingSmoothMeshNormal;
        public int DualMeshingFlatMeshNormal2d;
        public int DualMeshingSmoothMeshNormal2d;
        public int DualMeshingUpdateAutoSmooth;
        public int DualMeshingComputeAutoSmooth;
        public int DualMeshingUpdateSmoothCornerIndirectDispatchArgs;
        public int DualMeshingSmoothCorner;
        public int UpdateDualMeshingSplatsIndirectArgs;
        public int ConvertDualMeshingSplats;

        public int SurfaceNetsMovePoint;
        public int SurfaceNetsMovePoint2d;

        public int DualContouringMovePoint;
        public int DualContouringMovePoint2d;

        public int UpdateRayTracedVoxelIndirectDispatchArgs;
        public int ComputeRayTracedVoxelGenPoints;
        public int ComputeRayTracedVoxelGenPointsWithNormals;

        public int GenerateNoiseCache;

        public int RigBones;

        public int GenerateSdf;
      }
      public static KernelIndex Kernel;

      public static int TriTable;
      public static int VertTable;
      public static int TriTable2d;

      public static int Brushes;
      public static int BrushMaterials;
      public static int NumBrushes;

      public static int SurfaceShift;

      public static int RenderMode;
      public static int MeshingMode;
      public static int RayTracedVoxelMode;
      public static int RayTracedVoxelSizeMultiplier;
      public static int RayTracedVoxelSmoothCubeNormal;
      public static int RayTracedVoxelRadius;
      public static int RayTracedVoxelPaddingMode;
      public static int RayTracedVoxelInternalPaddingDistance;
      public static int RayTracedVoxelSizeFadeDistance;

      public static int NormalDifferentiationStep;
      public static int NormalQuantization;
      public static int Normal2dFadeDist;
      public static int Normal2dStrength;
      public static int EnableAutoSmooth;
      public static int AutoSmoothMaxAngle;
      public static int AutoSmoothVertDataTable;
      public static int AutoSmoothVertDataPoolSize;
      public static int EnableSmoothCorner;
      public static int SmoothCornerSubdivision;
      public static int SmoothCornerNormalBlur;
      public static int SmoothCornerFade;
      public static int InvertNormals;

      public static int SplatSize;
      public static int SplatSizeJitter;
      public static int SplatNormalShift;
      public static int SplatNormalShiftJitter;
      public static int SplatColorJitter;
      public static int SplatPositionJitter;
      public static int SplatRotationJitter;
      public static int SplatOrientationJitter;
      public static int SplatOriginalNormalBlend;
      public static int SplatJitterNoisiness;
      public static int SplatCameraFacing;
      public static int SplatNormalsMatchCameraFacing;
      public static int SplatShadowsMatchCameraFacing;
      public static int SplatScreenSpaceFlattening;
      //public static int SplatSmoothNormalBlend;
      public static int SurfaceNetsDualQuadsBlend;
      public static int SurfaceNetsBinarySearchIterations;
      public static int SurfaceNetsGradientDescentIterations;
      public static int SurfaceNetsGradientDescentFactor;
      public static int DualContouringDualQuadsBlend;
      public static int DualContouringRelaxation;
      public static int DualContouringSolverIterations;
      public static int DualContouringBinarySearchIterations;
      public static int DualContouringGradientDescentIterations;
      public static int DualContouringGradientDescentFactor;

      public static int AabbTree;
      public static int AabbRoot;

      public static int Enable2dMode;
      public static int ForceAllBrushes;
      public static int NumAllocations; // general allocation counters
      public static int NodeHashTable;
      public static int NodeHashTableSize;
      public static int NodePool;
      public static int NodePoolSize;
      public static int NumNodesAllocated;
      public static int UseVoxelCache;
      public static int VoxelCacheIdTable;
      public static int VoxelCache;
      public static int VoxelCacheSize;
      public static int BrushMaskPool;
      public static int BrushMaskPoolSize;
      public static int IndirectDispatchArgs;
      public static int CurrentNodeDepth;
      public static int CurrentNodeBranchingFactor;
      public static int CurrentNodeSize;
      public static int VoxelSize;
      public static int VoxelTreeBranchingFactorsCompressed;
      public static int VoxelNodeSizes;
      public static int MaxNodeDepth;
      public static int ChunkVoxelDensity;
      public static int GenPoints;
      public static int MaxGenPoints;
      public static int IndirectDrawArgs;

      public static int MasterColor;
      public static int MasterEmission;
      public static int MasterMetallic;
      public static int MasterSmoothness;

      public static int ScaleSign;
      public static int LocalToWorld;
      public static int LocalToWorldIt;
      public static int LocalToWorldScale;
      public static int WorldToLocal;
      public static int WorldToLocalIt;

      public static int NoiseCache;
      public static int NoiseCacheDimension;
      public static int NoiseCacheDensity;
      public static int NoiseCachePeriod;

      public static int SdfOutput;
      public static int SdfOutputSize;
      public static int SdfCenter;
      public static int SdfDimension;

      public static int MaxRayMarchSteps;
      public static int RayMarchHitDistance;
      public static int RayMarchMaxRayDistance;

      public static int MeshGenerationAutoRiggingAlgorithm;

      /*
      public static int NumLightMarchSteps;
      public static int RayMarchStepSize;
      public static int RayMarchVolumeDensity;
      public static int RayMarchVolumeBorderFade;
      public static int RayMarchLightPositionType;
      public static int RayMarchLightDirection;
      public static int RayMarchAbsorption;
      public static int RayMarchDarknesThreshold;
      public static int RayMarchTransmittanceCurve;
      public static int RayMarchNoiseEdgeFade;
      public static int RayMarchNoiseThreshold;
      public static int RayMarchNoiseScrollSpeed;
      public static int RayMarchNoiseBaseOctaveSize;
      public static int RayMarchNoiseNumOctaves;
      public static int RayMarchNoiseOctaveOffsetFactor;
      */

      public static int IsMeshRenderMaterial;
      public static int IsSplatRenderMaterial;
      public static int MaterialNeedsSdfProperties;
      public static int MaterialNeedsRayMarchingProperties;
    }

    #endregion // end: Enums & Structs

    //-------------------------------------------------------------------------

    #region Global Consts

    public static readonly int ThreadGroupExtent = 4;
    public static readonly int ThreadGroupSize = ThreadGroupExtent * ThreadGroupExtent * ThreadGroupExtent;
    public static readonly int ClearThreadGroupSize = 256;
    private static int[] s_aVoxelTreeBranchingFactor = new int[] { 8, 8, 4 };
    public static int[] VoxelTreeBranchingFactors => s_aVoxelTreeBranchingFactor; 
    public static int VoxelTreeBranchingFactorsComrpessed = (int) Codec.Pack8888((uint)s_aVoxelTreeBranchingFactor[0], (uint)s_aVoxelTreeBranchingFactor[1], (uint)s_aVoxelTreeBranchingFactor[2], 0);
    public static int VoxelNodeDepth => VoxelTreeBranchingFactors.Length;
    private static int s_chunkVoxelDensity = -1;
    public static int ChunkVoxelDensity
    {
      get
      {
        if (s_chunkVoxelDensity < 0)
          s_chunkVoxelDensity = VoxelTreeBranchingFactors.Aggregate((x, y) => x * y);
        return s_chunkVoxelDensity;
      }
    }

    // maximum allowed number of brushes per renderer
    // NOTE: MaxBrushes must be a multiple of 32 (8 * sizeof(int))
    //       (MaxBrushes / 32) must be less than or equal to kMaxBrushMaskInts in BrushMaskDefs.cginc 
    //       MaxBrushes must *NOT* be larger than 2^kAabbTreeNodeStackSize in AabbTreeDefs.cginc
    public static readonly int MaxBrushes = 1024;
    public static int MaxBrushMaskInts => MaxBrushes / 32;
    public static int MaxBrushGroupDepth = 6;

    private static int[] s_noiseCacheDimensionInts = new int[] { 256, 128, 256 };
    public static int[] NoiseCacheDimensionInts => s_noiseCacheDimensionInts;
    private static float[] s_noiseCacheDimensionFloats;
    public static float[] NoiseCacheDimensionFloats
    {
      get
      {
        if (s_noiseCacheDimensionFloats == null)
          s_noiseCacheDimensionFloats = NoiseCacheDimensionInts.Select(x => (float)x).ToArray();
        return s_noiseCacheDimensionFloats;
      }
    }
    public static readonly float NoiseCacheDensity = 32.0f;
    private static float[] s_noiseCachePeriod;
    public static float[] NoiseCachePeriod
    {
      get
      {
        if (s_noiseCachePeriod == null)
          s_noiseCachePeriod = NoiseCacheDimensionInts.Select(x => x / NoiseCacheDensity).ToArray();
        return s_noiseCachePeriod;
      }
    }

    public int VoxelToVertexFactor
    {
      get
      {
        switch (RenderMode)
        {
          case RenderModeEnum.FlatMesh:
          case RenderModeEnum.SmoothMesh:
            return 3;

          case RenderModeEnum.CircleSplats:
            return 2;

          case RenderModeEnum.QuadSplats:
            return 3;
        }
        return 3;
      }
    }

    #endregion // end: Global Consts

    //-------------------------------------------------------------------------

    #region Global Resources

    private static bool s_globalResourcesValid = false;

    protected static HashSet<MudRendererBase> s_renderers = new HashSet<MudRendererBase>();
    protected static Dictionary<float, MudBrushBase> s_brushMap = new Dictionary<float, MudBrushBase>();
    private static ComputeShader s_computeVoxelGen;
    private static ComputeShader s_computeMarchingCubes;
    private static ComputeShader s_computeDualMeshing;
    private static ComputeShader s_computeSurfaceNets;
    private static ComputeShader s_computeDualContouring;
    private static ComputeShader s_computeRayTracedVoxels;
    private static ComputeShader s_computeNoiseCache;
    private static ComputeShader s_computeMeshLock;
    private static ComputeShader s_computeSdfGen;
    private static ComputeBuffer s_triTableBuffer;
    private static ComputeBuffer s_vertTableBuffer;
    private static ComputeBuffer s_triTable2dBuffer;
    private static ComputeBuffer s_brushesBuffer;
    private static ComputeBuffer s_brushMaterialBuffer;
    private static ComputeBuffer s_aabbTreeBuffer;
    private static ComputeBuffer s_dummyBuffer;
    private static RenderTexture s_noiseCache;
    public static RenderTexture NoiseCache => s_noiseCache;
    /*
    private static Texture s_noiseCache;
    public static Texture NoiseCache
    {
      get
      {
        if (s_noiseCache != null)
          return s_noiseCache;

        s_noiseCache = ResourcesUtil.NoiseTexture;
        return s_noiseCache;
      }
    }
    */

    public static ResourcesUtil.RenderPipelineEnum RenderPipeline => ResourcesUtil.RenderPipeline;

    public static int GlobalResourceGpuMemoryAllocated
    {
      get
      {
        int bytes = 0;

        if (s_triTableBuffer != null)
          bytes += s_triTableBuffer.stride * s_triTableBuffer.count;

        if (s_vertTableBuffer != null)
          bytes += s_vertTableBuffer.stride * s_vertTableBuffer.count;

        if (s_triTable2dBuffer != null)
          bytes += s_triTable2dBuffer.stride * s_triTable2dBuffer.count;

        if (s_brushesBuffer != null)
          bytes += s_brushesBuffer.stride * s_brushesBuffer.count;

        if (s_brushMaterialBuffer != null)
          bytes += s_brushMaterialBuffer.stride * s_brushMaterialBuffer.count;

        if (s_aabbTreeBuffer != null)
          bytes += s_aabbTreeBuffer.stride * s_aabbTreeBuffer.count;

        if (s_noiseCache != null)
          bytes += s_noiseCache.width * s_noiseCache.height * s_noiseCache.volumeDepth * sizeof(float);

        if (s_dummyBuffer != null)
          bytes += s_dummyBuffer.stride * s_dummyBuffer.count;

        return bytes;
      }
    }

    public static void ReloadAllShaders()
    {
      foreach (var renderer in s_renderers)
      {
        renderer.ReloadShaders();
      }
    }

    #endregion // end: Global Resources

    //-------------------------------------------------------------------------

    #region Local Resources

    private bool m_localResourcesValid = false;

    protected NativeArray<SdfBrush> m_aSdfBrush;
    protected NativeArray<SdfBrushMaterial> m_aSdfBrushMaterial;
    protected Dictionary<int, int> m_sdfBrushMaterialIndexMap;

    //[Header("Budgets")]

    // base budgets
    [Range(1, 2048)] public int MaxVoxelsK = 256;
    [Range(16, 1024)] public int MaxChunks = 64;
    private bool UseVoxelCache = false; // profiler says this doesn't really help with performance

    // derived budgets
    public int MaxVoxels => 1024 * MaxVoxelsK;
    public int MaxVoxelNodes => 1024 * (MaxChunks + MaxVoxelsK);
    public int MaxBrushMasks => 256 * MaxChunks;
    public int MaxGenPoints => VoxelToVertexFactor * MaxVoxelNodes;

    // only works in editor
    public bool ShowGpuMemoryUsage = false;
    public bool AutoAdjustBudgetsToHighWaterMarks = false;
    [Range(0, 100)] public int AutoAdjustBudgetsToHighWaterMarksMarginPercent = 15;
    private bool m_autoAdjustBudgetsToHighWaterMarks = false;

    //[Header("Render")]

    // local shader resources
    [Range(0.1f, 100.0f)] public float VoxelDensity = 8.0f;
    public float TopVoxelNodeSize
    {
      get
      {
        float size = ChunkVoxelDensity / VoxelDensity;
        if (LockMeshIntermediateState != LockMeshIntermediateStateEnum.Idle)
          size = ChunkVoxelDensity / MeshGenerationVoxelDensity;
        return size * 1.0001f;
      }
    }
    private float[] m_aNodeSize;
    public float[] NodeSizes
    {
      get
      {
        if (m_aNodeSize == null || m_aNodeSize.Length != VoxelNodeDepth + 1)
          m_aNodeSize = new float[VoxelNodeDepth + 1];
        float nodeSize = TopVoxelNodeSize;
        for (int depth = 0; depth < m_aNodeSize.Length; ++depth)
        {
          m_aNodeSize[depth] = nodeSize;
          if (depth < VoxelNodeDepth)
            nodeSize /= VoxelTreeBranchingFactors[depth];
        }
        return m_aNodeSize;
      }
    }
    public Vector4 NodeSizesVector
    {
      get
      {
        var aNodeSize = NodeSizes;
        int maxDepth = aNodeSize.Length;

        Vector4 vec = Vector4.zero;
        if (maxDepth > 0)
          vec.x = aNodeSize[0];
        if (maxDepth > 1)
          vec.y = aNodeSize[1];
        if (maxDepth > 2)
          vec.z = aNodeSize[2];
        if (maxDepth > 3)
          vec.w = aNodeSize[3];

        return vec;
      }
    }

    public float VoxelSize
    {
      get
      {
        switch (RenderModeCategory)
        {
          case RenderModeCategoryEnum.Decal:
          //case RenderModeCategoryEnum.RayMarchedSurface:
            return 1e-5f;
        }
        return NodeSizes[VoxelNodeDepth];
      }
    }

    private bool AllowSharedRWBuffers
    {
      get
      {
        if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Metal) // Metal is weird
          return false;

        switch (RenderModeCategory)
        {
          case RenderModeCategoryEnum.Decal:
          //case RenderModeCategoryEnum.RayMarchedSurface:
          //case RenderModeCategoryEnum.RayTracedVoxels:
            return false;

          case RenderModeCategoryEnum.Splats:
            /*
            if (SplatSmoothNormalBlend > 0.0f)
              return false;
            */
            break;
        }

        return true;
      }
    }

    private bool m_initialized = false;

    private ComputeBuffer m_brushesBuffer;
    private ComputeBuffer m_brushMaterialBuffer;
    private ComputeBuffer m_aabbTreeBuffer;
    private int m_aabbTreeBufferRoot;
    private ComputeBuffer m_nodeHashTableBuffer;
    protected ComputeBuffer m_nodePoolBuffer;
    protected ComputeBuffer m_numNodesAllocatedBuffer;
    private ComputeBuffer m_numAllocationsBuffer;
    private ComputeBuffer m_voxelCacheIdTableBuffer;
    private ComputeBuffer m_voxelCacheBuffer;
    private ComputeBuffer m_brushMaskPoolBuffer;
    private ComputeBuffer m_indirectDispatchArgsBuffer;
    private ComputeBuffer m_autoSmoothVertDataTableBuffer;
    protected ComputeBuffer m_genPointsBufferDefault;
    protected ComputeBuffer m_indirectDrawArgsBufferDefault;
    protected ComputeBuffer m_genPointsBufferOverride;
    protected ComputeBuffer m_indirectDrawArgsBufferOverride;
    protected ComputeBuffer m_genPointsBufferUsedForCompute;
    protected ComputeBuffer m_indirectDrawArgsBufferUsedForCompute;

    private NativeArray<int> m_indirectDrawArgsInitData;
    private NativeArray<int> IndirectDrawArgsInitData
    {
      get
      {
        bool usingXr = false;
        var aXrDisplaySubsystem = new List<XRDisplaySubsystem>();
        SubsystemManager.GetInstances(aXrDisplaySubsystem);
        foreach (var xrDisplay in aXrDisplaySubsystem)
        {
          if (xrDisplay.running)
          {
            usingXr = true;
            break;
          }
        }
        int numInstances = usingXr ? 2 : 1;

        if (!m_indirectDrawArgsInitData.IsCreated)
        {
          m_indirectDrawArgsInitData = new NativeArray<int>(new int[] { 0, numInstances, 0, 0, 0, }, Allocator.Persistent);
        }

        m_indirectDrawArgsInitData[1] = numInstances;

        return m_indirectDrawArgsInitData;
      }
    }

    private static NativeArray<int> s_numAllocatoinsBufferInitData;
    private static NativeArray<int> NumAllocatoinsBufferInitData
    {
      get
      {
        int desiredLength = VoxelTreeBranchingFactors.Length;

        if (s_numAllocatoinsBufferInitData.IsCreated)
        {
          if (s_numAllocatoinsBufferInitData.Length == desiredLength)
            return s_numAllocatoinsBufferInitData;
          else
            s_numAllocatoinsBufferInitData.Dispose();
        }

        s_numAllocatoinsBufferInitData = new NativeArray<int>(desiredLength, Allocator.Persistent, NativeArrayOptions.ClearMemory);
        return s_numAllocatoinsBufferInitData;
      }
    }

    private static NativeArray<int> s_unitIndirectDispatchArgsInitData;
    private static NativeArray<int> UnitIndirectDispatchArgsInitData
    {
      get
      {
        if (s_unitIndirectDispatchArgsInitData.IsCreated)
          return s_unitIndirectDispatchArgsInitData;

        s_unitIndirectDispatchArgsInitData = new NativeArray<int>(new int[] { 1, 1, 1 }, Allocator.Persistent);
        return s_unitIndirectDispatchArgsInitData;
      }
    }

    private int[] m_numAllocationsBufferInitData;

    private static readonly int NodeHashTableAllocationMultiplier = 2;
    public int NodeHashTableSize
    {
      get
      {
        /*
        switch (RenderMode)
        {
          case RenderModeEnum.RayTracedVoxels:
            return MaxVoxels * NodeHashTableAllocationMultiplier;
        }
        */
        return MaxChunks * NodeHashTableAllocationMultiplier;
      }
    }

    private static readonly int AutoSmoothVertDataAllocationMultiplier = 2;
    public int AutoSmoothVertDataTableSize => MaxVoxels * AutoSmoothVertDataAllocationMultiplier;

    public enum NumAllcationIndex
    {
      BrushMask, 
      VoxelCache, 
      VoxelHash, 
      AutoSmoothVertData, 
    }

    private static bool s_warnedResourceAccessPerformanceImpact = false;

    public long LocalResourceGpuMemoryAllocated
    {
      get
      {
        long bytes = 0;

        if (!AllowSharedRWBuffers)
        {
          if (m_brushesBuffer != null)
            bytes += m_brushesBuffer.stride * m_brushesBuffer.count;

          if (m_brushMaterialBuffer != null)
            bytes += m_brushMaterialBuffer.stride * m_brushMaterialBuffer.count;

          if (m_aabbTreeBuffer != null)
            bytes += m_aabbTreeBuffer.stride * m_aabbTreeBuffer.count;
        }

        if (m_nodePoolBuffer != null)
          bytes += m_nodePoolBuffer.stride * ((long) m_nodePoolBuffer.count);

        if (m_nodeHashTableBuffer != null)
          bytes += m_nodeHashTableBuffer.stride * m_nodeHashTableBuffer.count;

        if (m_numNodesAllocatedBuffer != null)
          bytes += m_numNodesAllocatedBuffer.stride * m_numNodesAllocatedBuffer.count;

        if (m_numAllocationsBuffer != null)
          bytes += m_numAllocationsBuffer.stride * m_numAllocationsBuffer.count;

        if (m_voxelCacheIdTableBuffer != null)
          bytes += m_voxelCacheIdTableBuffer.stride * m_voxelCacheIdTableBuffer.count;

        if (m_voxelCacheBuffer != null)
          bytes += m_voxelCacheBuffer.stride * m_voxelCacheBuffer.count;

        if (m_brushMaskPoolBuffer != null)
          bytes += m_brushMaskPoolBuffer.stride * m_brushMaskPoolBuffer.count;

        if (m_indirectDispatchArgsBuffer != null)
          bytes += m_indirectDispatchArgsBuffer.stride * m_indirectDispatchArgsBuffer.count;

        if (m_genPointsBufferDefault != null)
          bytes += m_genPointsBufferDefault.stride * ((long)m_genPointsBufferDefault.count);

        if (m_indirectDrawArgsBufferDefault != null)
          bytes += m_indirectDrawArgsBufferDefault.stride * m_indirectDrawArgsBufferDefault.count;

        if (m_autoSmoothVertDataTableBuffer != null)
          bytes += m_autoSmoothVertDataTableBuffer.stride * m_autoSmoothVertDataTableBuffer.count;

        if (!s_warnedResourceAccessPerformanceImpact && Application.isPlaying)
        {
          Debug.LogWarning("MudBun: Accessing resource usage impacts performance!");
          s_warnedResourceAccessPerformanceImpact = true;
        }

        return bytes;
      }
    }

    public long LocalResourceGpuMemoryUsed
    {
      get
      {
        long bytes = 0;

        if (!AllowSharedRWBuffers)
        {
          if (m_brushesBuffer != null)
            bytes += m_brushesBuffer.stride * m_brushesBuffer.count;

          if (m_brushMaterialBuffer != null)
            bytes += m_brushMaterialBuffer.stride * m_brushMaterialBuffer.count;

          if (m_aabbTreeBuffer != null)
            bytes += m_aabbTreeBuffer.stride * m_aabbTreeBuffer.count;
        }

        if (m_numNodesAllocatedBuffer != null)
        {
          bytes += m_numNodesAllocatedBuffer.stride * ((long)m_numNodesAllocatedBuffer.count);

          var aNumNodesAllocated = new int[m_numNodesAllocatedBuffer.count];
          m_numNodesAllocatedBuffer.GetData(aNumNodesAllocated);
          int numTotalNodes = aNumNodesAllocated[0];
          bytes += numTotalNodes * m_nodePoolBuffer.stride;

          if (m_genPointsBufferDefault != null)
            bytes += 6 * numTotalNodes * m_genPointsBufferDefault.stride;
        }

        int [] aNumAllocated = null;
        if (m_numAllocationsBuffer != null)
        {
          aNumAllocated = new int[m_numAllocationsBuffer.count];
          m_numAllocationsBuffer.GetData(aNumAllocated);
        }
        if (aNumAllocated != null)
        {
          if (m_nodeHashTableBuffer != null)
          {
            int numTotalHashes = aNumAllocated[(int) NumAllcationIndex.VoxelHash];
            bytes += NodeHashTableAllocationMultiplier * numTotalHashes * m_nodeHashTableBuffer.stride;
          }

          if (m_autoSmoothVertDataTableBuffer != null)
          {
            int numSmoothVertData = aNumAllocated[(int) NumAllcationIndex.AutoSmoothVertData];
            bytes += numSmoothVertData * m_autoSmoothVertDataTableBuffer.count;
          }
        }

        if (m_numAllocationsBuffer != null)
          bytes += m_numAllocationsBuffer.stride * m_numAllocationsBuffer.count;

        if (m_voxelCacheIdTableBuffer != null)
          bytes += m_voxelCacheIdTableBuffer.stride * m_voxelCacheIdTableBuffer.count;

        if (m_voxelCacheBuffer != null)
          bytes += m_voxelCacheBuffer.stride * m_voxelCacheBuffer.count;

        if (m_brushMaskPoolBuffer != null)
          bytes += m_brushMaskPoolBuffer.stride * m_brushMaskPoolBuffer.count;

        if (m_indirectDispatchArgsBuffer != null)
          bytes += m_indirectDispatchArgsBuffer.stride * m_indirectDispatchArgsBuffer.count;

        if (m_indirectDrawArgsBufferDefault != null)
          bytes += m_indirectDrawArgsBufferDefault.stride * m_indirectDrawArgsBufferDefault.count;

        if (!s_warnedResourceAccessPerformanceImpact && Application.isPlaying)
        {
          Debug.LogWarning("MudBun: Accessing resource usage impacts performance!");
          s_warnedResourceAccessPerformanceImpact = true;
        }

        return bytes;
      }
    }

    public int NumVerticesAllocated => VoxelToVertexFactor * NumVoxelsAllocated;
    public int NumVerticesGenerated
    {
      get
      {
        if (m_indirectDispatchArgsBuffer == null)
          return 0;

        int[] aIndirectDrawArgs = new int[5];
        m_indirectDrawArgsBufferDefault.GetData(aIndirectDrawArgs);

        if (!s_warnedResourceAccessPerformanceImpact && Application.isPlaying)
        {
          Debug.LogWarning("MudBun: Accessing resource usage impacts performance!");
          s_warnedResourceAccessPerformanceImpact = true;
        }

        return aIndirectDrawArgs[0];
      }
    }

    public int NumVoxelsAllocated => (m_nodePoolBuffer != null) ? MaxVoxels : 0;
    public int NumChunksAllocated => (m_nodeHashTableBuffer != null) ? MaxChunks : 0;
    public int NumVoxelsUsed
    {
      get
      {
        if (m_numNodesAllocatedBuffer == null)
          return 0;

        var aNumAllocated = new int[m_numNodesAllocatedBuffer.count];
        m_numNodesAllocatedBuffer.GetData(aNumAllocated);

        if (!s_warnedResourceAccessPerformanceImpact && Application.isPlaying)
        {
          Debug.LogWarning("MudBun: Accessing resource usage impacts performance!");
          s_warnedResourceAccessPerformanceImpact = true;
        }

        return aNumAllocated[0];
      }
    }
    public int NumChunksUsed
    {
      get
      {
        if (m_numAllocationsBuffer == null)
          return 0;

        var aNumAllocated = new int[m_numAllocationsBuffer.count];
        m_numAllocationsBuffer.GetData(aNumAllocated);
        int numTotalHashes = aNumAllocated[(int)NumAllcationIndex.VoxelHash];

        if (!s_warnedResourceAccessPerformanceImpact && Application.isPlaying)
        {
          Debug.LogWarning("MudBun: Accessing resource usage impacts performance!");
          s_warnedResourceAccessPerformanceImpact = true;
        }

        return numTotalHashes;
      }
    }

    public bool ForceEvaluateAllBrushes = false;

    private bool ShouldForceAllBrushes()
    {
      if (ForceEvaluateAllBrushes)
        return true;

      // trial version can't reference MudSolid
      // should probably manually force this with the manual option anyway
      /*
      if (Enable2dMode)
      {
        if (Normal2dFade > MathUtil.Epsilon)
        {
          foreach (var b in m_aBrush)
          {
            var sb = (MudSolid) b;
            if (sb.Operator == SdfBrush.OperatorEnum.Subtract)
              return true;
          }
        }
      }
      */

      return false;
    }

    public bool Enable2dMode = false;
    [Range(-1.0f, 1.0f)] public float SurfaceShift = 0.0f;

    public HardwareModeEnum HardwareMode = HardwareModeEnum.Gpu;

    public RenderModeEnum RenderMode = RenderModeEnum.SmoothMesh;
    public RenderModeCategoryEnum RenderModeCategory
    {
      get
      {
        switch (RenderMode)
        {
          case RenderModeEnum.FlatMesh:
          case RenderModeEnum.SmoothMesh:
            return RenderModeCategoryEnum.Mesh;

          case RenderModeEnum.CircleSplats:
          case RenderModeEnum.QuadSplats:
            return RenderModeCategoryEnum.Splats;

          case RenderModeEnum.Decal:
            return RenderModeCategoryEnum.Decal;

          /*
          case RenderModeEnum.RayMarchedSurface:
            return RenderModeCategoryEnum.RayMarchedSurface;

          case RenderModeEnum.RayTracedVoxels:
            return RenderModeCategoryEnum.RayTracedVoxels;
          */
        }
        return RenderModeCategoryEnum.Mesh;
      }
    }

    public MeshingModeEnum MeshingMode = MeshingModeEnum.MarchingCubes;
    public RayTracedVoxelModeEnum RayTracedVoxelMode = RayTracedVoxelModeEnum.FlatCubes;
    [Range(0.0f, 1.0f)] public float RayTracedVoxelSizeMultiplier = 1.0f;
    [Range(0.0f, 1.0f)] public float RayTracedVoxelSmoothCubeNormal = 0.0f;
    [Range(0.0f, 1.0f)] public float RayTracedVoxelSphereFullness = 0.0f;
    public RayTracedVoxelPaddingModeEnum RayTracedVoxelPaddingMode = RayTracedVoxelPaddingModeEnum.ByDistance;
    public float RayTracedVoxelInternalPaddingDistance = 0.0f;
    public float RayTracedVoxelSizeFadeDistance = 0.0f;

    private static RenderModeCategoryEnum GetMaterialRenderModeCategory(Material material)
    {
      if (material == null)
        return RenderModeCategoryEnum.Unknown;

      if (material.HasProperty(Const.IsMeshRenderMaterial))
        return RenderModeCategoryEnum.Mesh;

      if (material.HasProperty(Const.IsSplatRenderMaterial))
        return RenderModeCategoryEnum.Splats;

      return RenderModeCategoryEnum.Unknown;
    }

    private static bool MaterialNeedsSdfProperties(Material material)
    {
      if (material == null)
        return false;

      return material.HasProperty(Const.MaterialNeedsSdfProperties);
    }

    private static bool MaterialNeedsRayMarchingProperties(Material material)
    {
      if (material == null)
        return false;

      return material.HasProperty(Const.MaterialNeedsRayMarchingProperties);
    }

    private enum RenderGeometryTypeEnum
    {
      Unknown = -1, 
      Mesh, 
      BoxProxy, 
      Chunks, 
    }

    private RenderGeometryTypeEnum RenderGeometryType
    {
      get
      {
        switch (RenderModeCategory)
        {
          case RenderModeCategoryEnum.Mesh:
          case RenderModeCategoryEnum.Splats:
            return RenderGeometryTypeEnum.Mesh;

          case RenderModeCategoryEnum.Decal:
          //case RenderModeCategoryEnum.RayMarchedSurface:
            return RenderGeometryTypeEnum.BoxProxy;

          /*
          case RenderModeCategoryEnum.RayTracedVoxels:
            return RenderGeometryTypeEnum.Chunks;
          */
        }

        return RenderGeometryTypeEnum.Unknown;
      }
    }

    private bool RenderMaterialNeedsSdfProperties
    {
      get
      {
        if (MaterialNeedsSdfProperties(m_materialUsed))
          return true;

        return false;
      }
    }
    private RenderGeometryTypeEnum m_prevRenderGeometryType = RenderGeometryTypeEnum.Unknown;

    public bool ShowAdvancedNormalOptions = false;
    [Range(0.0f, 1.0f)] public float SmoothNormalBlurRelative = 0.05f;
    [Range(0.0f, 0.2f)] public float SmoothNormalBlurAbsolute = 0.0f;
    public float NormalDifferentialStep => Mathf.Max(0.01f * VoxelSize, SmoothNormalBlurRelative * VoxelSize + SmoothNormalBlurAbsolute);
    [Range(0.0f, 1.0f)] public float NormalQuantization = 0.0f;
    [Range(0.0f, 1.0f)] public float Normal2dFade = 0.0f;
    [Range(0.0f, 1.0f)] public float Normal2dStrength = 1.0f;
    public bool EnableAutoSmoothing = false;
    [Range(0.0f, 180.0f)] public float AutoSmoothingMaxAngle = 30.0f;
    public bool EnableSmoothCorner = false;
    [Range(1, 4)] public int SmoothCornerSubdivision = 2;
    [Range(0.001f, 0.1f)] public float SmoothCornerNormalBlur = 0.02f;
    [Range(0.0f, 1.0f)] public float SmoothCornerFade = 0.0f;
    public bool InvertNormals = false;

    private bool ShouldDoAutoSmoothing
    {
      get
      {
        if (!EnableAutoSmoothing)
          return false;

        if (Enable2dMode)
          return false;

        switch (MeshingMode)
        {
          case MeshingModeEnum.MarchingCubes:
          case MeshingModeEnum.SurfaceNets:
          case MeshingModeEnum.DualContouring:
            switch (RenderMode)
            {
              case RenderModeEnum.FlatMesh:
              case RenderModeEnum.SmoothMesh:
                return true;
            }
            break;
        }

        return false;
      }
    }

    public bool ShowAdvancedSplatOptions = false;
    [Range(0.0f, 5.0f)] public float SplatSize = 1.0f;
    [Range(0.0f, 1.0f)] public float SplatSizeJitter = 0.0f;
    [Range(-1.0f, 1.0f)] public float SplatNormalShift = 0.0f;
    [Range(0.0f, 1.0f)] public float SplatNormalShiftJitter = 1.0f;
    [Range(0.0f, 1.0f)] public float SplatColorJitter = 0.0f;
    [Range(0.0f, 1.0f)] public float SplatPositionJitter = 0.0f;
    [Range(0.0f, 1.0f)] public float SplatRotationJitter = 0.0f;
    [Range(0.0f, 1.0f)] public float SplatOrientationJitter = 0.0f;
    [Range(0.0f, 1.0f)] public float SplatOriginalNormalBlend = 1.0f;
    [Range(0.01f, 1.0f)] public float SplatJitterNoisiness = 1.0f;
    [Range(0.0f, 1.0f)] public float SplatCameraFacing = 0.0f;
    public bool SplatNormalsMatchCameraFacing = false;
    public bool SplatShadowsMatchCameraFacing = false;
    [Range(0.0f, 1.0f)] public float SplatScreenSpaceFlattening = 1.0f;
    //[Range(0.0f, 1.0f)] public float SplatSmoothNormalBlend = 0.0f;

    [Range(0.0f, 1.0f)] public float SurfaceNetsDualQuadsBlend = 0.0f;
    public bool ShowAdvancedGeometryOptions = false;
    //[Range(0, 10)] public int SurfaceNetsBinarySearchIterations = 0;
    //[Range(0, 5)] public int SurfaceNetsGradientDescentIterations = 0;
    //[Range(0.0f, 1.0f)] public float SurfaceNetsGradientDescentFactor = 1.0f;
    public bool SurfaceNetsHighAccuracyMode = false;
    [Range(0.0f, 1.0f)] public float DualContouringDualQuadsBlend = 0.0f;
    [Range(0.0f, 1.0f)] public float DualContouringRelaxation = 0.0f;
    [Range(0, 20)] public int DualContouringSolverIterations = 5;
    //[Range(0, 10)] public int DualContouringBinarySearchIterations = 0;
    //[Range(0, 5)] public int DualContouringGradientDescentIterations = 0;
    //[Range(0.0f, 1.0f)] public float DualContouringGradientDescentFactor = 1.0f;
    public bool DualContouringHighAccuracyMode = false;

    public ShadowCastingMode CastShadows = ShadowCastingMode.On;
    public bool ReceiveShadows = true;

    public IList<MudBrushBase> Brushes => m_aBrush;
    private List<MudBrushBase> m_aBrush = new List<MudBrushBase>();
    private List<MudBrushBase> m_aBrushToProcess = new List<MudBrushBase>();
    private bool m_needRescanBrushes = false;
    private static readonly float AabbTreeFatBoundsRadius = 0.25f;
    internal AabbTree<MudBrushBase> m_aabbTree;
    private void ValidateAabbTree()
    {
      if (m_aabbTree != null)
        return;

      m_aabbTree = new AabbTree<MudBrushBase>(AabbTreeFatBoundsRadius);
    }

    //[Header("Material")]

    private MudSharedMaterialBase m_usedSharedMaterial;
    public MudSharedMaterialBase SharedMaterial;
    [SerializeField] private Color m_masterColor = Color.white;
    [SerializeField] private Color m_masterEmission = Color.white;
    [SerializeField] [Range(0.0f, 1.0f)] private float m_masterMetallic = 1.0f;
    [SerializeField] [Range(0.0f, 1.0f)] private float m_masterSmoothness = 1.0f;
    public Color MasterColor { get => m_usedSharedMaterial ? m_usedSharedMaterial.Color : m_masterColor; set { m_masterColor = value; } }
    public Color MasterEmission { get => m_usedSharedMaterial ? m_usedSharedMaterial.Emission : m_masterEmission; set { m_masterEmission = value; } }
    public float MasterMetallic { get => m_usedSharedMaterial ? m_usedSharedMaterial.Metallic : m_masterMetallic; set { m_masterMetallic = value; } }
    public float MasterSmoothness { get => m_usedSharedMaterial ? m_usedSharedMaterial.Smoothness : m_masterSmoothness; set { m_masterSmoothness = value; } }

    public Material RenderMaterialMesh;
    public Material RenderMaterialSplats;
    public Material RenderMaterialDecal;
    //public Material RenderMaterialRayMarchedSurface;
    //public Material RenderMaterialRayTracedVoxels;
    public Material RenderMaterial
    {
      get
      {
        switch (RenderModeCategory)
        {
          case RenderModeCategoryEnum.Mesh:
            return RenderMaterialMesh;

          case RenderModeCategoryEnum.Splats:
            return RenderMaterialSplats;

          case RenderModeCategoryEnum.Decal:
            return RenderMaterialDecal;

          /*
          case RenderModeCategoryEnum.RayMarchedSurface:
            return RenderMaterialRayMarchedSurface;

          case RenderModeCategoryEnum.RayTracedVoxels:
            return RenderMaterialRayTracedVoxels;
          */
        }
        return RenderMaterialMesh;
      }
    }
    private Material m_materialCloned;
    private Material m_materialUsed;
    private MaterialPropertyBlock m_materialProps;
    public MaterialPropertyBlock RenderMaterialPropertyBlock
    {
      get
      {
        if (m_materialProps != null)
          return m_materialProps;

        m_materialProps = new MaterialPropertyBlock();
        return m_materialProps;
      }
    }

    [Range(8, 256)] public int MaxRayMarchSteps = 64;
    [Range(0.0f, 1.0f)] public float RayMarchAccuracy = 0.5f;
    [Range(1.0f, 2e3f)] public float RayMarchMaxRayDistance = 1e3f;

    [Range(0.1f, 10.0f)] public float RayMarchStepSize = 0.5f;
    [Range(1, 16)] public int NumLightMarchSteps = 8;
    //[Range(0.1f, 100.0f)] public float RayMarchDistance = 20.0f;
    [Range(0.0f, 20.0f)] public float RayMarchVolumeDensity = 5.0f;
    public Light RayMarchLight;
    [Range(0.0f, 10.0f)] public float RayMarchVolumeAbsorption = 1.0f;
    [Range(0.0f, 10.0f)] public float RayMarchLightAbsorption = 1.0f;
    [Range(0.0f, 1.0f)] public float RayMarchDarknesThreshold = 0.1f;
    [Range(0.0f, 10.0f)] public float RayMarchTransmittanceCurve = 0.0f;
    public bool UseRayMarchNoise = false;
    [Range(0.0f, 1.0f)] public float RayMarchNoiseThreshold = 0.2f;
    [Range(0.0f, 10.0f)] public float RayMarchNoiseEdgeFade = 2.0f;
    public Vector3 RayMarchNoiseScrollSpeed = Vector3.zero;
    public Vector3 RayMarchNoiseBaseOctaveSize = Vector3.one;
    [Range(1, 3)] public int RayMarchNoiseNumOctaves = 2;
    public float RayMarchNoiseOctaveOffsetFactor = 0.5f;

    private Sdf.EvalJob[] m_aEvalJob;

    //[Header("Editor")]

    public enum ClickSelectionEnum
    {
      None, 
      Gizmos, 
      Raycast, 
      [InspectorName("Raycast (Forced Zero-Blend Union)")] RaycastForcedZeroBlendUnion, 
    }

    public ClickSelectionEnum ClickSelection = ClickSelectionEnum.Raycast;

    //[Header("Debug")]

    public bool AlwaysDrawGizmos = false;
    public bool DrawRawBrushBounds = false;
    public bool DrawComputeBrushBounds = false;
    public bool DrawRenderBounds = false;
    public bool DrawVoxelNodes = false;
    [Range(-1, 3)] public int DrawVoxelNodesDepth = -1;
    [Range(0.0f, 1.0f)] public float DrawVoxelNodesScale = 1.0f;

    public Aabb RenderBounds
    {
      get
      {
        var bounds = RenderBoundsRs;
        if (!bounds.IsEmpty) // don't expand if empty, or it might crash the GPU!
        {
          bounds.Expand(SurfaceShift);
          bounds.Transform(transform);
        }
        return bounds;
      }
    }

    public Aabb RenderBoundsRs => m_aabbTree.Bounds;

    [SerializeField] [HideInInspector] private string m_firstTrackedVersion = "";
    [SerializeField] [HideInInspector] private string m_previousTrackedVersion = "";
    [SerializeField] [HideInInspector] private string m_currentTrackedVersion = "";
    public string FirstTrackedVersion => m_firstTrackedVersion;
    public string PreviousTrackedVersion => m_previousTrackedVersion;
    public string CurrentTrackedVersion => m_currentTrackedVersion;

    #endregion // end: Local Resources

    //-------------------------------------------------------------------------

    #region Mesh Utilities

    public static readonly float MaxMeshGenerationVoxelDensityFreeVersion = 8.0f;
    public static readonly int MaxMeshGenerationTrianglesFreeVersion = 4096;

    public float MeshGenerationVoxelDensity
    {
      get
      {
        #if MUDBUN_FREE
        return Mathf.Min(MaxMeshGenerationVoxelDensityFreeVersion, VoxelDensity);
        #else
        return VoxelDensity;
        #endif
      }
    }

    public enum RenderableMeshMode
    {
      None, 
      Procedural, 
      MeshRenderer, 
    }

    public enum AutoRiggingAlgorithm
    {
      New,     // not ready yet
      Default, // will be Legacy once New is ready
    }

    public bool MeshGenerationCreateNewObject = false;
    public bool MeshGenerationCreateCollider = false;
    public bool MeshGenerationForceConvexCollider = false;
    [Range(-1.0f, 1.0f)] public float MeshGenerationColliderSurfaceShift = 0.0f;
    public bool MeshGenerationCreateRigidBody = false;
    public bool GenerateColliderMeshAssetByEditor = true; // editor only
    public string GenerateColliderMeshAssetByEditorName = ""; // editor only
    #if MUDBUN_FREE
    [Range(0.0f, 8.0f)] public float MeshGenerationColliderVoxelDensity = 4.0f;
    #else
    [Range(0.0f, 128.0f)] public float MeshGenerationColliderVoxelDensity = 4.0f;
    #endif
    public RenderableMeshMode MeshGenerationRenderableMeshMode = RenderableMeshMode.Procedural;
    public bool MeshGenerationAutoRigging = false;
    public AutoRiggingAlgorithm MeshGenerationAutoRiggingAlgorithm = AutoRiggingAlgorithm.Default;
    [Range(1, 4)] public int MeshGenerationAutoRiggingMaxBonesPerVertex = 4;
    public bool MeshGenerationGenerateTextureUV = false;
    public bool MeshGenerationGenerateLightMapUV = false;
    public bool MeshGenerationWeldVertices = false;
    public bool MeshGenerationLockOnStart = false;
    public bool GenerateMeshAssetByEditor = true; // editor only
    public string GenerateMeshAssetByEditorName = ""; // editor only
    public bool RecursiveLockMeshByEditor = true; // editor only
    public bool RememberLockedMeshMaterialByEditor = true; // editor only
    [SerializeField] [HideInInspector] public bool MeshGenerationLockOnStartByEditor = false; // editor only
    [SerializeField] protected Material m_lastLockedMeshMaterial;

    [Serializable]
    private class TransformCache
    {
      public Transform Transform;
      public Transform Parent;
      public Vector3 GlobalPosition;
      public Quaternion GlobalRotation;
      public Vector3 LocalPosition;
      public Quaternion LocalRotation;
      public Vector3 LocalScale;
      public bool HasBrushDescendants;
    }

    // order: parent->child
    [SerializeField] [HideInInspector] private List<TransformCache> m_aBrushTransformCache;
    [SerializeField] [HideInInspector] private List<TransformCache> m_aNestedRendereTransformCache;

    private bool HashNonMudBunObjectInHierarchy(Transform t)
    {
      if (t == null)
        return false;

      if (t.TryGetComponent(out Collider _) || t.TryGetComponent(out Collider2D _))
        return true;
      if (t.TryGetComponent(out MeshRenderer _) || t.TryGetComponent(out SkinnedMeshRenderer _))
      {
        if (!t.TryGetComponent(out MudRendererBase r) || !r.MeshLocked)
        {
          if (r != null)
            return true;
        }
      }

      for (int i = 0; i < t.childCount; ++i)
      {
        if (HashNonMudBunObjectInHierarchy(t.GetChild(i)))
          return true;
      }

      return false;
    }

    private bool HasBrushInHierarchy(Transform t)
    {
      if (t == null)
        return false;

      if (t.TryGetComponent(out MudBrushBase _))
        return true;

      for (int i = 0; i < t.childCount; ++i)
      {
        if (HasBrushInHierarchy(t.GetChild(i)))
          return true;
      }

      return false;
    }

    private void DetectMixedHierarchy(Transform t)
    {
      if (!HasBrushInHierarchy(t) || !HashNonMudBunObjectInHierarchy(t))
        return;

      Debug.LogWarning
      (
           "WARNING: Mixed MudBun objects and non-MudBun objects in renderer hierarchy detected during auto-rigging. "
         + "This can cause issues when normalizing bones to a unit scale of (1, 1, 1).\n" 
         + "Please follow this guideline: " 
         + "Objects with mixed MudBun objects and non-MudBun objects in their hierarchy should have a unit scale of (1, 1, 1)."
      );
    }

    private void CacheBoneTransforms()
    {
      m_aBrushTransformCache = new List<TransformCache>();
      m_aNestedRendereTransformCache = new List<TransformCache>();
      CacheBoneTransformsRecursive(transform);
      DetectMixedHierarchy(transform);
    }

    private void NormalizeBoneTransforms()
    {
      if (m_aBrushTransformCache == null)
        return;

      // move nested renderers out of the way
      foreach (var cache in m_aNestedRendereTransformCache)
      {
        cache.Transform.SetParent(null, true);
      }

      // set each bone's local scale to unit scale to avoid shearing in child bones
      foreach (var cache in m_aBrushTransformCache)
      {
        if (cache.HasBrushDescendants)
        {
          cache.Transform.localScale = Vector3.one;
          cache.Transform.position = cache.GlobalPosition;
          cache.Transform.rotation = cache.GlobalRotation;
        }
        else
        {
          cache.Transform.position = cache.GlobalPosition;
          cache.Transform.rotation = cache.GlobalRotation;
        }
      }

      // put nested renderers back in hierarchy
      foreach (var cache in m_aNestedRendereTransformCache)
      {
        cache.Transform.SetParent(cache.Parent, true);
        cache.Transform.position = cache.GlobalPosition;
        cache.Transform.rotation = cache.GlobalRotation;
      }
    }

    private void CacheBoneTransformsRecursive(Transform t)
    {
      if (t == null)
        return;

      var cache = 
        new TransformCache()
        {
          Transform = t, 
          Parent = t.parent, 
          GlobalPosition = t.position, 
          GlobalRotation = t.rotation, 
          LocalPosition = t.localPosition, 
          LocalRotation = t.localRotation, 
          LocalScale = t.localScale, 
          HasBrushDescendants = HasBrushInHierarchy(t), 
        };

      if (t != transform)
      {
        if (t.GetComponent<MudRendererBase>() == null)
        {
          m_aBrushTransformCache.Add(cache);
        }
        else
        {
          // renderer blocks recursion
          m_aNestedRendereTransformCache.Add(cache);
          return;
        }
      }

      for (int i = 0; i < t.childCount; ++i)
      {
        var child = t.GetChild(i);

        CacheBoneTransformsRecursive(t.GetChild(i));
      }
    }

    private void RestoreBoneTransforms()
    {
      if (m_aBrushTransformCache == null)
        return;

      // move nested renderers out of the way
      foreach (var cache in m_aNestedRendereTransformCache)
      {
        try
        {
          cache.Transform.SetParent(null, true);
        }
        catch (Exception)
        { }
      }

      // restore brush transforms
      foreach (var cache in m_aBrushTransformCache)
      {
        try
        {
          cache.Transform.localScale = cache.LocalScale;
          cache.Transform.localPosition = cache.LocalPosition;
          cache.Transform.localRotation = cache.LocalRotation;
        }
        catch (Exception)
        { }
      }

      // restore nested renderer transforms
      foreach (var cache in m_aNestedRendereTransformCache)
      {
        try
        {
          cache.Transform.SetParent(cache.Parent, true);
          cache.Transform.localScale = cache.LocalScale;
          cache.Transform.localPosition = cache.LocalPosition;
          cache.Transform.localRotation = cache.LocalRotation;
        }
        catch (Exception)
        { }
      }

      m_aBrushTransformCache = null;
      m_aNestedRendereTransformCache = null;
    }

    public enum GeneratedMeshType
    {
      Standard, 
      Compute, 
      Collider, 
    }

    private class PendingMeshData
    {
      public Mesh Mesh;
      public GeneratedMeshType MeshType;
      public Transform RootBone;
      public List<Transform> Bones;
      public bool DoRigging;
      public bool GenerateTextureUV;
      public bool GenerateLightMapUV;
      public bool WeldVertices;
      public bool OptimizeMeshForRendering;
      public bool Async;
      public ComputeBuffer IndirectDrawArgsBuffer;
      public ComputeBuffer GenPointsBuffer;

      public void Dispose()
      {
        if (IndirectDrawArgsBuffer != null)
        {
          //IndirectDrawArgsBuffer.Release();
          Janitor.Dispose(IndirectDrawArgsBuffer);
          IndirectDrawArgsBuffer = null;
        }

        if (GenPointsBuffer != null)
        {
          //GenPointsBuffer.Release();
          Janitor.Dispose(GenPointsBuffer);
          GenPointsBuffer = null;
        }
      }
    }

    private Dictionary<AsyncGPUReadbackRequest, PendingMeshData> m_pendingMeshTable = new Dictionary<AsyncGPUReadbackRequest, PendingMeshData>();
    public bool IsAnyMeshGenerationPending => m_pendingMeshTable != null && m_pendingMeshTable.Count > 0;
    public bool IsMeshGenerationPending(Mesh mesh)
    {
      foreach (var pair in m_pendingMeshTable)
        if (pair.Value.Mesh == mesh)
          return true;

      return false;
    }
    public void WaitForMeshGeneration(Mesh mesh)
    {
      foreach (var pair in m_pendingMeshTable)
      {
        if (pair.Value.Mesh != mesh)
          continue;

        pair.Key.WaitForCompletion();
        break;
      }
    }

    public Mesh GenerateMesh
    (
      GeneratedMeshType meshType, 
      bool async, 
      Mesh mesh = null, 
      bool generateTextureUV = false, 
      bool generateLightMapUV = false, 
      bool weldVertices = false, 
      bool optimizeMeshForRendering = false
    )
    {
      Transform [] aBone;
      return GenerateMesh(meshType, null, out aBone, async, mesh, generateTextureUV, generateLightMapUV, weldVertices, optimizeMeshForRendering);
    }

    public Mesh GenerateMesh
    (
      GeneratedMeshType meshType, 
      Transform rootBone, 
      out Transform [] aBone, 
      bool async, 
      Mesh mesh = null, 
      bool genreateTextureUV = false, 
      bool generateLightMapUV = false, 
      bool weldVertices = false, 
      bool optimizeMeshForRendering =false
    )
    {
      aBone = null;

      if (meshType == GeneratedMeshType.Compute)
        return null;

      UpdateComputeData();

      ValidateLocalResources();

      var prevRenderMode = RenderMode;
      var prevMeshingMode = MeshingMode;
      float prevVoxelDensity = VoxelDensity;
      int prevMaxVoxelsK = MaxVoxelsK;
      int prevMaxChunks = MaxChunks;
      float prevSurfaceShift = SurfaceShift;

      if (meshType == GeneratedMeshType.Collider)
      {
        RenderMode = RenderModeEnum.FlatMesh;
        VoxelDensity = MeshGenerationColliderVoxelDensity;
        SurfaceShift += MeshGenerationColliderSurfaceShift;
      }
      else
      {
        VoxelDensity = MeshGenerationVoxelDensity;
      }

      switch (RenderModeCategory)
      {
        case RenderModeCategoryEnum.Splats:
          RenderMode = RenderModeEnum.FlatMesh;
          break;

        /*
        case RenderModeCategoryEnum.RayMarchedSurface:
          RenderMode = RenderModeEnum.FlatMesh;
          MeshingMode = MeshingModeEnum.DualQuads;
          break;
        */
      }

      // always override buffers to work in Metal
      m_indirectDrawArgsBufferOverride = new ComputeBuffer(5, sizeof(int), ComputeBufferType.IndirectArguments);
      m_genPointsBufferOverride = new ComputeBuffer(MaxGenPoints, GenPoint.Stride);
      var indirectDrawArgsBuffer = m_indirectDrawArgsBufferOverride;
      var genPointsBuffer = m_genPointsBufferOverride;

      indirectDrawArgsBuffer.SetData(IndirectDrawArgsInitData);
      ForceCompute();

      RenderMode = prevRenderMode;
      MeshingMode = prevMeshingMode;
      VoxelDensity = prevVoxelDensity;
      MaxVoxelsK = prevMaxVoxelsK;
      MaxChunks = prevMaxChunks;
      SurfaceShift = prevSurfaceShift;
      MarkNeedsCompute();

      if (indirectDrawArgsBuffer == null)
        return null;

      if (genPointsBuffer == null)
        return null;

      if (mesh == null)
        mesh = new Mesh();

      var pendingMeshData = 
        new PendingMeshData()
        {
          Mesh = mesh, 
          MeshType = meshType, 
          RootBone = rootBone, 
          Bones = m_aBone, 
          DoRigging = m_doRigging, 
          GenerateTextureUV = genreateTextureUV, 
          GenerateLightMapUV = generateLightMapUV, 
          WeldVertices = weldVertices, 
          OptimizeMeshForRendering = optimizeMeshForRendering, 
          Async = async, 
          IndirectDrawArgsBuffer = indirectDrawArgsBuffer, 
          GenPointsBuffer = genPointsBuffer, 
        };

      if (m_aBone != null)
      {
        aBone = m_aBone.ToArray();
        m_aBone = null;
      }

      var request = AsyncGPUReadback.Request(pendingMeshData.IndirectDrawArgsBuffer, OnIndirectDrawArgsBufferRead);
      m_pendingMeshTable.Add(request, pendingMeshData);
      if (!async)
        request.WaitForCompletion();

      return mesh;
    }

    private void OnIndirectDrawArgsBufferRead(AsyncGPUReadbackRequest request)
    {
      PendingMeshData pendingMeshData;
      bool dataFound = m_pendingMeshTable.TryGetValue(request, out pendingMeshData);
      m_pendingMeshTable.Remove(request);
      var aDrawArgs = request.GetData<int>();
      if (!dataFound 
          || aDrawArgs.Length <= 0 
          || request.hasError)
      {
        if (pendingMeshData != null)
          pendingMeshData.Dispose();
        return;
      }

      int numVerts = aDrawArgs[0];
      if (MudBun.IsFreeVersion)
        numVerts = Mathf.Min(numVerts, 3 * MaxMeshGenerationTrianglesFreeVersion);

      if (numVerts <= 0)
      {
        pendingMeshData.Dispose();
        return;
      }

      var newRequest = AsyncGPUReadback.Request(pendingMeshData.GenPointsBuffer, numVerts * pendingMeshData.GenPointsBuffer.stride, 0, OnGenPointsBufferRead);
      m_pendingMeshTable.Add(newRequest, pendingMeshData);
      if (!pendingMeshData.Async)
        newRequest.WaitForCompletion();
    }

    private void OnGenPointsBufferRead(AsyncGPUReadbackRequest request)
    {
      PendingMeshData pendingMeshData;
      bool dataFound = m_pendingMeshTable.TryGetValue(request, out pendingMeshData);
      m_pendingMeshTable.Remove(request);
      if (!dataFound 
          || request.hasError)
      {
        if (pendingMeshData != null)
          pendingMeshData.Dispose();
        return;
      }

      var aGenPoint = request.GetData<GenPoint>();
      BuildMesh
      (
        pendingMeshData.Mesh, 
        pendingMeshData.MeshType, 
        aGenPoint, 
        pendingMeshData.RootBone, 
        pendingMeshData.Bones, 
        pendingMeshData.DoRigging, 
        pendingMeshData.GenerateTextureUV, 
        pendingMeshData.GenerateLightMapUV, 
        pendingMeshData.WeldVertices, 
        pendingMeshData.OptimizeMeshForRendering
      );

      if (pendingMeshData.Async 
          && pendingMeshData.IndirectDrawArgsBuffer == m_indirectDrawArgsBufferDefault 
          && pendingMeshData.GenPointsBuffer == m_genPointsBufferDefault 
          && m_isMeshLocked)
      {
        DisposeLocalResources();
      }

      InvokeOnMeshGenerated(pendingMeshData.Mesh);

      pendingMeshData.Dispose();
    }

    private void BuildMesh
    (
      Mesh mesh, 
      GeneratedMeshType meshType, 
      NativeArray<GenPoint> aGenPoint, 
      Transform rootBone, 
      List<Transform> bones, 
      bool doRigging, 
      bool generateTextureUV, 
      bool generateLightMapUV, 
      bool weldVertices, 
      bool optimizeMeshForRendering
    )
    {
      int numVerts = aGenPoint.Length;
      var aVertex = new NativeArray<Vector3>(numVerts, Allocator.Temp);
      var aNormal = new NativeArray<Vector3>(numVerts, Allocator.Temp);
      var aTangent = new NativeArray<Vector4>(numVerts, Allocator.Temp);
      var aVertIndex = new NativeArray<int>(numVerts, Allocator.Temp);
      bool invertNormals = InvertNormals && !Enable2dMode;
      for (int i = 0; i < numVerts; ++i)
      {
        aVertex[i] = aGenPoint[i].PosNorm;
        aNormal[i] = Codec.UnpackNormal(aGenPoint[i].PosNorm.w);
        aTangent[i] = VectorUtil.FindOrthogonal(aNormal[i]);
        aVertIndex[i] = invertNormals ? (3 * (i / 3) + 2 - (i % 3) ) : i;
      }

      mesh.Clear();
      mesh.SetVertices(aVertex);
      mesh.SetNormals(aNormal);
      mesh.SetTangents(aTangent);
      mesh.indexFormat = numVerts > 65535 ? IndexFormat.UInt32 : IndexFormat.UInt16;
      mesh.SetIndices(aVertIndex, MeshTopology.Triangles, 0);

      if (meshType == GeneratedMeshType.Standard)
      {
        Color [] aColor = new Color[numVerts];
        Vector4 [] aEmissionHash = new Vector4[numVerts];
        Vector2 [] aMetallicSmoothness = new Vector2[numVerts];
        Vector4 [] aTextureWeight = new Vector4[numVerts];

        BoneWeight [] aBoneWeight = null;
        if (doRigging)
        {
          CacheBoneTransforms();
          aBoneWeight = new BoneWeight[numVerts];
        }
        
        for (int i = 0; i < numVerts; ++i)
        {
          Color c = Codec.UnpackRgba(aGenPoint[i].Material.Color);
          Color et = Codec.UnpackRgba(aGenPoint[i].Material.EmissionTightness);

          /*
          if (QualitySettings.activeColorSpace == ColorSpace.Linear)
          {
            float gamma = 2.2f;
            aColor[i] = 
              new Color
              (
                Mathf.Pow(c.r * MasterColor.r, gamma), 
                Mathf.Pow(c.g * MasterColor.g, gamma), 
                Mathf.Pow(c.b * MasterColor.b, gamma), 
                c.a * MasterColor.a
              );
            aEmissionHash[i] = 
              new Vector4
              (
                Mathf.Pow(et.r * MasterEmission.r, gamma), 
                Mathf.Pow(et.g * MasterEmission.g, gamma), 
                Mathf.Pow(et.b * MasterEmission.b, gamma), 
                aGenPoint[i].Material.Hash
              );
          }
          else
          */
          {
            aColor[i] = c * MasterColor;
            aEmissionHash[i] = 
              new Vector4
              (
                et.r * MasterEmission.r, 
                et.g * MasterEmission.g, 
                et.b * MasterEmission.b, 
                aGenPoint[i].Material.Hash
              );
          }

          Vector2 ms = Codec.UnpackSaturated(aGenPoint[i].Material.MetallicSmoothness);
          aMetallicSmoothness[i] = new Vector2(ms.x * MasterMetallic, ms.y * MasterSmoothness);

          aTextureWeight[i] = Codec.UnpackRgba(aGenPoint[i].Material.TextureWeight);

          if (aBoneWeight != null)
          {
            aBoneWeight[i].boneIndex0 = aGenPoint[i].BoneIndex0;
            aBoneWeight[i].boneIndex1 = aGenPoint[i].BoneIndex1;
            aBoneWeight[i].boneIndex2 = aGenPoint[i].BoneIndex2;
            aBoneWeight[i].boneIndex3 = aGenPoint[i].BoneIndex3;

            Vector4 boneWeight = Codec.UnpackRgba(aGenPoint[i].BoneWeight);
            for (int iWeightComp = 4; iWeightComp > MeshGenerationAutoRiggingMaxBonesPerVertex && iWeightComp >= 0; --iWeightComp)
              boneWeight[iWeightComp - 1] = 0.0f;

            if (aBoneWeight[i].boneIndex0 < 0)
            {
              aBoneWeight[i].boneIndex0 = 0;
              boneWeight.x = 0.0f;
            }

            if (aBoneWeight[i].boneIndex1 < 0)
            {
              aBoneWeight[i].boneIndex1 = 0;
              boneWeight.y = 0.0f;
            }

            if (aBoneWeight[i].boneIndex2 < 0)
            {
              aBoneWeight[i].boneIndex2 = 0;
              boneWeight.z = 0.0f;
            }

            if (aBoneWeight[i].boneIndex3 < 0)
            {
              aBoneWeight[i].boneIndex3 = 0;
              boneWeight.w = 0.0f;
            }

            boneWeight /= Mathf.Max(MathUtil.Epsilon, Vector4.Dot(boneWeight, Vector4.one));
            aBoneWeight[i].weight0 = MathUtil.Saturate(boneWeight.x);
            aBoneWeight[i].weight1 = MathUtil.Saturate(boneWeight.y);
            aBoneWeight[i].weight2 = MathUtil.Saturate(boneWeight.z);
            aBoneWeight[i].weight3 = MathUtil.Saturate(boneWeight.w);
          }
        }
        mesh.SetColors(aColor);
        mesh.SetUVs(MeshUtil.EmissionHashUvIndex, aEmissionHash);
        mesh.SetUVs(MeshUtil.MetallicSmoothnessUvIndex, aMetallicSmoothness);
        mesh.SetUVs(MeshUtil.TextureWeightIndex, aTextureWeight);
        if (aBoneWeight != null 
            && rootBone != null 
            && bones != null)
        {
          NormalizeBoneTransforms();
          mesh.boneWeights = aBoneWeight;
          mesh.bindposes = bones.Select(x => x.worldToLocalMatrix * rootBone.localToWorldMatrix).ToArray();
        }

        bool uvGenerated = GenerateUV(mesh, generateTextureUV, generateLightMapUV);

        if (weldVertices)
        {
          MeshUtil.Weld(mesh, (uvGenerated ? 0 : -1));
        }

        if (optimizeMeshForRendering)
        {
          mesh.Optimize();
        }
      }
    }

    public virtual void RectifyNonUnitScaledParents() { }

    virtual protected bool GenerateUV(Mesh mesh, bool generateTextureUV, bool generateLightMapUV) { return false; }

    [SerializeField] [HideInInspector] private bool m_isMeshLocked = false;
    public bool MeshLocked => m_isMeshLocked;

    protected enum LockMeshIntermediateStateEnum
    {
      Idle, 
      PreLock, 
      PostLock, 
      PreUnlock, 
    }
    protected virtual LockMeshIntermediateStateEnum LockMeshIntermediateState => LockMeshIntermediateStateEnum.Idle;

    protected bool m_doRigging = false;

    public virtual Mesh AddCollider
    (
      GameObject go, 
      bool async, 
      Mesh mesh = null, 
      bool forceConvexCollider = false, 
      bool makeRigidBody = false
    )
    {
      return null;
    }

    public virtual Mesh AddLockedStandardMesh
    (
      GameObject go, 
      bool autoRigging, 
      bool async, 
      Mesh mesh = null, 
      bool generateTextureUV = false, 
      bool generateLightMapUV = false, 
      bool weldVertices = false, 
      bool optimizeMeshForRendering = false
    )
    {
      return null;
    }

    public virtual void LockMesh
    (
      bool autoRigging, 
      bool async, 
      Mesh mesh = null, 
      bool generateTextureUV = false, 
      bool generateLightMapUV = false, 
      bool weldVertices = false, 
      bool optimizeMeshForRendering = false
    )
    {
      m_isMeshLocked = true;
    }

    public void RememberLockedMeshMaterial()
    {
      var meshRenderer = GetComponent<MeshRenderer>();
      if (meshRenderer != null)
      {
        m_lastLockedMeshMaterial = meshRenderer.sharedMaterial;
      }
      else
      {
        var skinnedMeshRenderer = GetComponent<SkinnedMeshRenderer>();
        if (skinnedMeshRenderer != null)
          m_lastLockedMeshMaterial = skinnedMeshRenderer.sharedMaterial;
      }
    }

    public virtual void UnlockMesh()
    {
      m_isMeshLocked = false;
      MarkNeedsCompute();
      RestoreBoneTransforms();

      MarkNeedsCompute();
    }

    #endregion // end: Mesh Utilities

    //-------------------------------------------------------------------------

    #region SDF

    public bool DrawGenerateSdfGizmos = false; // editor only
    public string GenerateSdfByEditorName = ""; // editor only
    public Vector3 GenerateSdfCenter = Vector3.zero;
    public Vector3 GenerateSdfDimension = Vector3.one;
    public Vector3Int GenerateSdfTextureSize = new Vector3Int(64, 64, 64);
    [NonSerialized] protected List<Sdf.EvalJobHandle> m_jobQueue = new List<Sdf.EvalJobHandle>();
    [NonSerialized] protected List<Sdf.EvalJobHandle> m_jobCompleteQueue = new List<Sdf.EvalJobHandle>();

    public void ValidateAssetNames()
    {
      if (GenerateColliderMeshAssetByEditorName.Equals(""))
        GenerateColliderMeshAssetByEditorName = $"{gameObject.name} Collider Mesh {(uint)gameObject.GetInstanceID()}";

      if (GenerateMeshAssetByEditorName.Equals(""))
        GenerateMeshAssetByEditorName = $"{gameObject.name} Mesh {(uint) gameObject.GetInstanceID()}";

      if (GenerateSdfByEditorName.Equals(""))
        GenerateSdfByEditorName = $"{gameObject.name} SDF {(uint) gameObject.GetInstanceID()}";
    }

    public virtual void GenerateSdf
    (
      RenderTexture sdf, 
      Vector3 origin, 
      Vector3 dimension
    )
    {
      if (sdf == null)
        return;

      ValidateResources();
      SetUpResources();

      ComputeManager.SetTexture(Const.SdfOutput, sdf);
      ComputeManager.SetInts(Const.SdfOutputSize, new int[] { sdf.width, sdf.height, sdf.volumeDepth });
      ComputeManager.SetVector(Const.SdfCenter, origin);
      ComputeManager.SetVector(Const.SdfDimension, dimension);
      ComputeManager.Dispatch
      (
        s_computeSdfGen, 
        Const.Kernel.GenerateSdf,
        (sdf.width + ThreadGroupExtent - 1) / ThreadGroupExtent, 
        (sdf.height + ThreadGroupExtent - 1) / ThreadGroupExtent, 
        (sdf.volumeDepth + ThreadGroupExtent - 1) / ThreadGroupExtent
      );
    }

    public virtual void GenerateSdf
    (
      Texture3D sdf, 
      Vector3 origin, 
      Vector3 dimension
    )
    {
      if (sdf == null)
        return;

      var rt = new RenderTexture(sdf.width, sdf.height, 0, RenderTextureFormat.RFloat);
      rt.dimension = TextureDimension.Tex3D;
      rt.volumeDepth = sdf.depth;
      rt.enableRandomWrite = true;
      rt.Create();

      GenerateSdf(rt, origin, dimension);
      TextureUtil.RenderTextureToTexture3D(sdf, rt);

      rt.Release();
    }

    internal void UpdateComputeData()
    {
      Assert.True(m_initialized, "Renderer not initialized. Either an automatic call to OnEnable() or a manual call to Init() needs to be done before any computation.");

      if (m_needRescanBrushes)
      {
        RescanBrushesImmediate();
        m_needRescanBrushes = false;
      }

      UpdateNeedsCompute();
      UpdateBrushData();
      UpdateAabbTreeData();
    }

    public static MudBrushBase LookupBrush(float hash)
    {
      return s_brushMap.TryGetValue(hash, out var brush) ? brush : null;
    }

    #endregion // end: SDf

    //-------------------------------------------------------------------------

    #region Optimization

    public bool UseCutoffVolume = false;
    public Transform CutoffVolumeCenter;
    public Vector3 CutoffVolumeSize = Vector3.one;

    /*
    public Transform OptimizationRendererReference;
    public Transform OptimizationCameraRefernce;

    public bool EnableDistanceLod = false;
    public float DistanceLodStartDistance = 10.0f;
    public float DistanceLodFadeDistance = 5.0f;
    public float DistanceLodFarVoxelDensity = 4.0f;

    public bool EnableDistanceFade = false;
    public float DistanceFadeFarStartDistance = 10.0f;
    public float DistanceFadeFarFadeDistance = 5.0f;
    public float DistanceFadeNearStartDistance = 1.0f;
    public float DistanceFadeNearEndDistance = 1.0f;
    */

    #endregion // end: Optimization

    //-------------------------------------------------------------------------

    #region Callbacks

    protected virtual void OnSharedMaterialChanged(UnityEngine.Object material) { }

    protected virtual void InitBeforeFirstRenderer()
    {
      Sdf.InitEvalMap();
    }

    protected virtual void CleanUpAfterLastRenderer()
    {
      DisposeGlobalResources();
      Sdf.DisposeEvalMap();
    }

    private void Start()
    {
      if ((MeshGenerationLockOnStart || (MeshGenerationLockOnStartByEditor && MeshGenerationRenderableMeshMode == RenderableMeshMode.Procedural)) 
          && Application.isPlaying 
          && !m_isMeshLocked)
      {
        if (MeshGenerationCreateCollider)
          AddCollider(gameObject, false, null, MeshGenerationForceConvexCollider, MeshGenerationCreateRigidBody);

        MarkNeedsCompute();
        LockMesh(MeshGenerationAutoRigging, false);
      }
    }

    protected virtual void OnEnable() { Init(); }
    protected virtual void OnDisable() { ShutDown();}

    public void Init()
    {
      if (m_initialized)
        return;

      Janitor.Init();

      if (s_renderers.Count == 0)
      {
        InitBeforeFirstRenderer();
      }

      s_renderers.Add(this);
      m_rendererIndex = s_renderers.Count - 1;

      m_needRescanBrushes = true;
      MarkNeedsCompute();

      m_aSdfBrush = new NativeArray<SdfBrush>(MaxBrushes, Allocator.Persistent);
      m_aSdfBrushMaterial = new NativeArray<SdfBrushMaterial>(MaxBrushes, Allocator.Persistent);
      m_sdfBrushMaterialIndexMap = new Dictionary<int, int>();

      m_aabbTree = new AabbTree<MudBrushBase>(AabbTreeFatBoundsRadius);

      MarkNeedsCompute();

      MudSharedMaterialBase.OnSharedMaterialChanged += OnSharedMaterialChanged;

      m_previousTrackedVersion = m_currentTrackedVersion;
      m_currentTrackedVersion = MudBun.Version;
      if (m_firstTrackedVersion.Equals(""))
        m_firstTrackedVersion = m_currentTrackedVersion;
      if (m_previousTrackedVersion.Equals(""))
        m_previousTrackedVersion = m_currentTrackedVersion;

      m_initialized = true;
    }

    public void ShutDown()
    {
      if (!m_initialized)
        return;

      m_initialized = false;

      // complete lingering jobs
      for (int i = 0; i < m_jobCompleteQueue.Count; ++i)
      {
        m_jobCompleteQueue[i].Complete();
      }
      m_jobCompleteQueue.Clear();

      ClearBrushes();

      DisposeLocalResources();

      if (m_aSdfBrush.IsCreated)
        m_aSdfBrush.Dispose();
      if (m_aSdfBrushMaterial.IsCreated)
        m_aSdfBrushMaterial.Dispose();
      m_sdfBrushMaterialIndexMap = null;

      m_aabbTree.Dispose();
      m_aabbTree = null;

      MudSharedMaterialBase.OnSharedMaterialChanged -= OnSharedMaterialChanged;
      s_renderers.Remove(this);
      m_rendererIndex = -1;

      if (s_renderers.Count == 0)
      {
        CleanUpAfterLastRenderer();
      }
    }

    protected virtual void OnValidate()
    {
      SanitizeParameters();
      m_numVoxelsHighWaterMark = 0;
      m_numChunksHighWaterMark = 0;

      if (!m_isMeshLocked)
        MarkNeedsCompute();
    }

    internal void OnBrushDisabled(MudBrushBase brush)
    {
      RemoveBrush(brush);
    }

    // SDF evaluation interface would need brush & AABB tree data ready before Compute()
    // don't update it again for Compute() if they are already updated
    private bool m_brushDataDirty = false;
    private bool m_aabbTreeDirty = false;

    public void MarkNeedsCompute()
    {
      m_needsCompute = true;
      m_brushDataDirty = true;
      m_aabbTreeDirty = true;
    }
    private bool m_needsCompute = false;
    private bool UpdateNeedsCompute()
    {
      if (MeshLocked)
      {
        bool needsCompute = false;
        if (MeshGenerationRenderableMeshMode == RenderableMeshMode.Procedural)
          needsCompute = m_needsCompute;

        m_needsCompute = false;
        return needsCompute;
      }

      switch (ComputeMode)
      {
        case ComputeModeEnum.Auto:
          break;

        case ComputeModeEnum.Manual:
        case ComputeModeEnum.ManualNoRender:
          return m_needsCompute;
          
        case ComputeModeEnum.EveryFrame:
          MarkNeedsCompute();
          return true;

        case ComputeModeEnum.TimeSliced:
          switch (TimeSliceMode)
          {
            case TimeSliceModeEnum.ByFramesAutoOffset:
              if (((Time.frameCount + m_rendererIndex) % TimeSliceFrames) != 0)
                return false;
              break;

            case TimeSliceModeEnum.ByFramesManualOffset:
              if (((Time.frameCount + TimeSliceFramesOffset) % TimeSliceFrames) != 0)
                return false;
              break;

            case TimeSliceModeEnum.ByPeriodAutoOffset:
            {
              int q = (int) Mathf.Floor((Time.time + Mathf.Repeat(m_rendererIndex * Time.deltaTime, TimeSlicePeriod)) / TimeSlicePeriod);
              float qT = q * TimeSlicePeriod;
              if (qT <= m_lastUpdateTimeSliceTime)
                return false;

              m_lastUpdateTimeSliceTime = qT;
              break;
            }

            case TimeSliceModeEnum.ByPeriodManualOffset:
            {
              int q = (int) Mathf.Floor((Time.time + Mathf.Repeat(TimeSliceTimeOffset, TimeSlicePeriod)) / TimeSlicePeriod);
              float qT = q * TimeSlicePeriod;
              if (qT <= m_lastUpdateTimeSliceTime)
                return false;

              m_lastUpdateTimeSliceTime = qT;
              break;
            }
          }
          break;
      }

      if (m_needsCompute)
        return true;

      Profiler.BeginSample("UpdateNeedsCompute");

      // check need for rendering geometry type change
      if (m_needsCompute)
      {
        var currRenderGeometryType = RenderGeometryType;
        bool renderGeometryTypeChanged = (currRenderGeometryType != m_prevRenderGeometryType);
        m_prevRenderGeometryType = currRenderGeometryType;
        if (renderGeometryTypeChanged)
          m_needsCompute = true;
      }

      // check dirty brushes
      if (!m_needsCompute)
      {
        foreach (var b in m_aBrush)
        {
          if (!b.m_dirty && !b.transform.hasChanged)
            continue;

          MarkNeedsCompute();
          break;
        }
      }

      // check animation
      if (!m_needsCompute)
      {
        if (TryGetComponent(out Animation animation) 
            && animation.isPlaying)
        {
          MarkNeedsCompute();
        }
      }

      // check animator
      if (!m_needsCompute)
      {
        if (TryGetComponent(out Animator animator) 
            && animator.layerCount > 0)
        {
          var animState = animator.GetCurrentAnimatorStateInfo(0);
          if (animState.length > 0.0f && animState.speed > 0.0f && animState.speedMultiplier > 0.0f)
            MarkNeedsCompute();
        }
      }

      if (m_needsCompute)
      {
        Profiler.BeginSample("Clear Transform Change Flags");

        foreach (var b in m_aBrush)
        {
          b.m_dirty = false;
          b.transform.hasChanged = false;
        }

        Profiler.EndSample();
      }

      Profiler.EndSample();

      return m_needsCompute;
    }

    public RenderMaterialModeEnum RenderMaterialMode = RenderMaterialModeEnum.Static;
    public void MarkRenderMaterialDirty() { m_renderMaterialDirty = true; }
    private bool m_renderMaterialDirty = true;

    protected virtual bool IsEditorBusy() { return false; }

    private int m_numVoxelsHighWaterMark = 0;
    private int m_numChunksHighWaterMark = 0;
    private void TryAutoAdjustBudgets()
    {

      if (!Application.isEditor)
        return;

      if (!ShowGpuMemoryUsage)
      {
        m_autoAdjustBudgetsToHighWaterMarks = false;
        return;
      }

      if (!AutoAdjustBudgetsToHighWaterMarks)
      {
        m_autoAdjustBudgetsToHighWaterMarks = false;
        return;
      }

      Profiler.BeginSample("TryAutoAdjustBudgets");

      if (AutoAdjustBudgetsToHighWaterMarks && !m_autoAdjustBudgetsToHighWaterMarks)
      {
        m_numVoxelsHighWaterMark = 0;
        m_numChunksHighWaterMark = 0;
      }
      m_autoAdjustBudgetsToHighWaterMarks = AutoAdjustBudgetsToHighWaterMarks;

      m_numVoxelsHighWaterMark = Mathf.Max(NumVoxelsUsed, m_numVoxelsHighWaterMark);
      m_numChunksHighWaterMark = Mathf.Max(NumChunksUsed, m_numChunksHighWaterMark);

      int effectiveNumVoxelsHighWaterMark = m_numVoxelsHighWaterMark;
      if (ShouldDoAutoSmoothing && EnableSmoothCorner)
      {
        effectiveNumVoxelsHighWaterMark = Mathf.Max(effectiveNumVoxelsHighWaterMark, NumVerticesGenerated / VoxelToVertexFactor);
      }

      MaxVoxelsK = Mathf.Max(1, (int) Mathf.Ceil(effectiveNumVoxelsHighWaterMark * (1.0f + AutoAdjustBudgetsToHighWaterMarksMarginPercent / 100.0f) / 1024.0f));
      MaxChunks = Mathf.Max(16, (int) Mathf.Ceil(m_numChunksHighWaterMark * (1.0f + AutoAdjustBudgetsToHighWaterMarksMarginPercent / 100.0f)));

      Profiler.EndSample();
    }

    protected virtual bool PreUpdateValidate() { return true; }

    protected void LateUpdate()
    {
      UpdateComputeData();

      if (m_jobQueue.Count > 0)
      {
        // complete lingering jobs
        for (int i = 0; i < m_jobCompleteQueue.Count; ++i)
        {
          m_jobCompleteQueue[i].Complete();
        }
        m_jobCompleteQueue.Clear();

        // schedule queued jobs
        for (int i = 0; i < m_jobQueue.Count; ++i)
        {
          m_jobQueue[i].Schedule(true);
          m_jobCompleteQueue.Add(m_jobQueue[i]);
        }
        JobHandle.ScheduleBatchedJobs();
        m_jobQueue.Clear();
      }

      if (!PreUpdateValidate())
        return;

      if (m_isMeshLocked)
      {
        switch (MeshGenerationRenderableMeshMode)
        {
          case RenderableMeshMode.Procedural:
            // keep on rendering in procedural mode
            break;

          default:
            return;
        }
      }

      TryCompute();

      switch (ComputeMode)
      {
        case ComputeModeEnum.ManualNoRender:
          break;

        default:
          Render();
          break;
      }
    }

    public void ForceCompute()
    {
      TryCompute(true);
    }

    public virtual void ReloadShaders()
    {
      DisposeGlobalResources();
      DisposeLocalResources();
      MarkNeedsCompute();
    }

    private void TryCompute(bool forceCompute = false)
    {
      if (IsEditorBusy())
        return;

      TryAutoAdjustBudgets();

      SanitizeParameters();
      if (!ValidateResources())
        return;

      if (!s_globalResourcesValid || !m_localResourcesValid)
        return;

      if (forceCompute || m_needsCompute)
      {
        switch (HardwareMode)
        {
        case HardwareModeEnum.Gpu:
          ComputeGpu();
          break;

        /*
        case HardwareModeEnum.Cpu:
          ComputeCpu();
          break;
        */
        }

        m_needsCompute = false;
      }
    }

    private void SanitizeParameters()
    {
      Validate.Range(-1, VoxelNodeDepth, ref DrawVoxelNodesDepth);

      Validate.NonNegative(ref RayTracedVoxelInternalPaddingDistance);
      Validate.NonNegative(ref RayTracedVoxelSizeFadeDistance);
    }

    #if MUDBUN_FREE
    private static readonly int s_watermarkWidth = 300;
    private static readonly int s_watermariHeight = 100;
    private static readonly string s_watermarkStr = " ";
    private static Texture2D s_watermark;
    private void OnGUI()
    {
      switch (Event.current.type)
      {
        case EventType.Repaint:
          //if (MudBun.IsFreeVersion)
          {
            if (s_watermark == null)
            {
              s_watermark = new Texture2D(s_watermarkWidth, s_watermariHeight);
              s_watermark.LoadImage(Convert.FromBase64String(s_watermarkStr));
            }

            // just let one renderer draw
            var itRenderer = s_renderers.GetEnumerator();
            if (!itRenderer.MoveNext() || itRenderer.Current != this)
              return;

            Graphics.DrawTexture(new Rect(0.0f, Screen.height - s_watermark.height, s_watermark.width, s_watermark.height), s_watermark);
          }
          break;
      }
    }
    #endif

    #endregion // end: Callbacks

    //-------------------------------------------------------------------------

    #region Resources

    private bool ValidateResources()
    {
      Profiler.BeginSample("ValidateResources");
      
      if (m_needRescanBrushes)
      {
        RescanBrushesImmediate();
        m_needRescanBrushes = false;
      }

      if (!ValidateGlobalResources())
        return false;

      if (!ValidateLocalResources())
        return false;

      /*
      foreach (var b in m_aBrush)
        b.ValidateMaterial();
      */

      Profiler.EndSample();

      return true;
    }

    private static bool s_shaderConstantIdPopulated = false;
    public static void ValidateShaderConstantId()
    {
      if (s_shaderConstantIdPopulated)
        return;

      Const.TriTable = Shader.PropertyToID("triTable");
      Const.VertTable = Shader.PropertyToID("vertTable");
      Const.TriTable2d = Shader.PropertyToID("triTable2d");

      Const.Brushes = Shader.PropertyToID("aBrush");
      Const.BrushMaterials = Shader.PropertyToID("aBrushMaterial");
      Const.NumBrushes = Shader.PropertyToID("numBrushes");

      Const.SurfaceShift = Shader.PropertyToID("surfaceShift");

      Const.RenderMode = Shader.PropertyToID("renderMode");
      Const.MeshingMode = Shader.PropertyToID("meshingMode");
      Const.RayTracedVoxelMode = Shader.PropertyToID("rayTracedVoxelMode");
      Const.RayTracedVoxelSizeMultiplier = Shader.PropertyToID("rayTracedVoxelSizeMultiplier");
      Const.RayTracedVoxelSmoothCubeNormal = Shader.PropertyToID("rayTracedVoxelSmoothCubeNormal");
      Const.RayTracedVoxelRadius = Shader.PropertyToID("rayTracedVoxelRadius");
      Const.RayTracedVoxelPaddingMode = Shader.PropertyToID("rayTracedVoxelPaddingMode");
      Const.RayTracedVoxelInternalPaddingDistance = Shader.PropertyToID("rayTracedVoxelInternalPaddingDistance");
      Const.RayTracedVoxelSizeFadeDistance = Shader.PropertyToID("rayTracedVoxelSizeFadeDistance");

      Const.NormalDifferentiationStep = Shader.PropertyToID("normalDifferentiationStep");
      Const.NormalQuantization = Shader.PropertyToID("normalQuantization");
      Const.Normal2dFadeDist = Shader.PropertyToID("normal2dFadeDist");
      Const.Normal2dStrength = Shader.PropertyToID("normal2dStrength");
      Const.EnableAutoSmooth = Shader.PropertyToID("enableAutoSmooth");
      Const.AutoSmoothMaxAngle = Shader.PropertyToID("autoSmoothMaxAngle");
      Const.AutoSmoothVertDataTable = Shader.PropertyToID("autoSmoothVertDataTable");
      Const.AutoSmoothVertDataPoolSize = Shader.PropertyToID("autoSmoothVertDataPoolSize");
      Const.EnableSmoothCorner = Shader.PropertyToID("enableSmoothCorner");
      Const.SmoothCornerSubdivision = Shader.PropertyToID("smoothCornerSubdivision");
      Const.SmoothCornerNormalBlur = Shader.PropertyToID("smoothCornerNormalBlur");
      Const.SmoothCornerFade = Shader.PropertyToID("smoothCornerFade");
      Const.InvertNormals = Shader.PropertyToID("invertNormals");

      Const.SplatSize = Shader.PropertyToID("splatSize");
      Const.SplatSizeJitter = Shader.PropertyToID("splatSizeJitter");
      Const.SplatNormalShift = Shader.PropertyToID("splatNormalShift");
      Const.SplatNormalShiftJitter = Shader.PropertyToID("splatNormalShiftJitter");
      Const.SplatPositionJitter = Shader.PropertyToID("splatPositionJitter");
      Const.SplatColorJitter = Shader.PropertyToID("splatColorJitter");
      Const.SplatRotationJitter = Shader.PropertyToID("splatRotationJitter");
      Const.SplatOrientationJitter = Shader.PropertyToID("splatOrientationJitter");
      Const.SplatOriginalNormalBlend = Shader.PropertyToID("splatOriginalNormalBlend");
      Const.SplatJitterNoisiness = Shader.PropertyToID("splatJitterNoisiness");
      Const.SplatCameraFacing = Shader.PropertyToID("splatCameraFacing");
      Const.SplatNormalsMatchCameraFacing = Shader.PropertyToID("splatNormalsMatchCameraFacing");
      Const.SplatShadowsMatchCameraFacing = Shader.PropertyToID("splatShadowsMatchCameraFacing");
      Const.SplatScreenSpaceFlattening = Shader.PropertyToID("splatScreenSpaceFlattening");
      //Const.SplatSmoothNormalBlend = Shader.PropertyToID("splatSmoothNormalBlend");

      Const.SurfaceNetsDualQuadsBlend = Shader.PropertyToID("surfaceNetsDualQuadsBlend");
      Const.SurfaceNetsBinarySearchIterations = Shader.PropertyToID("surfaceNetsBinarySearchIterations");
      Const.SurfaceNetsGradientDescentIterations = Shader.PropertyToID("surfaceNetsGradientDescentIterations");
      Const.SurfaceNetsGradientDescentFactor = Shader.PropertyToID("surfaceNetsGradientDescentFactor");

      Const.DualContouringDualQuadsBlend = Shader.PropertyToID("dualContouringDualQuadsBlend");
      Const.DualContouringRelaxation = Shader.PropertyToID("dualContouringRelaxation");
      Const.DualContouringSolverIterations = Shader.PropertyToID("dualContouringSolverIterations");
      Const.DualContouringBinarySearchIterations = Shader.PropertyToID("dualContouringBinarySearchIterations");
      Const.DualContouringGradientDescentIterations = Shader.PropertyToID("dualContouringGradientDescentIterations");
      Const.DualContouringGradientDescentFactor = Shader.PropertyToID("dualContouringGradientDescentFactor");

      Const.AabbTree = Shader.PropertyToID("aabbTree");
      Const.AabbRoot = Shader.PropertyToID("aabbRoot");

      Const.Enable2dMode = Shader.PropertyToID("enable2dMode");
      Const.ForceAllBrushes = Shader.PropertyToID("forceAllBrushes");
      Const.NumAllocations = Shader.PropertyToID("aNumAllocation");
      Const.NodeHashTable = Shader.PropertyToID("nodeHashTable");
      Const.NodeHashTableSize = Shader.PropertyToID("nodeHashTableSize");
      Const.NodePool = Shader.PropertyToID("nodePool");
      Const.NodePoolSize = Shader.PropertyToID("nodePoolSize");
      Const.NumNodesAllocated = Shader.PropertyToID("aNumNodesAllocated");
      Const.UseVoxelCache = Shader.PropertyToID("useVoxelCache");
      Const.VoxelCacheIdTable = Shader.PropertyToID("voxelCacheIdTable");
      Const.VoxelCache = Shader.PropertyToID("voxelCache");
      Const.VoxelCacheSize = Shader.PropertyToID("voxelCacheSize");
      Const.BrushMaskPool = Shader.PropertyToID("brushMaskPool");
      Const.BrushMaskPoolSize = Shader.PropertyToID("brushMaskPoolSize");
      Const.IndirectDispatchArgs = Shader.PropertyToID("indirectDispatchArgs");
      Const.CurrentNodeDepth = Shader.PropertyToID("currentNodeDepth");
      Const.CurrentNodeBranchingFactor = Shader.PropertyToID("currentNodeBranchingFactor");
      Const.CurrentNodeSize = Shader.PropertyToID("currentNodeSize");
      Const.VoxelSize = Shader.PropertyToID("voxelSize");
      Const.VoxelTreeBranchingFactorsCompressed = Shader.PropertyToID("voxelTreeBranchingFactorsCompressed");
      Const.VoxelNodeSizes = Shader.PropertyToID("voxelNodeSizes");
      Const.MaxNodeDepth = Shader.PropertyToID("maxNodeDepth");
      Const.ChunkVoxelDensity = Shader.PropertyToID("chunkVoxelDensity");
      Const.GenPoints = Shader.PropertyToID("aGenPoint");
      Const.MaxGenPoints = Shader.PropertyToID("maxGenPoints");
      Const.IndirectDrawArgs = Shader.PropertyToID("indirectDrawArgs");

      Const.MasterColor = Shader.PropertyToID("_Color");
      Const.MasterEmission = Shader.PropertyToID("_Emission");
      Const.MasterMetallic = Shader.PropertyToID("_Metallic");
      Const.MasterSmoothness = Shader.PropertyToID("_Smoothness");

      Const.ScaleSign = Shader.PropertyToID("scaleSign");
      Const.LocalToWorld = Shader.PropertyToID("localToWorld");
      Const.LocalToWorldIt = Shader.PropertyToID("localToWorldIt");
      Const.LocalToWorldScale = Shader.PropertyToID("localToWorldScale");
      Const.WorldToLocal = Shader.PropertyToID("worldToLocal");
      Const.WorldToLocalIt = Shader.PropertyToID("worldToLocalIt");

      Const.NoiseCache = Shader.PropertyToID("noiseCache");
      Const.NoiseCacheDimension = Shader.PropertyToID("noiseCacheDimension");
      Const.NoiseCacheDensity = Shader.PropertyToID("noiseCacheDensity");
      Const.NoiseCachePeriod = Shader.PropertyToID("noiseCachePeriod");

      Const.SdfOutput = Shader.PropertyToID("sdfOutput");
      Const.SdfOutputSize = Shader.PropertyToID("sdfOutputSize");
      Const.SdfCenter = Shader.PropertyToID("sdfCenter");
      Const.SdfDimension = Shader.PropertyToID("sdfDimension");

      Const.MaxRayMarchSteps = Shader.PropertyToID("maxRayMarchSteps");
      Const.RayMarchHitDistance = Shader.PropertyToID("rayMarchHitDistance");
      Const.RayMarchMaxRayDistance = Shader.PropertyToID("rayMarchMaxRayDistance");

      Const.MeshGenerationAutoRiggingAlgorithm = Shader.PropertyToID("autoRiggingAlgorithm");

      /*
      Const.NumLightMarchSteps = Shader.PropertyToID("numLightMarchSteps");
      Const.RayMarchStepSize = Shader.PropertyToID("rayMarchStepSize");
      Const.RayMarchVolumeDensity = Shader.PropertyToID("rayMarchVolumeDensity");
      Const.RayMarchLightPositionType = Shader.PropertyToID("rayMarchLightPositionType");
      Const.RayMarchLightDirection = Shader.PropertyToID("rayMarchLightDirection");
      Const.RayMarchAbsorption = Shader.PropertyToID("rayMarchAbsorption");
      Const.RayMarchDarknesThreshold = Shader.PropertyToID("rayMarchDarknesThreshold");
      Const.RayMarchTransmittanceCurve = Shader.PropertyToID("rayMarchTransmittanceCurve");
      Const.RayMarchNoiseEdgeFade = Shader.PropertyToID("rayMarchNoiseEdgeFade");
      Const.RayMarchNoiseThreshold = Shader.PropertyToID("rayMarchNoiseThreshold");
      Const.RayMarchNoiseScrollSpeed = Shader.PropertyToID("rayMarchNoiseScrollSpeed");
      Const.RayMarchNoiseBaseOctaveSize = Shader.PropertyToID("rayMarchNoiseBaseOctaveSize");
      Const.RayMarchNoiseNumOctaves = Shader.PropertyToID("rayMarchNoiseNumOctaves");
      Const.RayMarchNoiseOctaveOffsetFactor = Shader.PropertyToID("rayMarchNoiseOctaveOffsetFactor");
      */

      Const.IsMeshRenderMaterial = Shader.PropertyToID("_IsMeshRenderMaterial");
      Const.IsSplatRenderMaterial = Shader.PropertyToID("_IsSplatRenderMaterial");
      Const.MaterialNeedsSdfProperties= Shader.PropertyToID("_MaterialNeedsSdfProperties");
      Const.MaterialNeedsRayMarchingProperties = Shader.PropertyToID("_MaterialNeedsRayMarchingProperties");

      s_shaderConstantIdPopulated = true;
    }

    private static void RegisterCommonMeshingConstants(ComputeShader shader)
    {
      ComputeManager.RegisterConstantId(shader, Const.RenderMode);
      ComputeManager.RegisterConstantId(shader, Const.MeshingMode);

      ComputeManager.RegisterConstantId(shader, Const.NormalDifferentiationStep);
      ComputeManager.RegisterConstantId(shader, Const.NormalQuantization);
      ComputeManager.RegisterConstantId(shader, Const.Normal2dFadeDist);
      ComputeManager.RegisterConstantId(shader, Const.Normal2dStrength);
      ComputeManager.RegisterConstantId(shader, Const.EnableAutoSmooth);
      ComputeManager.RegisterConstantId(shader, Const.AutoSmoothMaxAngle);
      ComputeManager.RegisterConstantId(shader, Const.AutoSmoothVertDataTable);
      ComputeManager.RegisterConstantId(shader, Const.AutoSmoothVertDataPoolSize);
      ComputeManager.RegisterConstantId(shader, Const.EnableSmoothCorner);
      ComputeManager.RegisterConstantId(shader, Const.SmoothCornerSubdivision);
      ComputeManager.RegisterConstantId(shader, Const.SmoothCornerNormalBlur);
      ComputeManager.RegisterConstantId(shader, Const.SmoothCornerFade);
      ComputeManager.RegisterConstantId(shader, Const.InvertNormals);

      ComputeManager.RegisterConstantId(shader, Const.NodeHashTableSize);
      ComputeManager.RegisterConstantId(shader, Const.NodePoolSize);
      ComputeManager.RegisterConstantId(shader, Const.UseVoxelCache);
      ComputeManager.RegisterConstantId(shader, Const.VoxelCacheSize);

      ComputeManager.RegisterConstantId(shader, Const.BrushMaskPoolSize);
      ComputeManager.RegisterConstantId(shader, Const.Brushes);
      ComputeManager.RegisterConstantId(shader, Const.NumBrushes);
      ComputeManager.RegisterConstantId(shader, Const.BrushMaterials);

      ComputeManager.RegisterConstantId(shader, Const.SurfaceShift);

      ComputeManager.RegisterConstantId(shader, Const.AabbTree);
      ComputeManager.RegisterConstantId(shader, Const.AabbRoot);

      ComputeManager.RegisterConstantId(shader, Const.Enable2dMode);
      ComputeManager.RegisterConstantId(shader, Const.ForceAllBrushes);
      ComputeManager.RegisterConstantId(shader, Const.NodeHashTable);
      ComputeManager.RegisterConstantId(shader, Const.NodePool);
      ComputeManager.RegisterConstantId(shader, Const.NumNodesAllocated);
      ComputeManager.RegisterConstantId(shader, Const.NumAllocations);
      ComputeManager.RegisterConstantId(shader, Const.VoxelCacheIdTable);
      ComputeManager.RegisterConstantId(shader, Const.VoxelCache);
      ComputeManager.RegisterConstantId(shader, Const.BrushMaskPool);

      ComputeManager.RegisterConstantId(shader, Const.IndirectDispatchArgs);
      ComputeManager.RegisterConstantId(shader, Const.GenPoints);
      ComputeManager.RegisterConstantId(shader, Const.IndirectDrawArgs);
      ComputeManager.RegisterConstantId(shader, Const.VoxelSize);
      ComputeManager.RegisterConstantId(shader, Const.VoxelTreeBranchingFactorsCompressed);
      ComputeManager.RegisterConstantId(shader, Const.VoxelNodeSizes);
      ComputeManager.RegisterConstantId(shader, Const.MaxNodeDepth);
      ComputeManager.RegisterConstantId(shader, Const.ChunkVoxelDensity);

      ComputeManager.RegisterConstantId(shader, Const.NoiseCache);
      ComputeManager.RegisterConstantId(shader, Const.NoiseCacheDimension);
      ComputeManager.RegisterConstantId(shader, Const.NoiseCacheDensity);

      ComputeManager.RegisterConstantId(shader, Const.CurrentNodeDepth);
      ComputeManager.RegisterConstantId(shader, Const.CurrentNodeBranchingFactor);
      ComputeManager.RegisterConstantId(shader, Const.CurrentNodeSize);
    }

    public static bool ValidateComputeShaders()
    {
      if (s_computeVoxelGen != null 
          && s_computeMarchingCubes != null 
          && s_computeDualMeshing != null 
          && s_computeNoiseCache != null 
          && s_computeMeshLock != null)
          return true;

      ComputeManager.Reset();

      ValidateShaderConstantId();

      // voxel gen
      {
        s_computeVoxelGen = ResourcesUtil.VoxelGen;
        if (s_computeVoxelGen == null)
          return false;

        ComputeManager.RegisterShader(s_computeVoxelGen);

        Const.Kernel.ClearVoxelHashTable = 
          ComputeManager.RegisterKernel(s_computeVoxelGen, "clear_voxel_hash_table");
        Const.Kernel.ClearAutoSmoothVertDataTable = 
          ComputeManager.RegisterKernel(s_computeVoxelGen, "clear_auto_smooth_vert_data_table");
        Const.Kernel.ClearVoxelCache = 
          ComputeManager.RegisterKernel(s_computeVoxelGen, "clear_voxel_cache");
        Const.Kernel.RegisterTopNodes = 
          ComputeManager.RegisterKernel(s_computeVoxelGen, "register_top_nodes");
        Const.Kernel.UpdateBranchingIndirectDispatchArgs = 
          ComputeManager.RegisterKernel(s_computeVoxelGen, "update_branching_indirect_dispatch_args");
        Const.Kernel.AllocateChildNodes = 
          ComputeManager.RegisterKernel(s_computeVoxelGen, "allocate_child_nodes");
        Const.Kernel.UpdateVoxelIndirectDispatchArgs = 
          ComputeManager.RegisterKernel(s_computeVoxelGen, "update_voxel_indirect_dispatch_args");

        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.RenderMode);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.RayTracedVoxelMode);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.RayTracedVoxelPaddingMode);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.RayTracedVoxelInternalPaddingDistance);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.RayTracedVoxelSizeFadeDistance);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NumBrushes);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NodeHashTableSize);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NodePoolSize);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.BrushMaskPoolSize);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.Brushes);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.BrushMaterials);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.SurfaceShift);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.AabbTree);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.AabbRoot);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.Enable2dMode);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.ForceAllBrushes);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NodeHashTable);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NodePool);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NumNodesAllocated);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NumAllocations);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.BrushMaskPool);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.IndirectDispatchArgs);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.GenPoints);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.IndirectDrawArgs);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.VoxelSize);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.VoxelTreeBranchingFactorsCompressed);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.VoxelNodeSizes);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.MaxNodeDepth);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.ChunkVoxelDensity);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NoiseCache);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NoiseCacheDimension);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NoiseCacheDensity);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.CurrentNodeDepth);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.CurrentNodeBranchingFactor);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.CurrentNodeSize);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NormalDifferentiationStep);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.EnableAutoSmooth);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.AutoSmoothMaxAngle);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.AutoSmoothVertDataTable);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.AutoSmoothVertDataPoolSize);
      } // end: voxel gen

      // marching cubes
      {
        s_computeMarchingCubes = ResourcesUtil.MarchingCubes;
        if (s_computeMarchingCubes == null)
          return false;

        ComputeManager.RegisterShader(s_computeMarchingCubes);

        Const.Kernel.GenerateFlatMarchingCubesMesh = 
          ComputeManager.RegisterKernel(s_computeMarchingCubes, "generate_flat_marching_cubes_mesh");
        Const.Kernel.GenerateSmoothMarchingCubesMesh = 
          ComputeManager.RegisterKernel(s_computeMarchingCubes, "generate_smooth_marching_cubes_mesh");
        Const.Kernel.GenerateMarchingCubesSplats = 
          ComputeManager.RegisterKernel(s_computeMarchingCubes, "generate_marching_splats");
        Const.Kernel.GenerateFlatMarchingCubesMesh2d = 
          ComputeManager.RegisterKernel(s_computeMarchingCubes, "generate_flat_marching_cubes_mesh_2d");
        Const.Kernel.GenerateSmoothMarchingCubesMesh2d = 
          ComputeManager.RegisterKernel(s_computeMarchingCubes, "generate_smooth_marching_cubes_mesh_2d");
        Const.Kernel.GenerateMarchingCubesSplats2d = 
          ComputeManager.RegisterKernel(s_computeMarchingCubes, "generate_marching_splats_2d");
        Const.Kernel.UpdateMarchingCubesAutoSmoothIndirectDispatchArgs = 
          ComputeManager.RegisterKernel(s_computeMarchingCubes, "update_marching_cubes_auto_smooth_indirect_dispatch_args");
        Const.Kernel.MarchingCubesUpdateAutoSmooth = 
          ComputeManager.RegisterKernel(s_computeMarchingCubes, "marching_cubes_update_auto_smooth");
        Const.Kernel.MarchingCubesComputeAutoSmooth = 
          ComputeManager.RegisterKernel(s_computeMarchingCubes, "marching_cubes_compute_auto_smooth");

        RegisterCommonMeshingConstants(s_computeMarchingCubes);
        ComputeManager.RegisterConstantId(s_computeMarchingCubes, Const.TriTable);
        ComputeManager.RegisterConstantId(s_computeMarchingCubes, Const.VertTable);
        ComputeManager.RegisterConstantId(s_computeMarchingCubes, Const.TriTable2d);
      }

      // dual meshing
      {
        s_computeDualMeshing = ResourcesUtil.DualMeshing;
        if (s_computeDualMeshing == null)
          return false;

        ComputeManager.RegisterShader(s_computeDualMeshing);

        Const.Kernel.GenerateDualQuads = 
          ComputeManager.RegisterKernel(s_computeDualMeshing, "generate_dual_quads");
        Const.Kernel.GenerateDualQuads2d = 
          ComputeManager.RegisterKernel(s_computeDualMeshing, "generate_dual_quads_2d");
        Const.Kernel.UpdateDualMeshingIndirectDispatchArgs = 
          ComputeManager.RegisterKernel(s_computeDualMeshing, "update_dual_meshing_indirect_dispatch_args");
        Const.Kernel.DualMeshingFlatMeshNormal = 
          ComputeManager.RegisterKernel(s_computeDualMeshing, "dual_meshing_flat_mesh_normal");
        Const.Kernel.DualMeshingSmoothMeshNormal = 
          ComputeManager.RegisterKernel(s_computeDualMeshing, "dual_meshing_smooth_mesh_normal");
        Const.Kernel.DualMeshingFlatMeshNormal2d = 
          ComputeManager.RegisterKernel(s_computeDualMeshing, "dual_meshing_flat_mesh_normal_2d");
        Const.Kernel.DualMeshingSmoothMeshNormal2d = 
          ComputeManager.RegisterKernel(s_computeDualMeshing, "dual_meshing_smooth_mesh_normal_2d");
        Const.Kernel.DualMeshingUpdateAutoSmooth = 
          ComputeManager.RegisterKernel(s_computeDualMeshing, "dual_meshing_update_auto_smooth");
        Const.Kernel.DualMeshingComputeAutoSmooth = 
          ComputeManager.RegisterKernel(s_computeDualMeshing, "dual_meshing_compute_auto_smooth");
        Const.Kernel.DualMeshingUpdateSmoothCornerIndirectDispatchArgs = 
          ComputeManager.RegisterKernel(s_computeDualMeshing, "dual_meshing_udpate_auto_smooth_SmoothCorner_indirect_dispatch_args");
        Const.Kernel.DualMeshingSmoothCorner = 
          ComputeManager.RegisterKernel(s_computeDualMeshing, "dual_meshing_auto_smooth_smooth_corner");
        Const.Kernel.UpdateDualMeshingSplatsIndirectArgs = 
          ComputeManager.RegisterKernel(s_computeDualMeshing, "update_dual_meshing_splats_indirect_args");
        Const.Kernel.ConvertDualMeshingSplats = 
          ComputeManager.RegisterKernel(s_computeDualMeshing, "convert_dual_meshing_splats");

        RegisterCommonMeshingConstants(s_computeDualMeshing);
      }

      // surface nets
      {
        s_computeSurfaceNets = ResourcesUtil.SurfaceNets;
        if (s_computeSurfaceNets == null)
          return false;

        ComputeManager.RegisterShader(s_computeSurfaceNets);

        Const.Kernel.SurfaceNetsMovePoint = 
          ComputeManager.RegisterKernel(s_computeSurfaceNets, "surface_nets_move_point");

        Const.Kernel.SurfaceNetsMovePoint2d = 
          ComputeManager.RegisterKernel(s_computeSurfaceNets, "surface_nets_move_point_2d");

        RegisterCommonMeshingConstants(s_computeSurfaceNets);
        ComputeManager.RegisterConstantId(s_computeSurfaceNets, Const.SurfaceNetsDualQuadsBlend);
        ComputeManager.RegisterConstantId(s_computeSurfaceNets, Const.SurfaceNetsBinarySearchIterations);
        ComputeManager.RegisterConstantId(s_computeSurfaceNets, Const.SurfaceNetsGradientDescentIterations);
        ComputeManager.RegisterConstantId(s_computeSurfaceNets, Const.SurfaceNetsGradientDescentFactor);
      }

      // dual contouring
      {
        s_computeDualContouring = ResourcesUtil.DualContouring;
        if (s_computeDualContouring == null)
          return false;

        ComputeManager.RegisterShader(s_computeDualContouring);

        Const.Kernel.DualContouringMovePoint = 
          ComputeManager.RegisterKernel(s_computeDualContouring, "dual_contouring_move_point");
        Const.Kernel.DualContouringMovePoint2d = 
          ComputeManager.RegisterKernel(s_computeDualContouring, "dual_contouring_move_point_2d");

        RegisterCommonMeshingConstants(s_computeDualContouring);
        ComputeManager.RegisterConstantId(s_computeDualContouring, Const.DualContouringDualQuadsBlend);
        ComputeManager.RegisterConstantId(s_computeDualContouring, Const.DualContouringRelaxation);
        ComputeManager.RegisterConstantId(s_computeDualContouring, Const.DualContouringSolverIterations);
        ComputeManager.RegisterConstantId(s_computeDualContouring, Const.DualContouringBinarySearchIterations);
        ComputeManager.RegisterConstantId(s_computeDualContouring, Const.DualContouringGradientDescentIterations);
        ComputeManager.RegisterConstantId(s_computeDualContouring, Const.DualContouringGradientDescentFactor);
      }

      // ray-traced voxels
      {
        s_computeRayTracedVoxels = ResourcesUtil.RayTracedVoxels;
        if (s_computeRayTracedVoxels == null)
          return false;

        ComputeManager.RegisterShader(s_computeRayTracedVoxels);

        Const.Kernel.UpdateRayTracedVoxelIndirectDispatchArgs = 
          ComputeManager.RegisterKernel(s_computeRayTracedVoxels, "update_ray_traced_voxel_indirect_dispatch_args");
        Const.Kernel.ComputeRayTracedVoxelGenPoints = 
          ComputeManager.RegisterKernel(s_computeRayTracedVoxels, "compute_ray_traced_voxel_gen_points");
        Const.Kernel.ComputeRayTracedVoxelGenPointsWithNormals = 
          ComputeManager.RegisterKernel(s_computeRayTracedVoxels, "compute_ray_traced_voxel_gen_points_with_normals");

        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.RenderMode);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.RayTracedVoxelMode);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.RayTracedVoxelSizeMultiplier);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.RayTracedVoxelSmoothCubeNormal);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.RayTracedVoxelRadius);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.RayTracedVoxelPaddingMode);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.RayTracedVoxelInternalPaddingDistance);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.RayTracedVoxelSizeFadeDistance);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.NumBrushes);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.NodeHashTableSize);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.NodePoolSize);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.BrushMaskPoolSize);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.Brushes);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.BrushMaterials);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.SurfaceShift);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.AabbTree);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.AabbRoot);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.ForceAllBrushes);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.NodeHashTable);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.NodePool);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.NumNodesAllocated);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.NumAllocations);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.BrushMaskPool);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.IndirectDispatchArgs);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.GenPoints);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.IndirectDrawArgs);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.VoxelSize);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.VoxelTreeBranchingFactorsCompressed);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.VoxelNodeSizes);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.MaxNodeDepth);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.ChunkVoxelDensity);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.NoiseCache);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.NoiseCacheDimension);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.NoiseCacheDensity);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.CurrentNodeDepth);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.CurrentNodeBranchingFactor);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.CurrentNodeSize);
        ComputeManager.RegisterConstantId(s_computeRayTracedVoxels, Const.NormalDifferentiationStep);
      }

      // noise cache
      {
        s_computeNoiseCache = ResourcesUtil.NoiseCache;
        if (s_computeNoiseCache == null)
          return false;

        // switched to using based noise texture
        ComputeManager.RegisterShader(s_computeNoiseCache);

        Const.Kernel.GenerateNoiseCache = 
          ComputeManager.RegisterKernel(s_computeNoiseCache, "generate_noise_cache");

        ComputeManager.RegisterConstantId(s_computeNoiseCache, Const.NoiseCache);
        ComputeManager.RegisterConstantId(s_computeNoiseCache, Const.NoiseCacheDimension);
        ComputeManager.RegisterConstantId(s_computeNoiseCache, Const.NoiseCacheDensity);
        ComputeManager.RegisterConstantId(s_computeNoiseCache, Const.NoiseCachePeriod);
      } // end: noise cache

      // mesh lock
      {
        s_computeMeshLock = ResourcesUtil.MeshLock;
        if (s_computeMeshLock == null)
          return false;
          
        ComputeManager.RegisterShader(s_computeMeshLock);

        Const.Kernel.RigBones = 
          ComputeManager.RegisterKernel(s_computeMeshLock, "rig_bones");

        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.NumBrushes);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.BrushMaskPoolSize);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.Brushes);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.BrushMaterials);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.AabbTree);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.AabbRoot);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.NodeHashTable);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.NodePool);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.NumNodesAllocated);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.NumAllocations);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.VoxelCacheIdTable);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.VoxelCache);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.BrushMaskPool);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.IndirectDispatchArgs);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.GenPoints);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.MaxGenPoints);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.IndirectDrawArgs);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.VoxelSize);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.MaxNodeDepth);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.ChunkVoxelDensity);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.NoiseCache);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.NoiseCacheDimension);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.NoiseCacheDensity);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.CurrentNodeDepth);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.CurrentNodeBranchingFactor);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.CurrentNodeSize);
        ComputeManager.RegisterConstantId(s_computeMeshLock, Const.MeshGenerationAutoRiggingAlgorithm);
      } // end: mesh lock

      // SDF gen
      {
        s_computeSdfGen = ResourcesUtil.SdfGen;
        if (s_computeSdfGen == null)
          return false;

        ComputeManager.RegisterShader(s_computeSdfGen);

        Const.Kernel.GenerateSdf =
          ComputeManager.RegisterKernel(s_computeSdfGen, "generate_sdf");

        ComputeManager.RegisterConstantId(s_computeSdfGen, Const.SdfOutput);
        ComputeManager.RegisterConstantId(s_computeSdfGen, Const.SdfOutputSize);
        ComputeManager.RegisterConstantId(s_computeSdfGen, Const.SdfCenter);
        ComputeManager.RegisterConstantId(s_computeSdfGen, Const.SdfDimension);
        ComputeManager.RegisterConstantId(s_computeSdfGen, Const.Brushes);
        ComputeManager.RegisterConstantId(s_computeSdfGen, Const.NumBrushes);
        ComputeManager.RegisterConstantId(s_computeSdfGen, Const.BrushMaskPoolSize);
        ComputeManager.RegisterConstantId(s_computeSdfGen, Const.Brushes);
        ComputeManager.RegisterConstantId(s_computeSdfGen, Const.BrushMaterials);
        ComputeManager.RegisterConstantId(s_computeSdfGen, Const.AabbTree);
        ComputeManager.RegisterConstantId(s_computeSdfGen, Const.AabbRoot);
        ComputeManager.RegisterConstantId(s_computeSdfGen, Const.NoiseCache);
        ComputeManager.RegisterConstantId(s_computeSdfGen, Const.NoiseCacheDimension);
        ComputeManager.RegisterConstantId(s_computeSdfGen, Const.NoiseCacheDensity);
      } // end: SDF gen

      ComputeManager.ActivateAllShaders();

      // generate noise cache
      {
        if (s_noiseCache != null)
        {
          s_noiseCache.Release();
          s_noiseCache = null;
        }
        s_noiseCache = new RenderTexture(NoiseCacheDimensionInts[0], NoiseCacheDimensionInts[1], 0, RenderTextureFormat.RFloat);
        s_noiseCache.dimension = TextureDimension.Tex3D;
        s_noiseCache.volumeDepth = NoiseCacheDimensionInts[2];
        s_noiseCache.enableRandomWrite = true;
        s_noiseCache.Create();
        ComputeManager.SetTexture(Const.NoiseCache, s_noiseCache);
        //ComputeManager.SetTexture(Const.NoiseCache, ResourcesUtil.NoiseTexture);
        ComputeManager.SetInts(Const.NoiseCacheDimension, NoiseCacheDimensionInts);
        ComputeManager.SetFloat(Const.NoiseCacheDensity, NoiseCacheDensity);
        ComputeManager.SetFloats(Const.NoiseCachePeriod, NoiseCachePeriod);
        ComputeManager.Dispatch
        (
          s_computeNoiseCache, 
          Const.Kernel.GenerateNoiseCache, 
          (s_noiseCache.width  + ThreadGroupExtent - 1) / ThreadGroupExtent, 
          (s_noiseCache.height + ThreadGroupExtent - 1) / ThreadGroupExtent, 
          (s_noiseCache.volumeDepth + ThreadGroupExtent - 1) / ThreadGroupExtent
        );
        ComputeManager.Deactivate(s_computeNoiseCache);
      } // end: generate noise cache
       
      return true;
    }

    private bool ValidateGlobalResources()
    {
      Profiler.BeginSample("ValidateGlobalResources");

      if (!ValidateComputeShaders())
        return false;

      if (s_triTableBuffer == null)
      {
        s_triTableBuffer = new ComputeBuffer(16 * 256, sizeof(int));
        s_triTableBuffer.SetData(MarchingCubes.TriTable);
        ComputeManager.SetBuffer(Const.TriTable, s_triTableBuffer);
        m_needsCompute = true;
      }

      if (s_vertTableBuffer == null)
      {
        s_vertTableBuffer = new ComputeBuffer(2 * 12, sizeof(int));
        s_vertTableBuffer.SetData(MarchingCubes.VertTable);
        ComputeManager.SetBuffer(Const.VertTable, s_vertTableBuffer);
        m_needsCompute = true;
      }

      if (s_triTable2dBuffer == null)
      {
        s_triTable2dBuffer = new ComputeBuffer(12 * 16, sizeof(int));
        s_triTable2dBuffer.SetData(MarchingCubes.TriTable2d);
        ComputeManager.SetBuffer(Const.TriTable2d, s_triTable2dBuffer);
        m_needsCompute = true;
      }

      if (AllowSharedRWBuffers)
      {
        if (s_brushesBuffer == null)
        {
          s_brushesBuffer = new ComputeBuffer(MaxBrushes, SdfBrush.Stride);
          m_needsCompute = true;
        }

        if (s_brushMaterialBuffer == null)
        {
          s_brushMaterialBuffer = new ComputeBuffer(MaxBrushes, SdfBrushMaterial.Stride);
          m_needsCompute = true;
        }
      }

      if (s_dummyBuffer == null)
      {
        s_dummyBuffer = new ComputeBuffer(4, sizeof(int));
      }

      Profiler.EndSample();

      s_globalResourcesValid = true;

      return true;
    }

    protected virtual bool ValidateLocalResources()
    {
      Profiler.BeginSample("ValidateLocalResources (Renderer Base)");

      if (AllowSharedRWBuffers)
      {
        if (m_brushesBuffer != null 
            && m_brushesBuffer != s_brushesBuffer)
        {
          //m_brushesBuffer.Release();
          Janitor.Dispose(m_brushesBuffer);
          m_needsCompute = true;
        }

        if (m_brushMaterialBuffer != null 
            && m_brushMaterialBuffer != s_brushMaterialBuffer)
        {
          //m_brushMaterialBuffer.Release();
          Janitor.Dispose(m_brushMaterialBuffer);
          m_needsCompute = true;
        }

        if (m_aabbTreeBuffer != null 
            && m_aabbTreeBuffer != s_aabbTreeBuffer)
        {
          //m_aabbTreeBuffer.Release();
          Janitor.Dispose(m_aabbTreeBuffer);
          m_needsCompute = true;
        }

        m_brushesBuffer = s_brushesBuffer;
        m_brushMaterialBuffer = s_brushMaterialBuffer;
        m_aabbTreeBuffer = s_aabbTreeBuffer;
      }
      else
      {
        if (m_brushesBuffer == null 
            || m_brushesBuffer == s_brushesBuffer)
        {
          m_brushesBuffer = new ComputeBuffer(MaxBrushes, SdfBrush.Stride);
          m_needsCompute = true;
        }

        if (m_brushMaterialBuffer == null 
            || m_brushMaterialBuffer == s_brushMaterialBuffer)
        {
          m_brushMaterialBuffer = new ComputeBuffer(MaxBrushes, SdfBrushMaterial.Stride);
          m_needsCompute = true;
        }

        if (m_aabbTreeBuffer == s_aabbTreeBuffer)
        {
          m_aabbTreeBuffer = null;
        }
      }

      if (m_nodeHashTableBuffer == null 
          || m_nodeHashTableBuffer.count != NodeHashTableSize)
      {
        if (m_nodeHashTableBuffer != null)
        {
          //m_nodeHashTableBuffer.Release();
          Janitor.Dispose(m_nodeHashTableBuffer);
        }

        m_nodeHashTableBuffer = new ComputeBuffer(NodeHashTableSize, VoxelHashEntry.Stride);
        m_needsCompute = true;
      }

      if (m_nodePoolBuffer == null 
          || m_nodePoolBuffer.count != MaxVoxels)
      {
        if (m_nodePoolBuffer != null)
        {
          //m_nodePoolBuffer.Release();
          Janitor.Dispose(m_nodePoolBuffer);
        }

        m_nodePoolBuffer = new ComputeBuffer(MaxVoxels, VoxelNode.Stride);
        m_needsCompute = true;
      }

      int voxelCacheSize = MaxVoxels * 2;
      if (UseVoxelCache)
      {
        if (m_voxelCacheIdTableBuffer == null 
            || m_voxelCacheIdTableBuffer.count != voxelCacheSize)
        {
          if (m_voxelCacheIdTableBuffer != null)
          {
            //m_voxelCacheIdTableBuffer.Release();
            //m_voxelCacheBuffer.Release();
            Janitor.Dispose(m_voxelCacheIdTableBuffer);
            Janitor.Dispose(m_voxelCacheBuffer);
          }

          m_voxelCacheIdTableBuffer = new ComputeBuffer(voxelCacheSize, sizeof(int));
          m_voxelCacheBuffer = new ComputeBuffer(voxelCacheSize, 4 * sizeof(float));
          m_needsCompute = true;
        }
      }
      else if (m_voxelCacheBuffer != null)
      {
        //m_voxelCacheIdTableBuffer.Release();
        //m_voxelCacheBuffer.Release();
        Janitor.Dispose(m_voxelCacheIdTableBuffer);
        Janitor.Dispose(m_voxelCacheBuffer);

        m_voxelCacheIdTableBuffer = null;
        m_voxelCacheBuffer = null;
      }

      if (m_brushMaskPoolBuffer == null 
          || m_brushMaskPoolBuffer.count != MaxBrushMasks * MaxBrushMaskInts)
      {
        if (m_brushMaskPoolBuffer != null)
        {
          //m_brushMaskPoolBuffer.Release();
          Janitor.Dispose(m_brushMaskPoolBuffer);
        }

        m_brushMaskPoolBuffer = new ComputeBuffer(MaxBrushMasks * MaxBrushMaskInts, sizeof(int));
        m_needsCompute = true;
      }

      int maxGenPoints = MaxGenPoints;
      if (m_genPointsBufferDefault == null 
          || m_genPointsBufferDefault.count != maxGenPoints)
      {
        if (m_genPointsBufferDefault != null)
        {
          //m_numNodesAllocatedBuffer.Release();
          //m_numAllocationsBuffer.Release();
          //m_indirectDispatchArgsBuffer.Release();
          //m_genPointsBufferDefault.Release();
          //m_indirectDrawArgsBufferDefault.Release();
          Janitor.Dispose(m_numNodesAllocatedBuffer);
          Janitor.Dispose(m_numAllocationsBuffer);
          Janitor.Dispose(m_indirectDispatchArgsBuffer);
          Janitor.Dispose(m_genPointsBufferDefault);
          Janitor.Dispose(m_indirectDrawArgsBufferDefault);
        }

        m_numNodesAllocatedBuffer = new ComputeBuffer(VoxelNodeDepth + 2, sizeof(int));
        m_numAllocationsBuffer = new ComputeBuffer(4, sizeof(int));
        m_indirectDispatchArgsBuffer = new ComputeBuffer(3, sizeof(int), ComputeBufferType.IndirectArguments);
        m_genPointsBufferDefault = new ComputeBuffer(maxGenPoints, GenPoint.Stride);
        m_indirectDrawArgsBufferDefault = new ComputeBuffer(5, sizeof(int), ComputeBufferType.IndirectArguments);
        m_needsCompute = true;
      }

      if (ShouldDoAutoSmoothing)
      {
        if (m_autoSmoothVertDataTableBuffer != null 
            && m_autoSmoothVertDataTableBuffer.count != AutoSmoothVertDataTableSize)
        {
          //m_autoSmoothVertDataTableBuffer.Release();
          Janitor.Dispose(m_autoSmoothVertDataTableBuffer);
          m_autoSmoothVertDataTableBuffer = null;
        }

        if (m_autoSmoothVertDataTableBuffer == null)
        {
          m_autoSmoothVertDataTableBuffer = new ComputeBuffer(AutoSmoothVertDataTableSize, AutoSmoothVertData.Stride);
        }
      }
      else
      {
        if (m_autoSmoothVertDataTableBuffer != null)
        {
          //m_autoSmoothVertDataTableBuffer.Release();
          Janitor.Dispose(m_autoSmoothVertDataTableBuffer);
          m_autoSmoothVertDataTableBuffer = null;
        }
      }

      if (m_usedSharedMaterial != SharedMaterial)
      {
        m_usedSharedMaterial = SharedMaterial;
        m_needsCompute = true;
      }

      Profiler.EndSample();

      m_localResourcesValid = true;

      return true;
    }

    protected static void DisposeGlobalResources()
    {
      s_globalResourcesValid = false;

      s_computeVoxelGen = null;

      if (s_triTableBuffer != null)
      {
        //s_triTableBuffer.Release();
        Janitor.Dispose(s_triTableBuffer);
        s_triTableBuffer = null;
      }

      if (s_vertTableBuffer != null)
      {
        //s_vertTableBuffer.Release();
        Janitor.Dispose(s_vertTableBuffer);
        s_vertTableBuffer = null;
      }

      if (s_triTable2dBuffer != null)
      {
        //s_triTable2dBuffer.Release();
        Janitor.Dispose(s_triTable2dBuffer);
        s_triTable2dBuffer = null;
      }

      if (s_brushesBuffer != null)
      {
        //s_brushesBuffer.Release();
        Janitor.Dispose(s_brushesBuffer);
        s_brushesBuffer = null;
      }

      if (s_brushMaterialBuffer != null)
      {
        //s_brushMaterialBuffer.Release();
        Janitor.Dispose(s_brushMaterialBuffer);
        s_brushMaterialBuffer = null;
      }

      if (s_aabbTreeBuffer != null)
      {
        //s_aabbTreeBuffer.Release();
        Janitor.Dispose(s_aabbTreeBuffer);
        s_aabbTreeBuffer = null;
      }

      if (s_dummyBuffer != null)
      {
        //s_dummyBuffer.Release();
        Janitor.Dispose(s_dummyBuffer);
        s_dummyBuffer = null;
      }

      if (s_numAllocatoinsBufferInitData.IsCreated)
        s_numAllocatoinsBufferInitData.Dispose();

      if (s_unitIndirectDispatchArgsInitData.IsCreated)
        s_unitIndirectDispatchArgsInitData.Dispose();
    }

    protected void DisposeLocalResources()
    {
      m_localResourcesValid = false;

      if (m_brushesBuffer != null 
          && m_brushesBuffer != s_brushesBuffer)
      {
        //m_brushesBuffer.Release();
        Janitor.Dispose(m_brushesBuffer);
        m_brushesBuffer = null;
      }

      if (m_brushMaterialBuffer != null 
          && m_brushMaterialBuffer != s_brushMaterialBuffer)
      {
        //m_brushMaterialBuffer.Release();
        Janitor.Dispose(m_brushMaterialBuffer);
        m_brushMaterialBuffer = null;
      }

      if (m_aabbTreeBuffer != null 
          && m_aabbTreeBuffer != s_aabbTreeBuffer)
      {
        //m_aabbTreeBuffer.Release();
        Janitor.Dispose(m_aabbTreeBuffer);
        m_aabbTreeBuffer = null;
      }

      if (m_nodeHashTableBuffer != null)
      {
        //m_nodeHashTableBuffer.Release();
        Janitor.Dispose(m_nodeHashTableBuffer);
        m_nodeHashTableBuffer = null;
      }

      if (m_nodePoolBuffer != null)
      {
        //m_nodePoolBuffer.Release();
        Janitor.Dispose(m_nodePoolBuffer);
        m_nodePoolBuffer = null;
      }

      if (m_voxelCacheIdTableBuffer != null)
      {
        //m_voxelCacheIdTableBuffer.Release();
        //m_voxelCacheBuffer.Release();
        Janitor.Dispose(m_voxelCacheIdTableBuffer);
        Janitor.Dispose(m_voxelCacheBuffer);

        m_voxelCacheIdTableBuffer = null;
        m_voxelCacheBuffer = null;
      }

      if (m_brushMaskPoolBuffer != null)
      {
        //m_brushMaskPoolBuffer.Release();
        Janitor.Dispose(m_brushMaskPoolBuffer);
        m_brushMaskPoolBuffer = null;
      }

      if (m_genPointsBufferDefault != null)
      {
        //m_numNodesAllocatedBuffer.Release();
        //m_numAllocationsBuffer.Release();
        //m_indirectDispatchArgsBuffer.Release();
        //m_genPointsBufferDefault.Release();
        //m_indirectDrawArgsBufferDefault.Release();
        Janitor.Dispose(m_numNodesAllocatedBuffer);
        Janitor.Dispose(m_numAllocationsBuffer);
        Janitor.Dispose(m_indirectDispatchArgsBuffer);
        Janitor.Dispose(m_genPointsBufferDefault);
        Janitor.Dispose(m_indirectDrawArgsBufferDefault);

        m_numNodesAllocatedBuffer = null;
        m_numAllocationsBuffer = null;
        m_indirectDispatchArgsBuffer = null;
        m_genPointsBufferDefault = null;
        m_indirectDrawArgsBufferDefault = null;
      }

      if (m_autoSmoothVertDataTableBuffer != null)
      {
        //m_autoSmoothVertDataTableBuffer.Release();
        Janitor.Dispose(m_autoSmoothVertDataTableBuffer);
        m_autoSmoothVertDataTableBuffer = null;
      }

      m_materialCloned = null;
      m_materialUsed = null;

      if (m_pendingMeshTable != null)
      {
        foreach (var pair in m_pendingMeshTable)
          pair.Value.Dispose();
        m_pendingMeshTable.Clear();
      }

      if (m_indirectDrawArgsInitData.IsCreated)
        m_indirectDrawArgsInitData.Dispose();
    }

    #endregion // end: Resources

    //-------------------------------------------------------------------------

    #region Brushes

    private static readonly string MaxBrushWarningMessage = 
      $"Maximum of {MaxBrushes} compute brushes per renderer exceeded (some brushes can count as more than one compute brushes).\n"
      + "You may try bumping the maximum compute brushes by following the instructions in comments above where MudRendererBase.MaxBrushes is declared, but beware of performance.";

    public void AddBrush(MudBrushBase brush)
    {
      if (brush.m_renderer != null)
        brush.m_renderer.RemoveBrush(brush);

      brush.m_renderer = this;

      ValidateAabbTree();
      brush.UpdateProxies(m_aabbTree, brush.BoundsRs);
      m_aabbTreeDirty = true;

      m_aBrush.Add(brush);
      m_aBrushToProcess.Add(brush);

      s_brushMap[brush.GetFloatHash()] = brush;

      Assert.True(m_aBrushToProcess.Count <= MaxBrushes, MaxBrushWarningMessage);

      RescanBrushes();
    }

    public void RemoveBrush(MudBrushBase brush)
    {
#if MUDBUN_DEV
      Assert.True(brush.m_renderer != null, "Brush was never added.");
      Assert.True(brush.m_renderer == this, "Brush was not added to this renderer.");
#endif

      ValidateAabbTree();
      brush.DestroyProxies(m_aabbTree);
      m_aabbTreeDirty = true;

      brush.m_renderer = null;

      m_aBrush.Remove(brush);
      m_aBrushToProcess.Remove(brush);
      if (brush.IsBrushGroup)
        m_aBrushToProcess.Remove(brush);

      s_brushMap.Remove(brush.GetFloatHash());

      RescanBrushes();
    }

    public void DestroyAllBrushes()
    {
      var aBrushCopy = Brushes.ToArray();
      foreach (var b in aBrushCopy)
      {
        var m = b.GetComponent<MudMaterialBase>();
        Destroy(b);
        Destroy(m);
      }
    }

    public void DestroyAllBrushesImmediate()
    {
      var aBrushCopy = Brushes.ToArray();
      foreach (var b in aBrushCopy)
      {
        var m = b.GetComponent<MudMaterialBase>();
        DestroyImmediate(b);
        DestroyImmediate(m);
      }
    }

    public virtual void NotifyHierarchyChange()
    {
      RescanBrushes();
    }

    private void ClearBrushes()
    {
      ValidateAabbTree();
      foreach (var brush in m_aBrush)
      {
        brush.m_renderer = null;
        brush.DestroyProxies(m_aabbTree);
      }

      m_aBrush.Clear();
      m_aBrushToProcess.Clear();

      RescanBrushes();
    }

    public void RescanBrushes()
    {
      m_needRescanBrushes = true;
    }

    public void RescanBrushesImmediate()
    {
      ClearBrushes();
      ScanHierarchyRecursive(transform);
      ValidateBrushSetHash();
    }

    private int ComputeBrushListHash()
    {
      int hash = 0;
      for (int iBrush = 0, numBrushes = m_aBrushToProcess.Count; iBrush < numBrushes; ++iBrush)
      {
        var brush = m_aBrushToProcess[iBrush];
        hash = Codec.HashConcat(hash, brush.GetInstanceID());
      }
      return hash;
    }

    private int m_brushSetHash = -1;
    private void ValidateBrushSetHash()
    {
      int brushListHash = ComputeBrushListHash();
      if (brushListHash != m_brushSetHash)
      {
        MarkNeedsCompute();
        m_brushSetHash = brushListHash;
        m_brushDataDirty = true;
      }
    }

    private int m_brushGroupDepth = 0;
    private void ScanHierarchyRecursive(Transform parent)
    {
      if (parent == null)
        return;

      parent.TryGetComponent(out MudBrushBase parentBrush);
      bool parentIsGroup = (parentBrush != null && parentBrush.IsBrushGroup && parentBrush.isActiveAndEnabled);
      if (parentIsGroup)
      {
        ++m_brushGroupDepth;
        if (m_brushGroupDepth >= MaxBrushGroupDepth)
          Assert.Warn($"MudBun: Exceeded maximum group depth of {MaxBrushGroupDepth}!");
      }

      for (int iChild = 0; iChild < parent.childCount; ++iChild)
      {
        var child = parent.GetChild(iChild);

        // renderer blocks recursion
        if (child.TryGetComponent(out MudRendererBase _))
          continue;

        if (child.TryGetComponent(out MudBrushBase brush) && brush.isActiveAndEnabled)
          AddBrush(brush);

        ScanHierarchyRecursive(child);
      }

      if (parentIsGroup)
      {
        m_aBrushToProcess.Add(parentBrush);
        --m_brushGroupDepth;
      }
    }

    private void SetUpResources()
    {
      Profiler.BeginSample("SetUpResources");

      WriteResources();
      BindComputeResources();

      Profiler.EndSample();
    }

    private void UpdateActivePreCompute()
    {
      if (m_doRigging)
        ComputeManager.Activate(s_computeMeshLock);
      else
        ComputeManager.Deactivate(s_computeMeshLock);

      switch (MeshingMode)
      {
        case MeshingModeEnum.MarchingCubes:
          ComputeManager.Activate(s_computeMarchingCubes);
          ComputeManager.Deactivate(s_computeDualMeshing);
          ComputeManager.Deactivate(s_computeSurfaceNets);
          ComputeManager.Deactivate(s_computeDualContouring);
          break;

        case MeshingModeEnum.DualQuads:
          ComputeManager.Activate(s_computeDualMeshing);
          ComputeManager.Deactivate(s_computeMarchingCubes);
          ComputeManager.Deactivate(s_computeSurfaceNets);
          ComputeManager.Deactivate(s_computeDualContouring);
          break;

        case MeshingModeEnum.SurfaceNets:
          ComputeManager.Activate(s_computeDualMeshing);
          ComputeManager.Activate(s_computeSurfaceNets);
          ComputeManager.Deactivate(s_computeMarchingCubes);
          ComputeManager.Deactivate(s_computeDualContouring);
          break;

        case MeshingModeEnum.DualContouring:
          ComputeManager.Activate(s_computeDualMeshing);
          ComputeManager.Activate(s_computeDualContouring);
          ComputeManager.Deactivate(s_computeMarchingCubes);
          ComputeManager.Deactivate(s_computeSurfaceNets);
          break;
      }

      /*
      if (RenderModeCategory == RenderModeCategoryEnum.RayTracedVoxels)
        ComputeManager.Activate(s_computeRayTracedVoxels);
      else
      */
        ComputeManager.Deactivate(s_computeRayTracedVoxels);
    }

    private void UpdateActivePostCompute()
    {
      if (m_doRigging)
        ComputeManager.Deactivate(s_computeMeshLock);
    }

    private bool ValidateAabbTreeBuffer(ref ComputeBuffer aabbTreeBuffer)
    {
      if (m_aabbTreeBuffer != null 
          && m_aabbTreeBuffer.count >= m_aabbTree.Capacity)
        return false;

      if (aabbTreeBuffer != null)
        aabbTreeBuffer.Release();

      aabbTreeBuffer = new ComputeBuffer(m_aabbTree.Capacity, AabbTree<MudBrushBase>.NodePod.Stride);

      return true;
    }

    private void ValidateRenderMaterial()
    {
      Material material = null;
      switch (RenderModeCategory)
      {
        case RenderModeCategoryEnum.Mesh:
          material = RenderMaterialMesh;
          break;

        case RenderModeCategoryEnum.Splats:
          material = RenderMaterialSplats;
          break;

        case RenderModeCategoryEnum.Decal:
          material = RenderMaterialDecal;
          break;

        /*
        case RenderModeCategoryEnum.RayMarchedSurface:
          material = RenderMaterialRayMarchedSurface;
          break;

        case RenderModeCategoryEnum.RayTracedVoxels:
          material = RenderMaterialRayTracedVoxels;
          break;
        */
      }

      {
        Profiler.BeginSample("Copy Render Material Properties");

        if (m_materialCloned != material)
        {
          m_materialUsed = new Material(material);
          m_materialCloned = material;

          if (!Application.isEditor)
          {
            m_renderMaterialDirty = true;
          }
        }

        if (Application.isEditor)
        {
          // need to do this constantly in editor because the user can change the referenced material
          // TODO: timeslice this when in play mode
          m_renderMaterialDirty = true;
        }

        if (RenderMaterialMode == RenderMaterialModeEnum.Dynamic)
        {
          m_renderMaterialDirty = true;
        }

        if (m_renderMaterialDirty)
        {
          m_materialUsed.CopyPropertiesFromMaterial(m_materialCloned);
          m_materialUsed.enableInstancing = true;

          m_renderMaterialDirty = false;
        }

        Profiler.EndSample();
      }

      m_materialUsed.EnableKeyword("MUDBUN_PROCEDURAL");
      switch (RenderMode)
      {
        case RenderModeEnum.QuadSplats:
          m_materialUsed.EnableKeyword("MUDBUN_QUAD_SPLATS");
          break;
        default:
          m_materialUsed.DisableKeyword("MUDBUN_QUAD_SPLATS");
          break;
      }
    }

    protected virtual bool ShouldHighlightBrushFromSelection(MudBrushBase brush)
    {
      return false;
    }

    private void UpdateBrushData()
    {
      if (!m_brushDataDirty && !m_doRigging)
        return;

      // need to complete queued jobs before writing to native arrays again
      for (int i = 0; i < m_jobCompleteQueue.Count; ++i)
      {
        m_jobCompleteQueue[i].Complete();
      }
      m_jobCompleteQueue.Clear();

      Profiler.BeginSample("UpdateBrushData");

      m_numSdfBrushes = 0;
      m_numSdfMaterials = 0;
      m_sdfBrushMaterialIndexMap.Clear();
      m_aBone = (m_doRigging ? new List<Transform>() : null);
      for (int iBrush = 0, numBrushes = m_aBrushToProcess.Count; iBrush < numBrushes; ++iBrush)
      {
        var brush = m_aBrushToProcess[iBrush];

        Profiler.BeginSample("Each Brush");

        brush.m_iSdfBrush = m_numSdfBrushes;

        Profiler.BeginSample("Fill Material");
        int materialIndex = -1;
        bool selectionHighlight = ShouldHighlightBrushFromSelection(brush);
        /*
        if (selectionHighlight)
        {
          materialIndex = m_numSdfMaterials;
          m_sdfBrushMaterialIndexMap[0] = materialIndex;
          var mat = m_aSdfBrushMaterial[m_numSdfMaterials];
          brush.FillBrushMaterialData(ref mat);
          var color = mat.Color;
          color.r = Mathf.Min(1.0f, color.r + 0.1f);
          color.g = Mathf.Min(1.0f, color.g + 0.1f);
          color.b = Mathf.Min(1.0f, color.b + 0.1f);
          mat.Color = color;
          var emissionHash = mat.EmissionHash;
          emissionHash.r = Mathf.Min(1.0f, emissionHash.r + 0.1f);
          emissionHash.g = Mathf.Min(1.0f, emissionHash.g + 0.1f);
          emissionHash.b = Mathf.Min(1.0f, emissionHash.b + 0.1f);
          mat.EmissionHash = emissionHash;
          m_aSdfBrushMaterial[m_numSdfMaterials] = mat;
          ++m_numSdfMaterials;
        }
        else
        */
        {
          if (brush.UsesMaterial 
              && !m_sdfBrushMaterialIndexMap.TryGetValue(brush.MaterialHash, out materialIndex))
          {
            materialIndex = m_numSdfMaterials;
            m_sdfBrushMaterialIndexMap.Add(brush.MaterialHash, materialIndex);
            var mat = m_aSdfBrushMaterial[m_numSdfMaterials];
            brush.FillBrushMaterialData(ref mat);
            m_aSdfBrushMaterial[m_numSdfMaterials] = mat;
            ++m_numSdfMaterials;
          }
        }
#if MUDBUN_DEV
        Assert.True(!brush.UsesMaterial || materialIndex >= 0);
#endif
        Profiler.EndSample();

        Profiler.BeginSample("Fill Compute Data");
        int numNewBrushes = 
          (!brush.IsBrushGroup || !brush.m_preChildrenFlag)
            ? brush.FillComputeData(m_aSdfBrush, m_numSdfBrushes, (brush.CountAsBone ? m_aBone : null)) 
            : brush.FillComputeDataPostChildren(m_aSdfBrush, m_numSdfBrushes);
        Profiler.EndSample();

        Profiler.BeginSample("Fill Brush Data");
        float hash = brush.GetFloatHash();
        for (int iNewBrush = 0; iNewBrush < numNewBrushes; ++iNewBrush)
        {
          int iSdfBrush = m_numSdfBrushes + iNewBrush;
          var b = m_aSdfBrush[iSdfBrush];
          b.Index = iSdfBrush;

          if (!brush.IsBrushGroup || !brush.m_preChildrenFlag)
            brush.FillBrushData(ref b, iSdfBrush);
          else
            brush.FillBrushDataPostChildren(ref b, iSdfBrush);

          b.MaterialIndex = materialIndex;
          b.Hash =
            selectionHighlight 
              ? -hash // encode selection highlight in negative hash sign
              : hash;

          m_aSdfBrush[iSdfBrush] = b;
        }
        Profiler.EndSample();

        m_numSdfBrushes += numNewBrushes;

        if (brush.IsBrushGroup)
          brush.m_preChildrenFlag = !brush.m_preChildrenFlag;

        Profiler.EndSample();
      }

      m_brushDataDirty = false;

      Profiler.EndSample();
    }

    private void UpdateAabbTreeData()
    {
      if (!m_aabbTreeDirty)
        return;

      Profiler.BeginSample("UpdateAabbTree");

      Aabb successorModifierBounds = Aabb.Empty;
      Aabb accumulatedBounds = Aabb.Empty;
      Aabb groupBounds = Aabb.Empty;
      var groupSymmetry = MudSolid.SymmetryMode.None;
      foreach (var brush in m_aBrushToProcess)
      {
        Aabb opBounds = brush.BoundsRs;

        if (brush.IsBrushGroup)
        {
          if (!brush.m_preChildrenFlag)
          {
            // begin group
            m_brushGroupBoundsStack.Push(groupBounds);
            m_accumulatedBoundsStack.Push(accumulatedBounds);
            m_brushGroupSymmetryStack.Push(groupSymmetry);
            groupBounds = Aabb.Empty;
            accumulatedBounds = Aabb.Empty;
            groupSymmetry = ((MudBrushGroup) brush).Symmetry;
          }
          else
          {
            // end group
            opBounds = groupBounds;
            opBounds.Expand(m_aabbTree.FatBoundsRadius);
            groupBounds = m_brushGroupBoundsStack.Pop();
            accumulatedBounds = m_accumulatedBoundsStack.Pop();
            groupSymmetry = m_brushGroupSymmetryStack.Pop();
          }
          brush.m_preChildrenFlag = !brush.m_preChildrenFlag;
        }

        if (groupSymmetry != MudSolid.SymmetryMode.None)
          opBounds = MudSolid.SymmetryBounds(groupSymmetry, opBounds);

        if (opBounds.IsEmpty)
        {
          if (brush.IsBrushGroup)
            brush.UpdateProxies(m_aabbTree, opBounds);

          continue;
        }

        if (brush.IsSuccessorModifier)
          successorModifierBounds.Include(opBounds);

        opBounds.Include(successorModifierBounds);

        accumulatedBounds.Include(opBounds);
        groupBounds.Include(opBounds);

        if (brush.ShouldUseAccumulatedBounds)
          opBounds = accumulatedBounds;

        opBounds.Expand(brush.BoundsRsPadding);
        accumulatedBounds.Include(opBounds);
        groupBounds.Include(opBounds);

        brush.UpdateProxies(m_aabbTree, opBounds);
      }

      m_aabbTree.UpdatePods();

      m_aabbTreeDirty = false;

      Profiler.EndSample();
    }

    protected int m_numSdfBrushes;
    protected int m_numSdfMaterials;
    protected List<Transform> m_aBone;
    private Stack<Aabb> m_brushGroupBoundsStack = new Stack<Aabb>();
    private Stack<Aabb> m_accumulatedBoundsStack = new Stack<Aabb>();
    private Stack<MudSolid.SymmetryMode> m_brushGroupSymmetryStack = new Stack<MudSolid.SymmetryMode>();
    private void WriteResources()
    {
      Profiler.BeginSample("WriteResources");


      Profiler.BeginSample("Brushes");

      if (m_materialUsed == null)
        ValidateRenderMaterial();

      float effectiveSurfaceShift = SurfaceShift + 1e-5f; // tiny offset to fix voxel edges perfectly lining up with zero isosurface
      if (RenderModeCategory == RenderModeCategoryEnum.Decal)
        effectiveSurfaceShift += 0.5f * VoxelSize;

      Profiler.BeginSample("Set Brush Data");
      m_brushesBuffer.SetData(m_aSdfBrush, 0, 0, m_numSdfBrushes);
      Profiler.EndSample();
      Profiler.BeginSample("Set Material Data");
      m_brushMaterialBuffer.SetData(m_aSdfBrushMaterial, 0, 0, m_numSdfMaterials);
      Profiler.EndSample();
      ComputeManager.SetInt(Const.NumBrushes, m_numSdfBrushes);
      ComputeManager.SetFloat(Const.SurfaceShift, effectiveSurfaceShift);
      ComputeManager.SetInt(Const.RenderMode, (int) RenderMode);
      ComputeManager.SetInt(Const.MeshingMode, (int) MeshingMode);
      ComputeManager.SetInt(Const.RayTracedVoxelMode, (int) RayTracedVoxelMode);
      ComputeManager.SetFloat(Const.RayTracedVoxelSizeMultiplier, RayTracedVoxelSizeMultiplier);
      ComputeManager.SetInt(Const.RayTracedVoxelPaddingMode, (int) RayTracedVoxelPaddingMode);
      ComputeManager.SetFloat(Const.RayTracedVoxelSmoothCubeNormal, RayTracedVoxelSmoothCubeNormal);
      ComputeManager.SetFloat(Const.RayTracedVoxelRadius, 1.0f + 0.733f * RayTracedVoxelSphereFullness);
      ComputeManager.SetInt(Const.RayTracedVoxelPaddingMode, (int) RayTracedVoxelPaddingMode);
      ComputeManager.SetFloat(Const.RayTracedVoxelInternalPaddingDistance, RayTracedVoxelInternalPaddingDistance);
      ComputeManager.SetFloat(Const.RayTracedVoxelSizeFadeDistance, RayTracedVoxelSizeFadeDistance);
      ComputeManager.SetFloat(Const.NormalDifferentiationStep, NormalDifferentialStep);
      ComputeManager.SetFloat(Const.NormalQuantization, NormalQuantization);
      ComputeManager.SetFloat(Const.Normal2dFadeDist, Normal2dFade);
      ComputeManager.SetFloat(Const.Normal2dStrength, Normal2dStrength);
      bool shouldDoAutoSmoothing = ShouldDoAutoSmoothing;
      ComputeManager.SetBool(Const.EnableAutoSmooth, shouldDoAutoSmoothing);
      ComputeManager.SetFloat(Const.AutoSmoothMaxAngle, AutoSmoothingMaxAngle * MathUtil.Deg2Rad);
      ComputeManager.SetBuffer(Const.AutoSmoothVertDataTable, (shouldDoAutoSmoothing && m_autoSmoothVertDataTableBuffer != null) ? m_autoSmoothVertDataTableBuffer : s_dummyBuffer);
      ComputeManager.SetInt(Const.AutoSmoothVertDataPoolSize, (shouldDoAutoSmoothing && m_autoSmoothVertDataTableBuffer != null) ? m_autoSmoothVertDataTableBuffer.count : 0);
      ComputeManager.SetBool(Const.EnableSmoothCorner, EnableSmoothCorner);
      ComputeManager.SetInt(Const.SmoothCornerSubdivision, SmoothCornerSubdivision);
      ComputeManager.SetFloat(Const.SmoothCornerNormalBlur, 0.05f * VoxelSize + SmoothCornerNormalBlur);
      ComputeManager.SetFloat(Const.SmoothCornerFade, SmoothCornerFade);
      ComputeManager.SetInt(Const.InvertNormals, (InvertNormals && !Enable2dMode ? 1 : 0));

      Profiler.EndSample();


      Profiler.BeginSample("BVH");

      bool needComputeFromAabbTree = false;
      if (AllowSharedRWBuffers)
      { 
        needComputeFromAabbTree = ValidateAabbTreeBuffer(ref s_aabbTreeBuffer);
        m_aabbTreeBuffer = s_aabbTreeBuffer;
      }
      else
      {
        needComputeFromAabbTree = ValidateAabbTreeBuffer(ref m_aabbTreeBuffer);
      }

      if (needComputeFromAabbTree)
      {
        m_needsCompute = true;
      }

      m_aabbTree.FillComputeBuffer(m_aabbTreeBuffer);
      m_aabbTreeBufferRoot = m_aabbTree.Root;

      Profiler.EndSample();


      Profiler.BeginSample("Voxel Tree");

      ComputeManager.SetInt(Const.NodeHashTableSize, m_nodeHashTableBuffer.count);

      // general allocation counters
      m_numAllocationsBuffer.SetData(NumAllocatoinsBufferInitData);

      // node pool
      ComputeManager.SetInt(Const.NodePoolSize, m_nodePoolBuffer.count);
      if (m_numAllocationsBufferInitData == null || m_numAllocationsBufferInitData.Length != m_numNodesAllocatedBuffer.count)
        m_numAllocationsBufferInitData = new int[m_numNodesAllocatedBuffer.count];
      for (int depth = 0; depth < m_numAllocationsBufferInitData.Length; ++depth)
        m_numAllocationsBufferInitData[depth] = 0;
      m_numNodesAllocatedBuffer.SetData(m_numAllocationsBufferInitData);

      // voxel cache
      ComputeManager.SetBool(Const.UseVoxelCache, UseVoxelCache);
      ComputeManager.SetInt(Const.VoxelCacheSize, (m_voxelCacheBuffer != null ? m_voxelCacheBuffer.count : 0));

      // brush bit masks
      ComputeManager.SetInt(Const.BrushMaskPoolSize, MaxBrushMasks);

      // dispatch args
      m_indirectDispatchArgsBuffer.SetData(UnitIndirectDispatchArgsInitData);

      Profiler.EndSample();


      Profiler.BeginSample("Indirect Draw Args");

      // indirect draw args
      m_indirectDrawArgsBufferDefault.SetData(IndirectDrawArgsInitData);
      if (m_indirectDrawArgsBufferOverride != null)
        m_indirectDrawArgsBufferOverride.SetData(IndirectDrawArgsInitData);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.RenderMode);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NumBrushes);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NodeHashTableSize);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NodePoolSize);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.BrushMaskPoolSize);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.Brushes);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.BrushMaterials);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.SurfaceShift);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.AabbTree);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.AabbRoot);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.Enable2dMode);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.ForceAllBrushes);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NodeHashTable);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NodePool);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NumNodesAllocated);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NumAllocations);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.BrushMaskPool);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.IndirectDispatchArgs);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.GenPoints);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.IndirectDrawArgs);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.VoxelSize);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.VoxelTreeBranchingFactorsCompressed);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.VoxelNodeSizes);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.MaxNodeDepth);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.ChunkVoxelDensity);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NoiseCache);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NoiseCacheDimension);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NoiseCacheDensity);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.CurrentNodeDepth);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.CurrentNodeBranchingFactor);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.CurrentNodeSize);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.NormalDifferentiationStep);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.EnableAutoSmooth);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.AutoSmoothMaxAngle);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.AutoSmoothVertDataTable);
        ComputeManager.RegisterConstantId(s_computeVoxelGen, Const.AutoSmoothVertDataPoolSize);

      Profiler.EndSample();


      Profiler.EndSample();
    }

    private void BindComputeResources()
    {
      Profiler.BeginSample("BindComputeResources");

      m_genPointsBufferUsedForCompute = m_genPointsBufferOverride != null ? m_genPointsBufferOverride : m_genPointsBufferDefault;
      m_indirectDrawArgsBufferUsedForCompute = m_indirectDrawArgsBufferOverride != null ? m_indirectDrawArgsBufferOverride : m_indirectDrawArgsBufferDefault;

      ComputeManager.SetBuffer(Const.Brushes, m_brushesBuffer);
      ComputeManager.SetBuffer(Const.BrushMaterials, m_brushMaterialBuffer);

      ComputeManager.SetBuffer(Const.AabbTree, m_aabbTreeBuffer);
      ComputeManager.SetInt(Const.AabbRoot, m_aabbTreeBufferRoot);

      ComputeManager.SetBuffer(Const.NodeHashTable, m_nodeHashTableBuffer);
      ComputeManager.SetBuffer(Const.NodePool, m_nodePoolBuffer);
      ComputeManager.SetBuffer(Const.NumNodesAllocated, m_numNodesAllocatedBuffer);
      ComputeManager.SetBuffer(Const.NumAllocations, m_numAllocationsBuffer);
      ComputeManager.SetBuffer(Const.VoxelCacheIdTable, UseVoxelCache ? m_voxelCacheIdTableBuffer : m_numAllocationsBuffer);
      ComputeManager.SetBuffer(Const.VoxelCache, UseVoxelCache ? m_voxelCacheBuffer : m_numAllocationsBuffer);
      ComputeManager.SetBuffer(Const.BrushMaskPool, m_brushMaskPoolBuffer);
      ComputeManager.SetBuffer(Const.IndirectDispatchArgs, m_indirectDispatchArgsBuffer);
      ComputeManager.SetBuffer(Const.GenPoints, m_genPointsBufferUsedForCompute);
      ComputeManager.SetBuffer(Const.IndirectDrawArgs, m_indirectDrawArgsBufferUsedForCompute);

      ComputeManager.SetBool(Const.Enable2dMode, Enable2dMode);
      ComputeManager.SetBool(Const.ForceAllBrushes, ShouldForceAllBrushes());
      ComputeManager.SetFloat(Const.VoxelSize, VoxelSize);
      ComputeManager.SetInt(Const.VoxelTreeBranchingFactorsCompressed, VoxelTreeBranchingFactorsComrpessed);
      ComputeManager.SetVector(Const.VoxelNodeSizes, NodeSizesVector);
      ComputeManager.SetInt(Const.MaxNodeDepth, VoxelNodeDepth);
      ComputeManager.SetInt(Const.ChunkVoxelDensity, ChunkVoxelDensity);
      ComputeManager.SetInt(Const.MaxGenPoints, MaxGenPoints);

      ComputeManager.SetTexture(Const.NoiseCache, s_noiseCache);
      //ComputeManager.SetTexture(Const.NoiseCache, NoiseCache);

      ComputeManager.SetFloats(Const.NoiseCacheDimension, NoiseCacheDimensionFloats);
      ComputeManager.SetFloat(Const.NoiseCacheDensity, NoiseCacheDensity);

      ComputeManager.SetInt(Const.MeshGenerationAutoRiggingAlgorithm, (int) MeshGenerationAutoRiggingAlgorithm);

      switch (MeshingMode)
      {
        case MeshingModeEnum.SurfaceNets:
          ComputeManager.SetFloat(Const.SurfaceNetsDualQuadsBlend, SurfaceNetsDualQuadsBlend);
          /*
          ComputeManager.SetInt(Const.SurfaceNetsBinarySearchIterations, SurfaceNetsBinarySearchIterations);
          ComputeManager.SetInt(Const.SurfaceNetsGradientDescentIterations, SurfaceNetsGradientDescentIterations);
          ComputeManager.SetFloat(Const.SurfaceNetsGradientDescentFactor, SurfaceNetsGradientDescentFactor);
          */
          /*
          if (SurfaceNetsHighAccuracyMode)
          {
            ComputeManager.SetInt(Const.SurfaceNetsBinarySearchIterations, 6);
            ComputeManager.SetInt(Const.SurfaceNetsGradientDescentIterations, 1);
            ComputeManager.SetFloat(Const.SurfaceNetsGradientDescentFactor, 1.0f);
          }
          else
          {
            ComputeManager.SetInt(Const.SurfaceNetsBinarySearchIterations, 0);
            ComputeManager.SetInt(Const.SurfaceNetsGradientDescentIterations, 0);
            ComputeManager.SetFloat(Const.SurfaceNetsGradientDescentFactor, 0.0f);
          }
          */
          break;

        case MeshingModeEnum.DualContouring:
          ComputeManager.SetFloat(Const.DualContouringDualQuadsBlend, DualContouringDualQuadsBlend);
          ComputeManager.SetFloat(Const.DualContouringRelaxation, 0.15f + 0.85f * DualContouringRelaxation);
          //ComputeManager.SetInt(Const.DualContouringSolverIterations, DualContouringSolverIterations);
          /*
          ComputeManager.SetInt(Const.DualContouringBinarySearchIterations, DualContouringBinarySearchIterations);
          ComputeManager.SetInt(Const.DualContouringGradientDescentIterations, DualContouringGradientDescentIterations);
          ComputeManager.SetFloat(Const.DualContouringGradientDescentFactor, DualContouringGradientDescentFactor);
          */
          if (DualContouringHighAccuracyMode)
          {
            ComputeManager.SetInt(Const.DualContouringSolverIterations, 0);
            ComputeManager.SetInt(Const.DualContouringBinarySearchIterations, 10);
            ComputeManager.SetInt(Const.DualContouringGradientDescentIterations, 1);
            ComputeManager.SetFloat(Const.DualContouringGradientDescentFactor, 1.0f);
          }
          else
          {
            ComputeManager.SetInt(Const.DualContouringSolverIterations, 0);
            ComputeManager.SetInt(Const.DualContouringBinarySearchIterations, 0);
            ComputeManager.SetInt(Const.DualContouringGradientDescentIterations, 0);
            ComputeManager.SetFloat(Const.DualContouringGradientDescentFactor, 0.0f);
          }
          break;
      }

      Profiler.EndSample();
    }

    private void BindRenderResources()
    {
      Profiler.BeginSample("BindRenderResources");
  
      if (m_materialProps == null)
      {
        m_materialProps = new MaterialPropertyBlock();
        m_needsCompute = true;
      }

      m_materialProps.SetBuffer(Const.GenPoints, m_genPointsBufferDefault);

      m_materialProps.SetVector(Const.MasterColor, MasterColor);
      m_materialProps.SetVector(Const.MasterEmission, MasterEmission);
      m_materialProps.SetFloat(Const.MasterMetallic, MasterMetallic);
      m_materialProps.SetFloat(Const.MasterSmoothness, MasterSmoothness);

      m_materialProps.SetInt(Const.InvertNormals, (InvertNormals && !Enable2dMode ? 1 : 0));

      m_materialProps.SetInt(Const.Enable2dMode, Enable2dMode ? 1 : 0);
      m_materialProps.SetFloat(Const.VoxelSize, VoxelSize);
      m_materialProps.SetInt(Const.VoxelTreeBranchingFactorsCompressed, VoxelTreeBranchingFactorsComrpessed);
      m_materialProps.SetVector(Const.VoxelNodeSizes, NodeSizesVector);
      m_materialProps.SetFloat(Const.SplatSize, SplatSize * (1.5f / VoxelDensity));
      m_materialProps.SetFloat(Const.SplatSizeJitter, SplatSizeJitter);
      m_materialProps.SetFloat(Const.SplatNormalShift, SplatNormalShift);
      m_materialProps.SetFloat(Const.SplatNormalShiftJitter, SplatNormalShiftJitter);
      m_materialProps.SetFloat(Const.SplatColorJitter, SplatColorJitter);
      m_materialProps.SetFloat(Const.SplatPositionJitter, SplatPositionJitter);
      m_materialProps.SetFloat(Const.SplatRotationJitter, SplatRotationJitter);
      m_materialProps.SetFloat(Const.SplatOrientationJitter, SplatOrientationJitter);
      m_materialProps.SetFloat(Const.SplatOriginalNormalBlend, SplatOriginalNormalBlend);
      m_materialProps.SetFloat(Const.SplatJitterNoisiness, SplatJitterNoisiness);
      m_materialProps.SetFloat(Const.SplatCameraFacing, SplatCameraFacing);
      m_materialProps.SetInt(Const.SplatNormalsMatchCameraFacing, SplatNormalsMatchCameraFacing ? 1 : 0);
      m_materialProps.SetInt(Const.SplatShadowsMatchCameraFacing, SplatShadowsMatchCameraFacing ? 1 : 0);
      m_materialProps.SetFloat(Const.SplatScreenSpaceFlattening, SplatScreenSpaceFlattening);
      //m_materialProps.SetFloat(Const.SplatSmoothNormalBlend, SplatSmoothNormalBlend);

      Vector3 scale = transform.localToWorldMatrix.lossyScale;
      m_materialProps.SetInt(Const.ScaleSign, (scale.x * scale.y * scale.z) >= 0.0f ? 1 : -1);
      m_materialProps.SetMatrix(Const.LocalToWorld, transform.localToWorldMatrix);
      m_materialProps.SetMatrix(Const.LocalToWorldIt, transform.localToWorldMatrix.inverse.transpose);
      m_materialProps.SetVector(Const.LocalToWorldScale, transform.localScale);
      m_materialProps.SetMatrix(Const.WorldToLocal, transform.worldToLocalMatrix);
      m_materialProps.SetMatrix(Const.WorldToLocalIt, transform.worldToLocalMatrix.inverse.transpose);

      m_materialProps.SetInt(Const.RenderMode, (int) RenderMode);
      m_materialProps.SetInt(Const.MeshingMode, (int) MeshingMode);
      m_materialProps.SetInt(Const.RayTracedVoxelMode, (int) RayTracedVoxelMode);
      m_materialProps.SetFloat(Const.RayTracedVoxelSizeMultiplier, RayTracedVoxelSizeMultiplier);
      m_materialProps.SetFloat(Const.RayTracedVoxelSmoothCubeNormal, RayTracedVoxelSmoothCubeNormal);
      m_materialProps.SetFloat(Const.RayTracedVoxelRadius, 1.0f + 0.733f * RayTracedVoxelSphereFullness);
      m_materialProps.SetInt(Const.RayTracedVoxelPaddingMode, (int) RayTracedVoxelPaddingMode);
      m_materialProps.SetInt(Const.RayTracedVoxelPaddingMode, (int)RayTracedVoxelPaddingMode);
      m_materialProps.SetFloat(Const.RayTracedVoxelInternalPaddingDistance, RayTracedVoxelInternalPaddingDistance);
      m_materialProps.SetFloat(Const.RayTracedVoxelSizeFadeDistance, RayTracedVoxelSizeFadeDistance);
      m_materialProps.SetFloat(Const.NormalQuantization, NormalQuantization);
      m_materialProps.SetFloat(Const.Normal2dFadeDist, Normal2dFade);
      m_materialProps.SetFloat(Const.Normal2dStrength, Normal2dStrength);

      m_materialProps.SetFloat(Const.AutoSmoothMaxAngle, EnableAutoSmoothing ? AutoSmoothingMaxAngle * MathUtil.Deg2Rad : -1.0f);

      //if (MaterialNeedsSdfProperties(m_materialUsed))
      switch (RenderModeCategory)
      {
        case RenderModeCategoryEnum.Decal:
        //case RenderModeCategoryEnum.RayMarchedSurface:
        //case RenderModeCategoryEnum.RayTracedVoxels:
          m_materialProps.SetBuffer(Const.NodeHashTable, m_nodeHashTableBuffer);
          m_materialProps.SetInt(Const.NodeHashTableSize, m_nodeHashTableBuffer.count);
          m_materialProps.SetBuffer(Const.NodePool, m_nodePoolBuffer);
          m_materialProps.SetBuffer(Const.VoxelCacheIdTable, UseVoxelCache ? m_voxelCacheIdTableBuffer : m_numAllocationsBuffer);
          m_materialProps.SetBuffer(Const.VoxelCache, UseVoxelCache ? m_voxelCacheBuffer : m_numAllocationsBuffer);
          m_materialProps.SetBuffer(Const.BrushMaskPool, m_brushMaskPoolBuffer);

          m_materialProps.SetBuffer(Const.Brushes, m_brushesBuffer);
          m_materialProps.SetBuffer(Const.BrushMaterials, m_brushMaterialBuffer);
          m_materialProps.SetInt(Const.NumBrushes, m_numSdfBrushes);

          m_materialProps.SetBuffer(Const.AabbTree, m_aabbTreeBuffer);
          m_materialProps.SetInt(Const.AabbRoot, m_aabbTreeBufferRoot);

          m_materialProps.SetTexture(Const.NoiseCache, NoiseCache);
          m_materialProps.SetVector(Const.NoiseCacheDimension, new Vector4(NoiseCacheDimensionFloats[0], NoiseCacheDimensionFloats[1], NoiseCacheDimensionFloats[2]));
          m_materialProps.SetFloat(Const.NoiseCacheDensity, NoiseCacheDensity);

          m_materialProps.SetFloat(Const.SurfaceShift, SurfaceShift);

          //if (MaterialNeedsRayMarchingProperties(m_materialUsed))
          if (false /* RenderModeCategory == RenderModeCategoryEnum.RayMarchedSurface */)
          {
            /*
            m_materialProps.SetInt(Const.MaxRayMarchSteps, MaxRayMarchSteps);
            m_materialProps.SetFloat(Const.RayMarchHitDistance, 1e-1f * Mathf.Pow(1e-1f, 2.0f * RayMarchAccuracy));
            m_materialProps.SetFloat(Const.RayMarchMaxRayDistance, RayMarchMaxRayDistance);
            */

            /*
            m_materialProps.SetFloat(Const.RayMarchStepSize, RayMarchDistance / Mathf.Max(1, MaxRayMarchSteps));
            m_materialProps.SetFloat(Const.RayMarchStepSize, RayMarchStepSize);
            m_materialProps.SetInt(Const.NumLightMarchSteps, NumLightMarchSteps);
            m_materialProps.SetFloat(Const.RayMarchVolumeDensity, RayMarchVolumeDensity);
            if (RayMarchLight != null)
            {
              Vector3 lightPos = RayMarchLight.transform.position;
              m_materialProps.SetVector(Const.RayMarchLightPositionType, new Vector4(lightPos.x, lightPos.y, lightPos.z, (int) RayMarchLight.type));
              m_materialProps.SetVector(Const.RayMarchLightDirection, RayMarchLight.transform.forward);
            }
            else
            {
              m_materialProps.SetVector(Const.RayMarchLightPositionType, new Vector4(0.0f, 0.0f, 0.0f, (int) LightType.Directional));
              m_materialProps.SetVector(Const.RayMarchLightDirection, Vector3.down);
            }
            m_materialProps.SetVector(Const.RayMarchAbsorption, new Vector4(RayMarchVolumeAbsorption, RayMarchLightAbsorption));
            m_materialProps.SetFloat(Const.RayMarchDarknesThreshold, RayMarchDarknesThreshold);
            m_materialProps.SetFloat(Const.RayMarchTransmittanceCurve, RayMarchTransmittanceCurve);
            m_materialProps.SetFloat(Const.RayMarchNoiseEdgeFade, RayMarchNoiseEdgeFade);
            m_materialProps.SetFloat(Const.RayMarchNoiseThreshold, UseRayMarchNoise ? RayMarchNoiseThreshold : 0.0f);
            m_materialProps.SetVector(Const.RayMarchNoiseScrollSpeed, RayMarchNoiseScrollSpeed);
            m_materialProps.SetVector(Const.RayMarchNoiseBaseOctaveSize, RayMarchNoiseBaseOctaveSize);
            m_materialProps.SetInt(Const.RayMarchNoiseNumOctaves, RayMarchNoiseNumOctaves);
            m_materialProps.SetFloat(Const.RayMarchNoiseOctaveOffsetFactor, RayMarchNoiseOctaveOffsetFactor);
            */
          }
          break;
      }

      Profiler.EndSample();
    }

    #endregion // end: Brushes

    //-------------------------------------------------------------------------

    #region Core

    public ComputeModeEnum ComputeMode = ComputeModeEnum.Auto;
    public TimeSliceModeEnum TimeSliceMode = TimeSliceModeEnum.ByPeriodAutoOffset;
    [Min(1)] public int TimeSliceFrames = 3;
    public int TimeSliceFramesOffset = 0;
    [Min(0.0001f)] public float TimeSlicePeriod = 0.05f;
    public float TimeSliceTimeOffset = 0;
    private float m_lastUpdateTimeSliceTime = -1.0f;

    private int m_rendererIndex = -1;

    private void SetComputeNodeDepth(int depth)
    {
      ComputeManager.SetInt(Const.CurrentNodeDepth, depth);
      ComputeManager.SetInt(Const.CurrentNodeBranchingFactor, (depth < VoxelNodeDepth ? VoxelTreeBranchingFactors[depth] : 0));
      ComputeManager.SetFloat(Const.CurrentNodeSize, NodeSizes[depth]);
    }

    private void ComputeGpu()
    {
      Profiler.BeginSample("Compute (GPU)");

      UpdateActivePreCompute();

      SetUpResources();

      Profiler.BeginSample("Core Dispatch");

      ComputeManager.Dispatch
      (
        s_computeVoxelGen, 
        Const.Kernel.ClearVoxelHashTable, 
        Mathf.Max(1, (m_nodeHashTableBuffer.count + ClearThreadGroupSize - 1) / ClearThreadGroupSize), 1, 1
      );

      if (ShouldDoAutoSmoothing)
      {
        ComputeManager.Dispatch
        (
          s_computeVoxelGen, 
          Const.Kernel.ClearAutoSmoothVertDataTable, 
          Mathf.Max(1, (m_autoSmoothVertDataTableBuffer.count + ClearThreadGroupSize - 1) / ClearThreadGroupSize), 1, 1
        );
      }

      if (UseVoxelCache)
      {
        ComputeManager.Dispatch
        (
          s_computeVoxelGen, 
          Const.Kernel.ClearVoxelCache, 
          Mathf.Max(1, (m_voxelCacheIdTableBuffer.count + ClearThreadGroupSize - 1) / ClearThreadGroupSize), 1, 1
        );
      }

      SetComputeNodeDepth(0);
      ComputeManager.Dispatch
      (
        s_computeVoxelGen, 
        Const.Kernel.RegisterTopNodes, 
        Mathf.Max(1, (m_numSdfBrushes + ThreadGroupSize - 1) / ThreadGroupSize), 1, 1
      );

      for (int depth = 0; depth < VoxelNodeDepth; ++depth)
      {
        SetComputeNodeDepth(depth);
        ComputeManager.Dispatch(s_computeVoxelGen, Const.Kernel.UpdateBranchingIndirectDispatchArgs, 1, 1, 1);
        ComputeManager.DispatchIndirect(s_computeVoxelGen, Const.Kernel.AllocateChildNodes, m_indirectDispatchArgsBuffer);
      }

      SetComputeNodeDepth(VoxelNodeDepth);
      ComputeManager.Dispatch(s_computeVoxelGen, Const.Kernel.UpdateVoxelIndirectDispatchArgs, 1, 1, 1);

      switch (RenderModeCategory)
      {
        case RenderModeCategoryEnum.Mesh:
        case RenderModeCategoryEnum.Splats:
        case RenderModeCategoryEnum.Decal:
          switch (MeshingMode)
          {
            case MeshingModeEnum.MarchingCubes:
              switch (RenderMode)
              {
                case RenderModeEnum.FlatMesh:
                  if (Enable2dMode)
                    ComputeManager.DispatchIndirect(s_computeMarchingCubes, Const.Kernel.GenerateFlatMarchingCubesMesh2d, m_indirectDispatchArgsBuffer);
                  else
                    ComputeManager.DispatchIndirect(s_computeMarchingCubes, Const.Kernel.GenerateFlatMarchingCubesMesh, m_indirectDispatchArgsBuffer);
                  if (ShouldDoAutoSmoothing)
                  {
                    ComputeManager.Dispatch(s_computeMarchingCubes, Const.Kernel.UpdateMarchingCubesAutoSmoothIndirectDispatchArgs, 1, 1, 1);
                    ComputeManager.DispatchIndirect(s_computeMarchingCubes, Const.Kernel.MarchingCubesUpdateAutoSmooth, m_indirectDispatchArgsBuffer);
                    ComputeManager.DispatchIndirect(s_computeMarchingCubes, Const.Kernel.MarchingCubesComputeAutoSmooth, m_indirectDispatchArgsBuffer);
                  }
                  break;

                case RenderModeEnum.SmoothMesh:
                  if (ShouldDoAutoSmoothing)
                  {
                    ComputeManager.DispatchIndirect(s_computeMarchingCubes, Const.Kernel.GenerateFlatMarchingCubesMesh, m_indirectDispatchArgsBuffer);
                    ComputeManager.Dispatch(s_computeMarchingCubes, Const.Kernel.UpdateMarchingCubesAutoSmoothIndirectDispatchArgs, 1, 1, 1);
                    ComputeManager.DispatchIndirect(s_computeMarchingCubes, Const.Kernel.MarchingCubesUpdateAutoSmooth, m_indirectDispatchArgsBuffer);
                    ComputeManager.DispatchIndirect(s_computeMarchingCubes, Const.Kernel.MarchingCubesComputeAutoSmooth, m_indirectDispatchArgsBuffer);
                  }
                  else
                  {
                    if (Enable2dMode)
                      ComputeManager.DispatchIndirect(s_computeMarchingCubes, Const.Kernel.GenerateSmoothMarchingCubesMesh2d, m_indirectDispatchArgsBuffer);
                    else
                      ComputeManager.DispatchIndirect(s_computeMarchingCubes, Const.Kernel.GenerateSmoothMarchingCubesMesh, m_indirectDispatchArgsBuffer);
                  }
                  break;

                case RenderModeEnum.CircleSplats:
                case RenderModeEnum.QuadSplats:
                  if (Enable2dMode)
                    ComputeManager.DispatchIndirect(s_computeMarchingCubes, Const.Kernel.GenerateMarchingCubesSplats2d, m_indirectDispatchArgsBuffer);
                  else
                    ComputeManager.DispatchIndirect(s_computeMarchingCubes, Const.Kernel.GenerateMarchingCubesSplats, m_indirectDispatchArgsBuffer);
                  break;
              }
              break;

            case MeshingModeEnum.DualQuads:
              if (Enable2dMode)
              {
                ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.GenerateDualQuads2d, m_indirectDispatchArgsBuffer);
              }
              else
              {
                ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.GenerateDualQuads, m_indirectDispatchArgsBuffer);
                switch (RenderMode)
                {
                  case RenderModeEnum.FlatMesh:
                    // do nothing
                    break;

                  case RenderModeEnum.SmoothMesh:
                    ComputeManager.Dispatch(s_computeDualMeshing, Const.Kernel.UpdateDualMeshingIndirectDispatchArgs, 1, 1, 1);
                    ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingSmoothMeshNormal, m_indirectDispatchArgsBuffer);
                    break;
                }
              }
              break;

            case MeshingModeEnum.SurfaceNets:
              if (Enable2dMode)
                ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.GenerateDualQuads2d, m_indirectDispatchArgsBuffer);
              else
                ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.GenerateDualQuads, m_indirectDispatchArgsBuffer);
              ComputeManager.Dispatch(s_computeDualMeshing, Const.Kernel.UpdateDualMeshingIndirectDispatchArgs, 1, 1, 1);
              if (Enable2dMode)
                ComputeManager.DispatchIndirect(s_computeSurfaceNets, Const.Kernel.SurfaceNetsMovePoint2d, m_indirectDispatchArgsBuffer);
              else
                ComputeManager.DispatchIndirect(s_computeSurfaceNets, Const.Kernel.SurfaceNetsMovePoint, m_indirectDispatchArgsBuffer);
              switch (RenderMode)
              {
                case RenderModeEnum.FlatMesh:
                  if (ShouldDoAutoSmoothing)
                  {
                    ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingUpdateAutoSmooth, m_indirectDispatchArgsBuffer);
                    ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingComputeAutoSmooth, m_indirectDispatchArgsBuffer);
                  }
                  else
                  {
                    if (Enable2dMode)
                      ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingFlatMeshNormal2d, m_indirectDispatchArgsBuffer);
                    else
                      ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingFlatMeshNormal, m_indirectDispatchArgsBuffer);
                  }
                  break;

                case RenderModeEnum.SmoothMesh:
                  if (ShouldDoAutoSmoothing)
                  {
                    ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingUpdateAutoSmooth, m_indirectDispatchArgsBuffer);
                    ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingComputeAutoSmooth, m_indirectDispatchArgsBuffer);
                  }
                  else
                  {
                    if (Enable2dMode)
                      ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingSmoothMeshNormal2d, m_indirectDispatchArgsBuffer);
                    else
                      ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingSmoothMeshNormal, m_indirectDispatchArgsBuffer);
                  }
                  break;

                case RenderModeEnum.CircleSplats:
                case RenderModeEnum.QuadSplats:
                  ComputeManager.Dispatch(s_computeDualMeshing, Const.Kernel.UpdateDualMeshingSplatsIndirectArgs, 1, 1, 1);
                  ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.ConvertDualMeshingSplats, m_indirectDispatchArgsBuffer);
                  break;
              }
              break;

            case MeshingModeEnum.DualContouring:
              if (Enable2dMode)
                ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.GenerateDualQuads2d, m_indirectDispatchArgsBuffer);
              else
                ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.GenerateDualQuads, m_indirectDispatchArgsBuffer);
              ComputeManager.Dispatch(s_computeDualMeshing, Const.Kernel.UpdateDualMeshingIndirectDispatchArgs, 1, 1, 1);
              if (Enable2dMode)
                ComputeManager.DispatchIndirect(s_computeDualContouring, Const.Kernel.DualContouringMovePoint2d, m_indirectDispatchArgsBuffer);
              else
                ComputeManager.DispatchIndirect(s_computeDualContouring, Const.Kernel.DualContouringMovePoint, m_indirectDispatchArgsBuffer);
              switch (RenderMode)
              {
                case RenderModeEnum.FlatMesh:
                  if (ShouldDoAutoSmoothing)
                  {
                    ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingUpdateAutoSmooth, m_indirectDispatchArgsBuffer);
                    ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingComputeAutoSmooth, m_indirectDispatchArgsBuffer);
                    if (EnableSmoothCorner)
                    {
                      ComputeManager.Dispatch(s_computeDualMeshing, Const.Kernel.DualMeshingUpdateSmoothCornerIndirectDispatchArgs, 1, 1, 1);
                      ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingSmoothCorner, m_indirectDispatchArgsBuffer);
                    }
                  }
                  else
                  {
                    if (Enable2dMode)
                      ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingFlatMeshNormal2d, m_indirectDispatchArgsBuffer);
                    else
                      ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingFlatMeshNormal, m_indirectDispatchArgsBuffer);
                  }
                  break;

                case RenderModeEnum.SmoothMesh:
                  if (ShouldDoAutoSmoothing)
                  {
                    ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingUpdateAutoSmooth, m_indirectDispatchArgsBuffer);
                    ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingComputeAutoSmooth, m_indirectDispatchArgsBuffer);
                    if (EnableSmoothCorner)
                    {
                      ComputeManager.Dispatch(s_computeDualMeshing, Const.Kernel.DualMeshingUpdateSmoothCornerIndirectDispatchArgs, 1, 1, 1);
                      ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingSmoothCorner, m_indirectDispatchArgsBuffer);
                    }
                  }
                  else
                  {
                    if (Enable2dMode)
                      ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingSmoothMeshNormal2d, m_indirectDispatchArgsBuffer);
                    else
                      ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.DualMeshingSmoothMeshNormal, m_indirectDispatchArgsBuffer);
                  }
                  break;

                case RenderModeEnum.CircleSplats:
                case RenderModeEnum.QuadSplats:
                  ComputeManager.Dispatch(s_computeDualMeshing, Const.Kernel.UpdateDualMeshingSplatsIndirectArgs, 1, 1, 1);
                  ComputeManager.DispatchIndirect(s_computeDualMeshing, Const.Kernel.ConvertDualMeshingSplats, m_indirectDispatchArgsBuffer);
                  break;
              }
              break;
          }
          break;

          // done in voxel gen
          /*
          case RenderModeCategoryEnum.RayTracedVoxels:
            ComputeManager.Dispatch(s_computeRayTracedVoxels, Const.Kernel.UpdateRayTracedVoxelIndirectDispatchArgs, 1, 1, 1);
            switch(RayTracedVoxelMode)
            {
              case RayTracedVoxelModeEnum.CubesFaceted:
                ComputeManager.DispatchIndirect(s_computeRayTracedVoxels, Const.Kernel.ComputeRayTracedVoxelGenPoints, m_indirectDispatchArgsBuffer);
                break;
              case RayTracedVoxelModeEnum.CubesFlat:
                ComputeManager.DispatchIndirect(s_computeRayTracedVoxels, Const.Kernel.ComputeRayTracedVoxelGenPointsWithNormals, m_indirectDispatchArgsBuffer);
                break;
            }
          break;
          */
      }

      Profiler.EndSample();

      if (MudBun.IsFreeVersion)
      {
        if (LockMeshIntermediateState != LockMeshIntermediateStateEnum.Idle 
            && m_indirectDrawArgsBufferUsedForCompute != null)
        {
          int[] aIndirectDrawArgs = new int[5];
          m_indirectDrawArgsBufferUsedForCompute.GetData(aIndirectDrawArgs);
          aIndirectDrawArgs[0] = Mathf.Min(aIndirectDrawArgs[0], 3 * MaxMeshGenerationTrianglesFreeVersion);
          m_indirectDrawArgsBufferUsedForCompute.SetData(aIndirectDrawArgs);
        }
      }

      if (m_doRigging)
      {
        Profiler.BeginSample("Rigging Dispatch");

        ComputeManager.SetBuffer(Const.IndirectDrawArgs, m_indirectDrawArgsBufferUsedForCompute);
        ComputeManager.SetBuffer(Const.GenPoints, m_genPointsBufferUsedForCompute);
        ComputeManager.SetBuffer(Const.BrushMaterials, m_brushMaterialBuffer);
        ComputeManager.SetBuffer(Const.Brushes, m_brushesBuffer);
        ComputeManager.SetTexture(Const.NoiseCache, NoiseCache);
        ComputeManager.SetInt(Const.NumBrushes, m_numSdfBrushes);

        int[] aIndirectDrawArgs = new int[5];
        m_indirectDrawArgsBufferUsedForCompute.GetData(aIndirectDrawArgs);
        ComputeManager.Dispatch
        (
          s_computeMeshLock, 
          Const.Kernel.RigBones, 
          (aIndirectDrawArgs[0] + ThreadGroupSize - 1) / ThreadGroupSize, 1, 1
        );

        Profiler.EndSample();
      }

      // only use buffer overrides once
      if (m_genPointsBufferOverride != null)
        m_genPointsBufferOverride = null;
      if (m_indirectDrawArgsBufferOverride != null)
        m_indirectDrawArgsBufferOverride = null;

      UpdateActivePostCompute();

      Profiler.EndSample();
    }

    private void ComputeCpu()
    {
      Profiler.BeginSample("Compute (CPU)");

      // TODO

      Profiler.EndSample();
    }

    private Mesh m_renderBoxProxy;

    private void Render()
    {
      if (IsEditorBusy())
        return;

      if (m_indirectDrawArgsBufferDefault == null)
        return;

      if (!s_globalResourcesValid || !m_localResourcesValid)
        return;

      Profiler.BeginSample("Render");

      ValidateRenderMaterial();
      BindRenderResources();

      Aabb renderBounds = RenderBounds;
      if (renderBounds.IsEmpty)
      {
        renderBounds.Min = -Vector3.one;
        renderBounds.Max = Vector3.one;
      }

      switch (RenderGeometryType)
      {
        case RenderGeometryTypeEnum.Mesh:
        case RenderGeometryTypeEnum.Chunks:
          Graphics.DrawProceduralIndirect
          (
            m_materialUsed, 
            new Bounds(renderBounds.Center, renderBounds.Size), 
            MeshTopology.Triangles, 
            m_indirectDrawArgsBufferDefault, 
            0, 
            null, 
            m_materialProps, 
            CastShadows, 
            ReceiveShadows, 
            gameObject.layer
          );
          break;

        case RenderGeometryTypeEnum.BoxProxy:
          MeshUtil.UpdateRenderBoxProxy(ref m_renderBoxProxy, renderBounds);
          Graphics.DrawMesh
          (
            m_renderBoxProxy, 
            Matrix4x4.identity, 
            m_materialUsed, 
            gameObject.layer, 
            null, 
            0, 
            m_materialProps, 
            CastShadows, 
            ReceiveShadows
          );
          break;
      }

      Profiler.EndSample();
    }

    #endregion // end: Core
  }
}

