Untitled

mail@pastecode.io avatar
unknown
csharp
a year ago
40 kB
2
Indexable
Never
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using System.Threading;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace GPUInstancer
{
    public abstract class GPUInstancerManager : MonoBehaviour
    {
        public List<GPUInstancerPrototype> prototypeList = new List<GPUInstancerPrototype>();

        public bool autoSelectCamera = true;
        public GPUInstancerCameraData cameraData = new GPUInstancerCameraData(null);

        public bool useFloatingOriginHandler = false;
        public bool applyFloatingOriginRotationAndScale = false;
        public Transform floatingOriginTransform;
        [NonSerialized]
        public GPUInstancerFloatingOriginHandler floatingOriginHandler;

        [NonSerialized]
        public List<GPUInstancerRuntimeData> runtimeDataList;
        [NonSerialized]
        public Bounds instancingBounds;

        public bool isFrustumCulling = true;
        public bool isOcclusionCulling = true;
        public float minCullingDistance = 0;

        protected GPUInstancerSpatialPartitioningData<GPUInstancerCell> spData;

        public static List<GPUInstancerManager> activeManagerList;
        public static bool showRenderedAmount;

        protected static ComputeShader _cameraComputeShader;
        protected static ComputeShader _cameraComputeShaderVR;
        protected static int[] _cameraComputeKernelIDs;
        protected static ComputeShader _visibilityComputeShader;
        protected static int[] _instanceVisibilityComputeKernelIDs;
        protected static ComputeShader _bufferToTextureComputeShader;
        protected static int _bufferToTextureComputeKernelID;
        protected static ComputeShader _argsBufferComputeShader;
        protected static int _argsBufferDoubleInstanceCountComputeKernelID;

#if UNITY_EDITOR
        public List<GPUInstancerPrototype> selectedPrototypeList;
        [NonSerialized]
        public GPUInstancerEditorSimulator gpuiSimulator;
        public bool isPrototypeTextMode = false;

        public bool showSceneSettingsBox = true;
        public bool showPrototypeBox = true;
        public bool showAdvancedBox = false;
        public bool showHelpText = false;
        public bool showDebugBox = true;
        public bool showGlobalValuesBox = true;
        public bool showRegisteredPrefabsBox = true;
        public bool showPrototypesBox = true;

        public bool keepSimulationLive = false;
        public bool updateSimulation = true;
#endif

        public class GPUIThreadData
        {
            public Thread thread;
            public object parameter;
        }
        public static int maxThreads = 3;
        public readonly List<Thread> activeThreads = new List<Thread>();
        public readonly Queue<GPUIThreadData> threadStartQueue = new Queue<GPUIThreadData>();
        public readonly Queue<Action> threadQueue = new Queue<Action>();

        // Tree variables
        public static int lastTreePositionUpdate;
        public static GameObject treeProxyParent;
        public static Dictionary<GameObject, Transform> treeProxyList; // Dict[TreePrefab, TreeProxyGO]

        // Time management
        public static int lastDrawCallFrame;
        public static float lastDrawCallTime;
        public static float timeSinceLastDrawCall;

        // Global Wind
        protected static Vector4 _windVector = Vector4.zero;

        [NonSerialized]
        protected bool isInitial = true;

        [NonSerialized]
        public bool isInitialized = false;

#if UNITY_EDITOR && UNITY_2017_2_OR_NEWER
        [NonSerialized]
        public PlayModeStateChange playModeState;
#endif
        [NonSerialized]
        public bool isQuiting = false;
        [NonSerialized]
        public Dictionary<GPUInstancerPrototype, GPUInstancerRuntimeData> runtimeDataDictionary;

        public LayerMask layerMask = ~0;
        public bool lightProbeDisabled = false;

        #region MonoBehaviour Methods

        public virtual void Awake()
		{
#if UNITY_EDITOR			
			if (Application.isBatchMode) return;
#endif
			
            GPUInstancerConstants.gpuiSettings.SetDefultBindings();
            GPUInstancerUtility.SetPlatformDependentVariables();

#if UNITY_EDITOR
			// BKOM Modification to 3rd-party code - sgravel 2021-10-28
			// Remove this line to prevent the editor from checking out all prototypes on editor launch
			// if (!Application.isPlaying)
			//    CheckPrototypeChanges();
#endif
            if (Application.isPlaying && activeManagerList == null)
                activeManagerList = new List<GPUInstancerManager>();

            if (SystemInfo.supportsComputeShaders)
            {
                if (_visibilityComputeShader == null)
                {
                    switch (GPUInstancerUtility.matrixHandlingType)
                    {
                        case GPUIMatrixHandlingType.MatrixAppend:
                            _visibilityComputeShader = (ComputeShader)Resources.Load(GPUInstancerConstants.VISIBILITY_COMPUTE_RESOURCE_PATH_VULKAN);
                            GPUInstancerConstants.DETAIL_STORE_INSTANCE_DATA = true;
                            GPUInstancerConstants.COMPUTE_MAX_LOD_BUFFER = 2;
                            break;
                        case GPUIMatrixHandlingType.CopyToTexture:
                            _visibilityComputeShader = (ComputeShader)Resources.Load(GPUInstancerConstants.VISIBILITY_COMPUTE_RESOURCE_PATH);
                            _bufferToTextureComputeShader = (ComputeShader)Resources.Load(GPUInstancerConstants.BUFFER_TO_TEXTURE_COMPUTE_RESOURCE_PATH);
                            _bufferToTextureComputeKernelID = _bufferToTextureComputeShader.FindKernel(GPUInstancerConstants.BUFFER_TO_TEXTURE_KERNEL);
                            break;
                        default:
                            _visibilityComputeShader = (ComputeShader)Resources.Load(GPUInstancerConstants.VISIBILITY_COMPUTE_RESOURCE_PATH);
                            break;
                    }

                    _instanceVisibilityComputeKernelIDs = new int[GPUInstancerConstants.VISIBILITY_COMPUTE_KERNELS.Length];
                    for (int i = 0; i < _instanceVisibilityComputeKernelIDs.Length; i++)
                        _instanceVisibilityComputeKernelIDs[i] = _visibilityComputeShader.FindKernel(GPUInstancerConstants.VISIBILITY_COMPUTE_KERNELS[i]);
                    GPUInstancerConstants.TEXTURE_MAX_SIZE = SystemInfo.maxTextureSize;

                    _cameraComputeShader = (ComputeShader)Resources.Load(GPUInstancerConstants.CAMERA_COMPUTE_RESOURCE_PATH);
                    _cameraComputeShaderVR = (ComputeShader)Resources.Load(GPUInstancerConstants.CAMERA_VR_COMPUTE_RESOURCE_PATH);
                    _cameraComputeKernelIDs = new int[GPUInstancerConstants.CAMERA_COMPUTE_KERNELS.Length];
                    for (int i = 0; i < _cameraComputeKernelIDs.Length; i++)
                        _cameraComputeKernelIDs[i] = _cameraComputeShader.FindKernel(GPUInstancerConstants.CAMERA_COMPUTE_KERNELS[i]);

                    _argsBufferComputeShader = Resources.Load<ComputeShader>(GPUInstancerConstants.ARGS_BUFFER_COMPUTE_RESOURCE_PATH);
                    _argsBufferDoubleInstanceCountComputeKernelID = _argsBufferComputeShader.FindKernel(GPUInstancerConstants.ARGS_BUFFER_DOUBLE_INSTANCE_COUNT_KERNEL);
                }

                GPUInstancerConstants.SetupComputeRuntimeModification();
                GPUInstancerConstants.SetupComputeSetDataPartial();
            }
            else if (Application.isPlaying)
            {
                Debug.LogError("Target Graphics API does not support Compute Shaders. Please refer to Minimum Requirements on GPUInstancer/ReadMe.txt for detailed information.");
                this.enabled = false;
            }

            showRenderedAmount = false;

            InitializeCameraData();

#if UNITY_EDITOR && UNITY_2017_2_OR_NEWER
            EditorApplication.playModeStateChanged -= HandlePlayModeStateChanged;
            EditorApplication.playModeStateChanged += HandlePlayModeStateChanged;
#endif
        }

        public virtual void Start()
        {
            if (Application.isPlaying && SystemInfo.supportsComputeShaders)
            {
                SetupOcclusionCulling(cameraData);
            }
#if UNITY_EDITOR
            // Sometimes Awake is not called on Editor Mode after compilation
            else if (_instanceVisibilityComputeKernelIDs == null)
                Awake();
#endif
        }

        public virtual void OnEnable()
        {
#if UNITY_EDITOR
            if (gpuiSimulator == null)
                gpuiSimulator = new GPUInstancerEditorSimulator(this);
#endif

            if (!Application.isPlaying)
                return;

            if (cameraData.mainCamera == null)
            {
                InitializeCameraData();
                if (cameraData.mainCamera == null)
                    Debug.LogWarning(GPUInstancerConstants.ERRORTEXT_cameraNotFound);
            }

            if (activeManagerList != null && !activeManagerList.Contains(this))
                activeManagerList.Add(this);

            if (SystemInfo.supportsComputeShaders)
            {
                if (GPUInstancerConstants.gpuiSettings == null || GPUInstancerConstants.gpuiSettings.shaderBindings == null)
                    Debug.LogWarning("No shader bindings file was supplied. Instancing will terminate!");

                if (runtimeDataList == null || runtimeDataList.Count == 0)
                    InitializeRuntimeDataAndBuffers();
                isInitial = true;
            }

            if (useFloatingOriginHandler && floatingOriginTransform != null)
            {
                if (floatingOriginHandler == null)
                {
                    floatingOriginHandler = floatingOriginTransform.gameObject.GetComponent<GPUInstancerFloatingOriginHandler>();
                    if (floatingOriginHandler == null)
                        floatingOriginHandler = floatingOriginTransform.gameObject.AddComponent<GPUInstancerFloatingOriginHandler>();
                }
                floatingOriginHandler.applyRotationAndScale = applyFloatingOriginRotationAndScale;
                if (floatingOriginHandler.gPUIManagers == null)
                    floatingOriginHandler.gPUIManagers = new List<GPUInstancerManager>();
                if (!floatingOriginHandler.gPUIManagers.Contains(this))
                    floatingOriginHandler.gPUIManagers.Add(this);
            }
        }

        public virtual void Update()
        {
            ClearCompletedThreads();
            while (threadStartQueue.Count > 0 && activeThreads.Count < maxThreads)
            {
                GPUIThreadData threadData = threadStartQueue.Dequeue();
                threadData.thread.Start(threadData.parameter);
                activeThreads.Add(threadData.thread);
            }
            if (threadQueue.Count > 0)
            {
                Action action = threadQueue.Dequeue();
                if (action != null)
                    action.Invoke();
            }

            if (Application.isPlaying && treeProxyParent && lastTreePositionUpdate != Time.frameCount && cameraData.mainCamera != null)
            {
                treeProxyParent.transform.position = cameraData.mainCamera.transform.position;
                treeProxyParent.transform.rotation = cameraData.mainCamera.transform.rotation;
                lastTreePositionUpdate = Time.frameCount;
            }
        }

        public virtual void LateUpdate()
        {
#if UNITY_EDITOR
			if (!Application.isPlaying)
			{
				// BKOM Modification to 3rd-party code - cmacdougall 2021-06-10
				// Remove this line to prevent the editor from periodically freezing
				// CheckPrototypeChanges();
			}
            else
            {
#endif
                if (cameraData.mainCamera != null)
                {
                    UpdateTreeMPB();
                    UpdateBuffers(cameraData);
                }
#if UNITY_EDITOR
            }
#endif
        }

        public virtual void OnDestroy()
        {
        }

        public virtual void Reset()
        {
            GPUInstancerConstants.gpuiSettings.SetDefultBindings();
#if UNITY_EDITOR
			// BKOM Modification to 3rd-party code - sgravel 2021-10-28
			// Remove this line to prevent the editor from checking out all prototypes on editor launch
			//CheckPrototypeChanges();
#endif
        }

        public virtual void OnDisable() // could also be OnDestroy, but OnDestroy seems to be too late to prevent buffer leaks.
        {
            if (activeManagerList != null)
                activeManagerList.Remove(this);

            ClearInstancingData();
#if UNITY_EDITOR
            if (gpuiSimulator != null)
            {
                gpuiSimulator.ClearEditorUpdates();
                gpuiSimulator = null;
            }
#endif

            if (floatingOriginHandler != null && floatingOriginHandler.gPUIManagers != null && floatingOriginHandler.gPUIManagers.Contains(this))
            {
                floatingOriginHandler.gPUIManagers.Remove(this);
            }
        }

        private void OnApplicationQuit()
        {
            isQuiting = true;
        }
        #endregion MonoBehaviour Methods

        #region Virtual Methods

        public virtual void ClearInstancingData()
        {
            GPUInstancerUtility.ReleaseInstanceBuffers(runtimeDataList);
            GPUInstancerUtility.ReleaseSPBuffers(spData);
            if (runtimeDataList != null)
                runtimeDataList.Clear();
            if (runtimeDataDictionary != null)
                runtimeDataDictionary.Clear();
            spData = null;
            threadStartQueue.Clear();
            threadQueue.Clear();
            isInitialized = false;
        }

        public virtual void GeneratePrototypes(bool forceNew = false)
        {
            ClearInstancingData();

            if (forceNew || prototypeList == null)
                prototypeList = new List<GPUInstancerPrototype>();
            else
                prototypeList.RemoveAll(p => p == null);

            GPUInstancerConstants.gpuiSettings.SetDefultBindings();
        }

#if UNITY_EDITOR
		// BKOM Modification to 3rd-party code -fchene 2021-07-14
		// Added the attribute to replace the check on update
		[ContextMenu("Check Prototype Changes")]
        public virtual void CheckPrototypeChanges()
        {
            GPUInstancerConstants.gpuiSettings.SetDefultBindings();

            if (prototypeList == null)
                GeneratePrototypes();
            else
                prototypeList.RemoveAll(p => p == null);

            if (GPUInstancerConstants.gpuiSettings != null && GPUInstancerConstants.gpuiSettings.shaderBindings != null)
            {
                GPUInstancerConstants.gpuiSettings.shaderBindings.ClearEmptyShaderInstances();
                foreach (GPUInstancerPrototype prototype in prototypeList)
                {
                    if (prototype.PrefabObject != null)
                    {
                        GPUInstancerUtility.GenerateInstancedShadersForGameObject(prototype);
                        if (string.IsNullOrEmpty(prototype.warningText))
                        {
                            if (prototype.PrefabObject.GetComponentInChildren<MeshRenderer>() == null)
                            {
                                prototype.warningText = "Prefab object does not contain any Mesh Renderers.";
                            }
                        }
                    }
                    else
                    {
                        if (GPUInstancerConstants.gpuiSettings.IsStandardRenderPipeline())
                            GPUInstancerConstants.gpuiSettings.AddShaderVariantToCollection(GPUInstancerConstants.SHADER_GPUI_FOLIAGE);
                        else if (GPUInstancerConstants.gpuiSettings.isURP)
                        {
                            if (Shader.Find(GPUInstancerConstants.SHADER_GPUI_FOLIAGE_URP) != null)
                                GPUInstancerConstants.gpuiSettings.AddShaderVariantToCollection(GPUInstancerConstants.SHADER_GPUI_FOLIAGE_URP);
                        }
                        else if (GPUInstancerConstants.gpuiSettings.isLWRP)
                        {
                            if (Shader.Find(GPUInstancerConstants.SHADER_GPUI_FOLIAGE_LWRP) != null)
                                GPUInstancerConstants.gpuiSettings.AddShaderVariantToCollection(GPUInstancerConstants.SHADER_GPUI_FOLIAGE_LWRP);
                        }
                        else if (GPUInstancerConstants.gpuiSettings.isHDRP)
                        {
                            if (Shader.Find(GPUInstancerConstants.SHADER_GPUI_FOLIAGE_HDRP) != null)
                                GPUInstancerConstants.gpuiSettings.AddShaderVariantToCollection(GPUInstancerConstants.SHADER_GPUI_FOLIAGE_HDRP);
                        }
                    }
                }
            }
            if (GPUInstancerConstants.gpuiSettings != null && GPUInstancerConstants.gpuiSettings.billboardAtlasBindings != null)
            {
                GPUInstancerConstants.gpuiSettings.billboardAtlasBindings.ClearEmptyBillboardAtlases();
                //foreach (GPUInstancerPrototype prototype in prototypeList)
                //{
                //    if (prototype.prefabObject != null && prototype.useGeneratedBillboard && 
                //        (prototype.billboard == null || prototype.billboard.albedoAtlasTexture == null || prototype.billboard.normalAtlasTexture == null))
                //        GPUInstancerUtility.GeneratePrototypeBillboard(prototype, billboardAtlasBindings);
                //}
            }
        }
//----------------------------------BKOM MODIFICATIONS------------------------------------------------
		[ContextMenu ("Sort Items")]
		public virtual void SortItems ()
		{
			prototypeList.Sort ((p1, p2) => {
				return p1.name.CompareTo (p2.name);
			});
		}
//------------------------------END BKOM MODIFICATIONS------------------------------------------------
#endif
        public virtual void InitializeRuntimeDataAndBuffers(bool forceNew = true)
        {
            GPUInstancerUtility.SetPlatformDependentVariables();
            if (forceNew || !isInitialized)
            {
                instancingBounds = new Bounds(Vector3.zero, Vector3.one * GPUInstancerConstants.gpuiSettings.instancingBoundsSize);

                GPUInstancerUtility.ReleaseInstanceBuffers(runtimeDataList);
                GPUInstancerUtility.ReleaseSPBuffers(spData);
                if (runtimeDataList != null)
                    runtimeDataList.Clear();
                else
                    runtimeDataList = new List<GPUInstancerRuntimeData>();

                if (runtimeDataDictionary != null)
                    runtimeDataDictionary.Clear();
                else
                    runtimeDataDictionary = new Dictionary<GPUInstancerPrototype, GPUInstancerRuntimeData>();

                if (prototypeList == null)
                    prototypeList = new List<GPUInstancerPrototype>();
            }
        }

        public virtual void InitializeSpatialPartitioning()
        {

        }

        public virtual void UpdateSpatialPartitioningCells(GPUInstancerCameraData renderingCameraData)
        {

        }

        public virtual void DeletePrototype(GPUInstancerPrototype prototype, bool removeSO = true)
        {
#if UNITY_EDITOR
            UnityEditor.Undo.RecordObject(this, "Delete prototype");
#endif
            prototypeList.Remove(prototype);

            if (removeSO && prototype.useGeneratedBillboard && prototype.billboard != null)
            {
                if (GPUInstancerConstants.gpuiSettings.billboardAtlasBindings.DeleteBillboardTextures(prototype, false))
                    prototype.billboard = null;
            }
        }

        public virtual void RemoveInstancesInsideBounds(Bounds bounds, float offset, List<GPUInstancerPrototype> prototypeFilter = null)
        {
            if (runtimeDataList != null)
            {
                foreach (GPUInstancerRuntimeData rd in runtimeDataList)
                {
                    if (prototypeFilter != null && !prototypeFilter.Contains(rd.prototype))
                        continue;
                    GPUInstancerUtility.RemoveInstancesInsideBounds(rd.transformationMatrixVisibilityBuffer, bounds.center, bounds.extents, offset);
                }
            }
        }
        public virtual void RemoveInstancesInsideCollider(Collider collider, float offset, List<GPUInstancerPrototype> prototypeFilter = null)
        {
            if (runtimeDataList != null)
            {
                foreach (GPUInstancerRuntimeData rd in runtimeDataList)
                {
                    if (prototypeFilter != null && !prototypeFilter.Contains(rd.prototype))
                        continue;
                    if (collider is BoxCollider)
                        GPUInstancerUtility.RemoveInstancesInsideBoxCollider(rd.transformationMatrixVisibilityBuffer, (BoxCollider)collider, offset);
                    else if (collider is SphereCollider)
                        GPUInstancerUtility.RemoveInstancesInsideSphereCollider(rd.transformationMatrixVisibilityBuffer, (SphereCollider)collider, offset);
                    else if (collider is CapsuleCollider)
                        GPUInstancerUtility.RemoveInstancesInsideCapsuleCollider(rd.transformationMatrixVisibilityBuffer, (CapsuleCollider)collider, offset);
                    else
                        GPUInstancerUtility.RemoveInstancesInsideBounds(rd.transformationMatrixVisibilityBuffer, collider.bounds.center, collider.bounds.extents, offset);
                }
            }
        }

        public virtual void SetGlobalPositionOffset(Vector3 offsetPosition)
        {
        }

        public virtual void SetGlobalMatrixOffset(Matrix4x4 offsetMatrix)
        {
        }
        #endregion Virtual Methods

        #region Public Methods

        public void ClearCompletedThreads()
        {
            if (activeThreads.Count > 0)
            {
                for (int i = activeThreads.Count - 1; i >= 0; i--)
                {
                    if (!activeThreads[i].IsAlive)
                        activeThreads.RemoveAt(i);
                }
            }
        }

        public void InitializeCameraData()
        {
            if (autoSelectCamera || cameraData.mainCamera == null)
                cameraData.SetCamera(Camera.main);
            else
                cameraData.CalculateHalfAngle();
        }

        public void SetupOcclusionCulling(GPUInstancerCameraData renderingCameraData)
        {
            if (renderingCameraData == null || renderingCameraData.mainCamera == null)
                return;

#if UNITY_EDITOR
            if (renderingCameraData.mainCamera.name == GPUInstancerEditorSimulator.sceneViewCameraName || !Application.isPlaying)
                return;
#endif

            if (isOcclusionCulling)
            {
                if (renderingCameraData.hiZOcclusionGenerator == null)
                {
                    GPUInstancerHiZOcclusionGenerator hiZOcclusionGenerator =
                        renderingCameraData.mainCamera.GetComponent<GPUInstancerHiZOcclusionGenerator>();

                    if (hiZOcclusionGenerator == null)
                        hiZOcclusionGenerator = renderingCameraData.mainCamera.gameObject.AddComponent<GPUInstancerHiZOcclusionGenerator>();

                    renderingCameraData.hiZOcclusionGenerator = hiZOcclusionGenerator;
                    renderingCameraData.hiZOcclusionGenerator.Initialize(renderingCameraData.mainCamera);
                }
            }
        }

        public void UpdateBuffers()
        {
            UpdateBuffers(cameraData);
        }

        public void UpdateBuffers(GPUInstancerCameraData renderingCameraData)
        {
            if (renderingCameraData != null && renderingCameraData.mainCamera != null && SystemInfo.supportsComputeShaders)
            {
                if (isOcclusionCulling && renderingCameraData.hiZOcclusionGenerator == null)
                    SetupOcclusionCulling(renderingCameraData);

                renderingCameraData.CalculateCameraData();

                instancingBounds.center = renderingCameraData.mainCamera.transform.position;

                if (lastDrawCallFrame != Time.frameCount)
                {
                    lastDrawCallFrame = Time.frameCount;
                    timeSinceLastDrawCall = Time.realtimeSinceStartup - lastDrawCallTime;
                    lastDrawCallTime = Time.realtimeSinceStartup;
                }

                UpdateSpatialPartitioningCells(renderingCameraData);

                GPUInstancerUtility.UpdateGPUBuffers(GPUInstancerConstants.gpuiSettings.IsUseBothEyesVRCulling() ? _cameraComputeShaderVR : _cameraComputeShader, _cameraComputeKernelIDs, _visibilityComputeShader, _instanceVisibilityComputeKernelIDs, runtimeDataList, renderingCameraData, isFrustumCulling,
                    isOcclusionCulling, showRenderedAmount, isInitial);
                isInitial = false;

                if (GPUInstancerUtility.matrixHandlingType == GPUIMatrixHandlingType.CopyToTexture)
                    GPUInstancerUtility.DispatchBufferToTexture(runtimeDataList, _bufferToTextureComputeShader, _bufferToTextureComputeKernelID);

#if GPUI_XR
                if (GPUInstancerConstants.gpuiSettings.isVREnabled && UnityEngine.XR.XRSettings.stereoRenderingMode == UnityEngine.XR.XRSettings.StereoRenderingMode.SinglePassInstanced)
                {
                    for (int i = 0; i < runtimeDataList.Count; i++)
                    {
                        GPUInstancerRuntimeData runtimeData = runtimeDataList[i];

                        if (runtimeData == null || runtimeData.argsBuffer == null || runtimeData.argsBuffer.count == 0)
                            continue;

                        int count = runtimeData.argsBuffer.count / 5;
                        _argsBufferComputeShader.SetBuffer(_argsBufferDoubleInstanceCountComputeKernelID, GPUInstancerConstants.VisibilityKernelPoperties.ARGS_BUFFER, runtimeData.argsBuffer);
                        _argsBufferComputeShader.SetInt(GPUInstancerConstants.VisibilityKernelPoperties.COUNT, count);
                        _argsBufferComputeShader.Dispatch(_argsBufferDoubleInstanceCountComputeKernelID, Mathf.CeilToInt(count / GPUInstancerConstants.COMPUTE_SHADER_THREAD_COUNT), 1, 1);
                    }
                }
#endif

                GPUInstancerUtility.GPUIDrawMeshInstancedIndirect(runtimeDataList, instancingBounds, renderingCameraData, layerMask, lightProbeDisabled);
            }
        }

        public void SetCamera(Camera camera)
        {
            if (cameraData == null)
                cameraData = new GPUInstancerCameraData(camera);
            else
                cameraData.SetCamera(camera);

            if (cameraData.hiZOcclusionGenerator != null)
                DestroyImmediate(cameraData.hiZOcclusionGenerator);

            if (isOcclusionCulling)
            {
                cameraData.hiZOcclusionGenerator = cameraData.mainCamera.GetComponent<GPUInstancerHiZOcclusionGenerator>();
                if (cameraData.hiZOcclusionGenerator == null)
                {
                    cameraData.hiZOcclusionGenerator = cameraData.mainCamera.gameObject.AddComponent<GPUInstancerHiZOcclusionGenerator>();
                }
                cameraData.hiZOcclusionGenerator.Initialize(cameraData.mainCamera);
            }
        }

        public ComputeBuffer GetTransformDataBuffer(GPUInstancerPrototype prototype)
        {
            GPUInstancerRuntimeData runtimeData = GetRuntimeData(prototype);
            if (runtimeData != null)
                return runtimeData.transformationMatrixVisibilityBuffer;
            return null;
        }

        public void SetLODBias(float newLODBias, List<GPUInstancerPrototype> prototypeFilter)
        {
            if (runtimeDataList == null || newLODBias <= 0)
                return;

            foreach (GPUInstancerRuntimeData runtimeData in runtimeDataList)
            {
                if (runtimeData == null || runtimeData.lodBiasApplied <= 0 || runtimeData.lodBiasApplied == newLODBias || runtimeData.instanceLODs == null
                    || runtimeData.instanceLODs.Count == 0 || (prototypeFilter != null && !prototypeFilter.Contains(runtimeData.prototype)))
                    continue;

                for (int i = 0; i < runtimeData.instanceLODs.Count; i++)
                {
                    if (i < 4)
                        runtimeData.lodSizes[i * 4] *= runtimeData.lodBiasApplied / newLODBias;
                    else
                        runtimeData.lodSizes[(i - 4) * 4 + 1] *= runtimeData.lodBiasApplied / newLODBias;
                }
                runtimeData.lodBiasApplied = newLODBias;
            }
        }

        #region Tree Instancing Methods
        public void UpdateTreeMPB()
        {
            if (treeProxyList != null && treeProxyList.Count > 0)
            {
                GPUInstancerPrototypeLOD rdLOD;
                GPUInstancerRenderer rdRenderer;
                MeshRenderer meshRenderer;
                Transform proxyTransform;
                foreach (GPUInstancerRuntimeData runtimeData in runtimeDataList)
                {
                    // Do not set append buffers if there is no instance of this tree prototype on the terrain
                    if (runtimeData.bufferSize == 0)
                        continue;

                    if (runtimeData.prototype.treeType != GPUInstancerTreeType.SpeedTree && runtimeData.prototype.treeType != GPUInstancerTreeType.SpeedTree8)
                        continue;

                    proxyTransform = treeProxyList[runtimeData.prototype.PrefabObject];

                    if (!proxyTransform)
                        continue;

                    for (int lod = 0; lod < runtimeData.instanceLODs.Count; lod++)
                    {
                        if (proxyTransform.childCount <= lod)
                            continue;

                        rdLOD = runtimeData.instanceLODs[lod];
                        meshRenderer = proxyTransform.GetChild(lod).GetComponent<MeshRenderer>();

                        for (int r = 0; r < rdLOD.renderers.Count; r++)
                        {
                            rdRenderer = rdLOD.renderers[r];
                            //if (treeType == GPUInstancerTreeType.SoftOcclusionTree)
                            //{
                            //    // Soft occlusion shader wind parameters here.
                            //    // rdRenderer.mpb.SetFloat("_ShakeDisplacement", 0.8f);
                            //    continue;
                            //}

                            meshRenderer.GetPropertyBlock(rdRenderer.mpb);
                            if (rdRenderer.shadowMPB != null)
                                meshRenderer.GetPropertyBlock(rdRenderer.shadowMPB);
                        }
                    }

                    GPUInstancerUtility.SetAppendBuffers(runtimeData);
                }
            }
        }

        // Wind workaround:
        public static void AddTreeProxy(GPUInstancerPrototype treePrototype, GPUInstancerRuntimeData runtimeData)
        {
            switch (treePrototype.treeType)
            {
                case GPUInstancerTreeType.SpeedTree:
                case GPUInstancerTreeType.SpeedTree8:

                    if (treeProxyParent == null)
                    {
                        treeProxyParent = new GameObject("GPUI Tree Manager Proxy");
                        if (treeProxyList != null)
                            treeProxyList.Clear();
                    }

                    if (treeProxyList == null)
                    {
                        treeProxyList = new Dictionary<GameObject, Transform>(); // Dict[TreePrefab, TreeProxyGO]
                    }
                    else if (treeProxyList.ContainsKey(treePrototype.PrefabObject))
                    {
                        if (treeProxyList[treePrototype.PrefabObject] == null)
                            treeProxyList.Remove(treePrototype.PrefabObject);
                        else
                            return;
                    }

                    Mesh treeProxyMesh = new Mesh();
                    treeProxyMesh.name = "TreeProxyMesh";

                    GameObject treeProxyObjectParent = new GameObject(treeProxyList.Count + "_" + treePrototype.name);
                    treeProxyObjectParent.transform.SetParent(treeProxyParent.transform);
                    treeProxyObjectParent.transform.localPosition = Vector3.zero;
                    treeProxyObjectParent.transform.localRotation = Quaternion.identity;
                    treeProxyList.Add(treePrototype.PrefabObject, treeProxyObjectParent.transform);

                    if (treePrototype.PrefabObject.GetComponent<LODGroup>() != null)
                    {
                        LOD[] speedTreeLODs = treePrototype.PrefabObject.GetComponent<LODGroup>().GetLODs();
                        for (int lod = 0; lod < speedTreeLODs.Length; lod++)
                        {
                            if (speedTreeLODs[lod].renderers[0].GetComponent<BillboardRenderer>())
                                continue;

                            Material[] treeProxyMaterial = new Material[1] { new Material(Shader.Find(GPUInstancerConstants.SHADER_GPUI_TREE_PROXY)) };

                            InstantiateTreeProxyObject(speedTreeLODs[lod].renderers[0].gameObject, treeProxyObjectParent, treeProxyMaterial, treeProxyMesh, lod == 0);
                        }
                    }
                    else
                    {
                        Material[] treeProxyMaterial = new Material[1] { new Material(Shader.Find(GPUInstancerConstants.SHADER_GPUI_TREE_PROXY)) };

                        InstantiateTreeProxyObject(treePrototype.PrefabObject, treeProxyObjectParent, treeProxyMaterial, treeProxyMesh, true);
                    }
                    break;

                case GPUInstancerTreeType.TreeCreatorTree:

                    // no need to create a TreeCreator proxy - setting the global wind vector instead
                    Shader.SetGlobalVector("_Wind", GetWindVector());

                    //Material[] treeCreatorProxyMaterials = new Material[2];
                    //treeCreatorProxyMaterials[0] = new Material(Shader.Find(GPUInstancerConstants.SHADER_GPUI_TREE_PROXY));
                    //treeCreatorProxyMaterials[1] = new Material(Shader.Find(GPUInstancerConstants.SHADER_GPUI_TREE_PROXY));
                    //InstantiateTreeProxyObject(treePrototype.prefabObject, treeProxyObjectParent, treeCreatorProxyMaterials, treeProxyMesh, true);
                    break;

            }
        }

        public static void InstantiateTreeProxyObject(GameObject treePrefab, GameObject proxyObjectParent, Material[] proxyMaterials, Mesh proxyMesh, bool setBounds)
        {
            GameObject treeProxyObject = Instantiate(treePrefab, proxyObjectParent.transform);
            treeProxyObject.name = treePrefab.name;

            if (setBounds)
                proxyMesh.bounds = treeProxyObject.GetComponent<MeshFilter>().sharedMesh.bounds;

            // Setup Tree Proxy object mesh renderer.
            MeshRenderer treeProxyObjectMR = treeProxyObject.GetComponent<MeshRenderer>();
            treeProxyObjectMR.shadowCastingMode = ShadowCastingMode.Off;
            treeProxyObjectMR.receiveShadows = false;
            treeProxyObjectMR.lightProbeUsage = LightProbeUsage.Off;

            for (int i = 0; i < proxyMaterials.Length; i++)
            {
                proxyMaterials[i].CopyPropertiesFromMaterial(treeProxyObjectMR.materials[i]);
                proxyMaterials[i].enableInstancing = true;
            }

            treeProxyObjectMR.sharedMaterials = proxyMaterials;
            treeProxyObjectMR.GetComponent<MeshFilter>().sharedMesh = proxyMesh;

            // Strip all unwanted components potentially on the tree prefab:
            Component[] allComponents = treeProxyObject.GetComponents(typeof(Component));
            for (int i = 0; i < allComponents.Length; i++)
            {
                if (allComponents[i] is Transform || allComponents[i] is MeshFilter ||
                    allComponents[i] is MeshRenderer || allComponents[i] is Tree)
                    continue;

                Destroy(allComponents[i]);
            }
        }
        #endregion Tree Instancing Methods

        #region Global Wind Methods

        public static Vector4 GetWindVector()
        {
            if (_windVector != Vector4.zero)
                return _windVector;

            UpdateSceneWind();

            return _windVector;
        }

        public static void UpdateSceneWind()
        {
            WindZone[] sceneWindZones = FindObjectsOfType<WindZone>();

            for (int i = 0; i < sceneWindZones.Length; i++)
            {
                if (sceneWindZones[i].mode == WindZoneMode.Directional)
                {
                    _windVector = new Vector4(sceneWindZones[i].windTurbulence, sceneWindZones[i].windPulseMagnitude, sceneWindZones[i].windPulseFrequency, sceneWindZones[i].windMain);
                    break;
                }
            }
        }

        #endregion Wind Methods

        public Exception threadException;
        public void LogThreadException()
        {
            Debug.LogException(threadException);
        }

#if UNITY_EDITOR && UNITY_2017_2_OR_NEWER
        public void HandlePlayModeStateChanged(PlayModeStateChange state)
        {
            playModeState = state;
        }
#endif

        public GPUInstancerRuntimeData GetRuntimeData(GPUInstancerPrototype prototype, bool logError = false)
        {
            GPUInstancerRuntimeData runtimeData = null;
            if (runtimeDataDictionary != null && !runtimeDataDictionary.TryGetValue(prototype, out runtimeData) && logError)
                Debug.LogError("Can not find runtime data for prototype: " + prototype + ". Please check if the prototype was added to the Manager and the initialize method was called.");
            return runtimeData;
        }
        #endregion Public Methods
    }

}