#if ENABLE_PATH_TRACING_SRP
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine.PathTracing.Core;
using UnityEngine.Rendering.Denoising;
using UnityEngine.Rendering.Sampling;
using UnityEngine.Rendering.UnifiedRayTracing;

namespace UnityEngine.Rendering.LiveGI
{
    using InstanceHandle = Handle<World.InstanceKey>;
    using LightHandle = Handle<World.LightDescriptor>;
    using MaterialHandle = Handle<MaterialPool.MaterialDescriptor>;

    [Serializable]
    internal class PathTracingSettings
    {
        [Header("Light Transport")]
        [Range(0, 10)]
        public int bounceCount = 2;

        [Range(1, 16)]
        public int sampleCount = 1;

        [Range(1, 8)]
        public int lightEvaluations = 1;

        [Min(0)]
        public int maxIntensity;

        [HideInInspector]
        public float exposureScale = 1.0f;

        // Keep emissive mesh sampling disabled for now, as we need a more reliable sceneMaterials.IsEmissive.
        // If you change that, you need to modify the EMISSIVE_SAMPLING define in the shader
        [HideInInspector]
        public bool enableEmissiveSampling;

        // For now, some SRPs pre-multiply the light intensity with PI (like URP), while other don't
        // https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
        [HideInInspector]
        public bool multiplyPunctualLightIntensityByPI;

        // This automatically adjust the light range based on the light intensity and range to better fit the falloff LUT (applies to punctual lights with inverse squared falloff only).
        [HideInInspector]
        public bool autoEstimateLUTRange = true;

        public LightPickingMethod lightPickingMethod = LightPickingMethod.Uniform;

        [HideInInspector]
        public PathTermination pathTermination = PathTermination.RusianRoulette;

        [Header("Post Processing")]
        public DenoiserType denoising = DenoiserType.None;

        [Header("Intersections")]
        public RayTracingBackend raytracingBackend = RayTracingBackend.Hardware;

        // Use "only static" when previewing lightmaps. Use "All" to path trace the full frame
        public RenderedGameObjectsFilter renderedGameObjects = RenderedGameObjectsFilter.OnlyStatic;

        [Header("Artistic Controls")]
        public bool respectLightLayers = true;

        [Range(1, 10)]
        public float albedoBoost = 1.0f;

        [Range(0, 10)]
        public float indirectIntensity = 1.0f;

        [Range(0, 10)]
        public float environmentIntensityMultiplier = 1.0f;

        [HideInInspector]
        public Vector3Int reservoirGridSize = new Vector3Int(64, 16, 64);

        [HideInInspector]
        public int reservoirsPerVoxel = 16;

        [Header("Debug")]
        public bool showRayHeatmap = false;
    }

    internal enum PathTracingOutput { FullPathTracer, GIPreview };

    internal enum RayTracingBackend { Hardware = 0, Compute = 1 };

    internal enum PathTermination { RusianRoulette = 0, NewUnbiased = 1, NewBiased = 2 };

    internal class PathTracingContext : IDisposable
    {
        // Public API
        #region Public API

        public PathTracingContext(PathTracingOutput pathTracingOutput)
        {
#if UNITY_EDITOR
            // Note: this code is not ready to run in the player, we just disable some parts to make sure the player builds successfully
            _defaultMaterial = UnityEditor.AssetDatabase.GetBuiltinExtraResource<Material>("Default-Material.mat");
#endif
            _sceneUpdatesTracker = new SceneUpdatesTracker();

            _pathTracingOutput = pathTracingOutput;

            _samplingResources = new SamplingResources();
#if UNITY_EDITOR
            _samplingResources.Load((uint)SamplingResources.ResourceType.All);
#endif
            _emptyExposureTexture = RTHandles.Alloc(1, 1,
                enableRandomWrite: true, name: "Empty EV100 Exposure");
        }

        public void Dispose()
        {
            _world?.Dispose();

            _rayTracingContext?.Dispose();
            _rayTracingContext = null;

            _sceneUpdatesTracker?.Dispose();

            _emptyExposureTexture?.Release();

            _buildScratchBuffer?.Dispose();

            _samplingResources?.Dispose();

            // Delete temporary textures for the default material
            CoreUtils.Destroy(_defaultMaterialDescriptor.Albedo);
            CoreUtils.Destroy(_defaultMaterialDescriptor.Emission);
            CoreUtils.Destroy(_defaultMaterialDescriptor.Transmission);
        }

        internal RayTracingBackend SelectRayTracingBackend(RayTracingBackend requestedBackend, RayTracingResources resources)
        {
            if (!RayTracingContext.IsBackendSupported((UnifiedRayTracing.RayTracingBackend)requestedBackend))
            {
                Debug.LogWarning("Hardware RayTracing not available. Falling back to Compute Shader based implementation.");
                requestedBackend = RayTracingBackend.Compute;
            }

            // Early exit if the backend has not changed and the RT resources are already loaded
            if (_world != null && _world.GetAccelerationStructure() != null && _rayTracingShader != null && _currentRayTracingBackend == requestedBackend)
                return requestedBackend;

            _rayTracingContext?.Dispose();

            _rayTracingContext = new RayTracingContext((UnifiedRayTracing.RayTracingBackend)requestedBackend, resources);
            _currentRayTracingBackend = requestedBackend;
#if UNITY_EDITOR
            _rayTracingShader = _rayTracingContext.LoadRayTracingShader("Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LiveGI.urtshader");
#endif
            _world?.Dispose();
            _world = new World();
            var worldResources = new WorldResourceSet();
#if UNITY_EDITOR
            worldResources.LoadFromAssetDatabase();
#endif
            _world.Init(_rayTracingContext, worldResources);
            _defaultMaterialDescriptor = MaterialPool.ConvertUnityMaterialToMaterialDescriptor(_defaultMaterial);
            var defaultHandle = _world.AddMaterial(in _defaultMaterialDescriptor, UVChannel.UV0);
            _instanceIDToWorldMaterialHandles.Add(_defaultMaterial.GetEntityId(), defaultHandle);
            _instanceIDToWorldMaterialDescriptors.Add(_defaultMaterial.GetEntityId(), _defaultMaterialDescriptor);

            return requestedBackend;
        }

        // Handles scene updates. Should be called once per frame (and not per camera)
        public void Update(CommandBuffer cmd, PathTracingSettings settings)
        {
            using (new ProfilingScope(cmd, new ProfilingSampler("LiveGI Update")))
            {
                // TODO: enable this only for debugging
                Unity.Collections.NativeLeakDetection.Mode = Unity.Collections.NativeLeakDetectionMode.EnabledWithStackTrace;

                var changes = _sceneUpdatesTracker.GetChanges(_pathTracingOutput == PathTracingOutput.GIPreview);
                _sceneChanged = changes.HasChanges();

                UpdateMaterials(_world, _instanceIDToWorldMaterialHandles, _instanceIDToWorldMaterialDescriptors, changes.addedMaterials, changes.removedMaterials, changes.changedMaterials);
                UpdateInstances(_world, _instanceIDToWorldInstanceHandles, _instanceIDToWorldMaterialHandles, changes.addedInstances, changes.changedInstances, changes.removedInstances, settings.renderedGameObjects, settings.enableEmissiveSampling, _defaultMaterial);
                UpdateLights(_world, _instanceIDToWorldLightHandles, changes.addedLights, changes.removedLights, changes.changedLights, settings);

                // Calculate scene bounds.
                Bounds sceneBounds = new Bounds();
                if (settings.lightPickingMethod == LightPickingMethod.Regir ||
                    settings.lightPickingMethod == LightPickingMethod.LightGrid)
                {
                    var sceneRenderers = Object.FindObjectsByType<Renderer>();
                    foreach (Renderer r in sceneRenderers)
                        sceneBounds.Encapsulate(r.bounds);
                }

                _world.SetEnvironmentMaterial(RenderSettings.skybox);
                //_world.EnableEmissiveSampling = settings.enableEmissiveSampling;
                _world.Build(sceneBounds, cmd, ref _buildScratchBuffer, _samplingResources, settings.enableEmissiveSampling);

                int newLightListHashCode = _world.LightListHashCode;
                _sceneChanged |= (newLightListHashCode != _previousLightsHashCode);
                _previousLightsHashCode = newLightListHashCode;
            }
        }

        public void Render(CommandBuffer cmd, Vector2Int scaledSize, Vector4 viewFustum, Matrix4x4 cameraToWorldMatrix, Matrix4x4 worldToCameraMatrix, Matrix4x4 projectionMatrix, Matrix4x4 previousViewProjection, PathTracingSettings settings, RTHandle output, RTHandle normals, RTHandle motionVectors, RTHandle debugOutput, int frameIndex, bool preExpose = false)
        {
            using (new ProfilingScope(cmd, new ProfilingSampler("LiveGI Render")))
            {
                Debug.Assert(_world.GetAccelerationStructure() != null, "Acceleration structure does not exist. Did you call Update()?");

                // Path-tracer Input
                Util.BindWorld(cmd, _rayTracingShader, _world, _pathTracingOutput == PathTracingOutput.GIPreview ? 32 : 1024);
                _rayTracingShader.SetVectorParam(cmd, Shader.PropertyToID("g_CameraFrustum"), viewFustum);
                _rayTracingShader.SetMatrixParam(cmd, Shader.PropertyToID("g_CameraToWorldMatrix"), cameraToWorldMatrix);
                _rayTracingShader.SetMatrixParam(cmd, Shader.PropertyToID("g_PreviousViewProjection"), previousViewProjection);

                var viewProjection = projectionMatrix * worldToCameraMatrix;
                _rayTracingShader.SetMatrixParam(cmd, Shader.PropertyToID("g_CameraViewProjection"), viewProjection);

                _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_FrameIndex"), frameIndex);

                // For now the history rejection in the denoising is not compatible with stochastic jitter
                _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_EnableSubPixelJittering"), 0);
                _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_LightPickingMethod"), (int)settings.lightPickingMethod);
                _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_BounceCount"), settings.bounceCount);
                _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_SampleCount"), settings.sampleCount);
                _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_LightEvaluations"), settings.lightEvaluations);
                _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_PathtracerAsGiPreviewMode"), (_pathTracingOutput == PathTracingOutput.GIPreview) ? 1 : 0);
                _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_CountNEERayAsPathSegment"), 1);
                _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_RenderedInstances"), (int)settings.renderedGameObjects);
                _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_PreExpose"), preExpose ? 1 : 0);
                _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_MaxIntensity"), settings.maxIntensity > 0 ? settings.maxIntensity : int.MaxValue);
                _rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_ExposureScale"), settings.exposureScale);
                _rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_AlbedoBoost"), settings.albedoBoost);
                _rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_IndirectScale"), settings.indirectIntensity);
                _rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_EnvIntensityMultiplier"), settings.environmentIntensityMultiplier);
                _rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_EnableDebug"), settings.showRayHeatmap ? 1 : 0);
                _rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_PathTermination"), (int)settings.pathTermination);

                // To avoid shader permutations, we always need to set an exposure texture, even if we don't read it
                if (!preExpose)
                {
                    _rayTracingShader.SetTextureParam(cmd, Shader.PropertyToID("_ExposureTexture"), _emptyExposureTexture);
                }

                SamplingResources.Bind(cmd, _samplingResources);

                // Path-tracer Output
                _rayTracingShader.SetTextureParam(cmd, Shader.PropertyToID("g_Radiance"), output);
                _rayTracingShader.SetTextureParam(cmd, Shader.PropertyToID("g_MotionVectors"), motionVectors);
                _rayTracingShader.SetTextureParam(cmd, Shader.PropertyToID("g_NormalsDepth"), normals);
                _rayTracingShader.SetTextureParam(cmd, Shader.PropertyToID("g_DebugOutput"), debugOutput);

                // Path-tracing
                RayTracingHelper.ResizeScratchBufferForTrace(_rayTracingShader, (uint)scaledSize.x, (uint)scaledSize.y, 1, ref _traceScratchBuffer);
                _rayTracingShader.Dispatch(cmd, _traceScratchBuffer, (uint)scaledSize.x, (uint)scaledSize.y, 1);

                _world.NextFrame();
            }
        }

        public void Denoise(CommandBuffer cmd, CommandBufferDenoiser denoiser, float nearClipPlane, float farClipPlane, Matrix4x4 viewProjection, PathTracingSettings settings, RTHandle color, RTHandle normals, RTHandle motionVectors)
        {
            using (new ProfilingScope(cmd, new ProfilingSampler("LiveGI Denoise")))
            {
                // Denoising
                if (settings.denoising != DenoiserType.None && denoiser != null)
                {
                    denoiser.DenoiseRequest(cmd, "color", color);
                    denoiser.DenoiseRequest(cmd, "flow", motionVectors);
                    denoiser.DenoiseRequest(cmd, "normals", normals);
                    //if (denoiser is RealTimeDenoiser)
                    //    ((RealTimeDenoiser)denoiser).SetCameraMatrix(cmd, viewProjection, nearClipPlane, farClipPlane, _sceneChanged);
                    denoiser.GetResults(cmd, color);
                }
            }
        }

        #endregion

        // Private Implementation
        #region Private Implementation

        private readonly SamplingResources _samplingResources;
        private RayTracingBackend _currentRayTracingBackend;
        private RayTracingContext _rayTracingContext;
        private IRayTracingShader _rayTracingShader;
        private GraphicsBuffer _traceScratchBuffer;
        private GraphicsBuffer _buildScratchBuffer;

        // TODO(Yvain) We should use the same buffer for  m_TraceScratchBuffer and m_BuildScratchBuffer but that's impractical when we need to
        // resize the buffer for both the build and the trace.
        // (we can't call Dispose() on a GraphicsBuffer before submitting the CommandBuffer using it)

        private readonly SceneUpdatesTracker _sceneUpdatesTracker;
        private bool _sceneChanged = true;
        private int _previousLightsHashCode;

        private readonly Material _defaultMaterial;
        private MaterialPool.MaterialDescriptor _defaultMaterialDescriptor;

        private readonly PathTracingOutput _pathTracingOutput;

        private readonly RTHandle _emptyExposureTexture;

        private World _world;

        // This dictionary maps from Unity InstanceID for MeshRenderer or Terrain, to corresponding InstanceHandle for accessing World.
        private readonly Dictionary<EntityId, InstanceHandle> _instanceIDToWorldInstanceHandles = new();

        // Same as above but for Lights
        private readonly Dictionary<EntityId, LightHandle> _instanceIDToWorldLightHandles = new();

        // Same as above but for Materials
        private Dictionary<EntityId, MaterialHandle> _instanceIDToWorldMaterialHandles = new();

        // We also keep track of associated material descriptors, so we can free temporary temporary textures when a material is removed
        private Dictionary<EntityId, MaterialPool.MaterialDescriptor> _instanceIDToWorldMaterialDescriptors = new();

        public static Vector4 GetCameraFrustum(Camera camera)
        {
            Vector3[] frustumCorners = new Vector3[4];
            camera.CalculateFrustumCorners(new Rect(0, 0, 1, 1), 1.0f, Camera.MonoOrStereoscopicEye.Mono, frustumCorners);
            float left = frustumCorners[0].x;
            float right = frustumCorners[2].x;
            float bottom = frustumCorners[0].y;
            float top = frustumCorners[2].y;

            return new Vector4(left, right, bottom, top);
        }

        internal static void UpdateMaterials(World world, Dictionary<EntityId, MaterialHandle> instanceIDToHandle, Dictionary<EntityId, MaterialPool.MaterialDescriptor> instanceIDToDescriptor, List<Material> addedMaterials, List<EntityId> removedMaterials, List<Material> changedMaterials)
        {
            static void DeleteTemporaryTextures(ref MaterialPool.MaterialDescriptor desc)
            {
                CoreUtils.Destroy(desc.Albedo);
                CoreUtils.Destroy(desc.Emission);
                CoreUtils.Destroy(desc.Transmission);
            }

            foreach (var materialInstanceID in removedMaterials)
            {
                // Clean up temporary textures in the descriptor
                Debug.Assert(instanceIDToDescriptor.ContainsKey(materialInstanceID));
                var descriptor = instanceIDToDescriptor[materialInstanceID];
                DeleteTemporaryTextures(ref descriptor);
                instanceIDToDescriptor.Remove(materialInstanceID);

                // Remove the material from the world
                Debug.Assert(instanceIDToHandle.ContainsKey(materialInstanceID));
                world.RemoveMaterial(instanceIDToHandle[materialInstanceID]);
                instanceIDToHandle.Remove(materialInstanceID);
            }

            foreach (var material in addedMaterials)
            {
                // Add material to the world
                var descriptor = MaterialPool.ConvertUnityMaterialToMaterialDescriptor(material);
                var handle = world.AddMaterial(in descriptor, UVChannel.UV0);
                instanceIDToHandle.Add(material.GetEntityId(), handle);

                // Keep track of the descriptor
                instanceIDToDescriptor.Add(material.GetEntityId(), descriptor);
            }

            foreach (var material in changedMaterials)
            {
                // Clean up temporary textures in the old descriptor
                Debug.Assert(instanceIDToDescriptor.ContainsKey(material.GetEntityId()));
                var oldDescriptor = instanceIDToDescriptor[material.GetEntityId()];
                DeleteTemporaryTextures(ref oldDescriptor);

                // Update the material in the world using the new descriptor
                Debug.Assert(instanceIDToHandle.ContainsKey(material.GetEntityId()));
                var newDescriptor = MaterialPool.ConvertUnityMaterialToMaterialDescriptor(material);
                world.UpdateMaterial(instanceIDToHandle[material.GetEntityId()], in newDescriptor, UVChannel.UV0);
                instanceIDToDescriptor[material.GetEntityId()] = newDescriptor;
            }
        }

        private static void UpdateLights(World world, Dictionary<EntityId, LightHandle> instanceIDToHandle, List<Light> addedLights, List<EntityId> removedLights,
            List<Light> changedLights, PathTracingSettings settings, MixedLightingMode mixedLightingMode = MixedLightingMode.IndirectOnly)
        {
            world.lightPickingMethod = settings.lightPickingMethod;

            // Remove deleted lights
            LightHandle[] handlesToRemove = new LightHandle[removedLights.Count];
            for (int i = 0; i < removedLights.Count; i++)
            {
                var lightInstanceID = removedLights[i];
                handlesToRemove[i] = instanceIDToHandle[lightInstanceID];
                instanceIDToHandle.Remove(lightInstanceID);
            }
            world.RemoveLights(handlesToRemove);

            // Add new lights
            LightHandle[] addedHandles = world.AddLights(Util.ConvertUnityLightsToLightDescriptors(addedLights.ToArray(), settings.multiplyPunctualLightIntensityByPI), settings.respectLightLayers, settings.autoEstimateLUTRange, mixedLightingMode);
            for (int i = 0; i < addedLights.Count; ++i)
                instanceIDToHandle.Add(addedLights[i].GetEntityId(), addedHandles[i]);

            // Update changed lights
            LightHandle[] handlesToUpdate = new LightHandle[changedLights.Count];
            for (int i = 0; i < changedLights.Count; i++)
                handlesToUpdate[i] = instanceIDToHandle[changedLights[i].GetEntityId()];
            world.UpdateLights(handlesToUpdate, Util.ConvertUnityLightsToLightDescriptors(changedLights.ToArray(), settings.multiplyPunctualLightIntensityByPI), settings.respectLightLayers, settings.autoEstimateLUTRange, mixedLightingMode);
        }

        internal static void UpdateInstances(
            World world,
            Dictionary<EntityId, InstanceHandle> instanceIDToInstanceHandle,
            Dictionary<EntityId, MaterialHandle> instanceIDToMaterialHandle,
            List<MeshRenderer> addedInstances,
            List<InstanceChanges> changedInstances,
            List<EntityId> removedInstances,
            RenderedGameObjectsFilter renderedGameObjects,
            bool enableEmissiveSampling,
            Material fallbackMaterial)
        {
            foreach (var meshRendererInstanceID in removedInstances)
            {
                if (instanceIDToInstanceHandle.TryGetValue(meshRendererInstanceID, out InstanceHandle instance))
                {
                    world.RemoveInstance(instance);
                    instanceIDToInstanceHandle.Remove(meshRendererInstanceID);
                }
                else
                {
                    Debug.LogError($"Failed to remove an instance with InstanceID {meshRendererInstanceID}");
                }
            }

            foreach (var meshRenderer in addedInstances)
            {
                if (meshRenderer.isPartOfStaticBatch)
                {
                    Debug.LogError("Static batching should be disabled when using the real time path tracer in play mode. You can disable it from the project settings.");
                    continue;
                }

                var mesh = meshRenderer.GetComponent<MeshFilter>().sharedMesh;
                var localToWorldMatrix = meshRenderer.transform.localToWorldMatrix;

                var materials = Util.GetMaterials(meshRenderer);
                var materialHandles = new MaterialHandle[materials.Length];
                bool[] visibility = new bool[materials.Length];
                for (int i = 0; i < materials.Length; i++)
                {
                    if (materials[i] == null)
                    {
                        materialHandles[i] = instanceIDToMaterialHandle[fallbackMaterial.GetEntityId()];
                        visibility[i] = false;
                    }
                    else
                    {
                        materialHandles[i] = instanceIDToMaterialHandle[materials[i].GetEntityId()];
                        visibility[i] = true;
                    }
                }
                uint[] masks = new uint[materials.Length];
                for (int i = 0; i < masks.Length; i++)
                {
#if UNITY_EDITOR
                    bool hasLightmaps = (meshRenderer.receiveGI == ReceiveGI.Lightmaps);
#else
                    bool hasLightmaps = true;
#endif
                    var mask = World.GetInstanceMask(meshRenderer.shadowCastingMode, Util.IsStatic(meshRenderer.gameObject), renderedGameObjects, hasLightmaps);
                    masks[i] = visibility[i] ? mask : 0u;
                }


                InstanceHandle instance = world.AddInstance(
                    mesh,
                    materialHandles,
                    masks,
                    1u << meshRenderer.gameObject.layer,
                    in localToWorldMatrix,
                    meshRenderer.bounds,
                    Util.IsStatic(meshRenderer.gameObject),
                    renderedGameObjects,
                    enableEmissiveSampling);
                var instanceID = meshRenderer.GetEntityId();
                Debug.Assert(!instanceIDToInstanceHandle.ContainsKey(instanceID));
                instanceIDToInstanceHandle.Add(instanceID, instance);
            }

            foreach (var instanceUpdate in changedInstances)
            {
                try
                {
                    var renderer = instanceUpdate.meshRenderer;
                    var gameObject = renderer.gameObject;

                    if (!instanceIDToInstanceHandle.TryGetValue(renderer.GetEntityId(), out InstanceHandle instance))
                    {
                        Debug.LogError($"Failed to update an instance with InstanceID {instanceUpdate.meshRenderer.GetEntityId()}");
                        continue;
                    }

                    if ((instanceUpdate.changes & ModifiedProperties.Transform) != 0)
                    {
                        world.UpdateInstanceTransform(instance, gameObject.transform.localToWorldMatrix);
                    }

                    bool materialChanged = (instanceUpdate.changes & ModifiedProperties.Material) != 0;
                    bool maskPropertiesChanged = (instanceUpdate.changes & ModifiedProperties.IsStatic) != 0 || (instanceUpdate.changes & ModifiedProperties.ShadowCasting) != 0 || (instanceUpdate.changes & ModifiedProperties.Layer) != 0;
                    if (materialChanged || enableEmissiveSampling || maskPropertiesChanged)
                    {
                        var materials = Util.GetMaterials(renderer);
                        var materialHandles = new MaterialHandle[materials.Length];
                        for (int i = 0; i < materials.Length; i++)
                        {
                            if (materials[i] == null)
                            {
                                materialHandles[i] = instanceIDToMaterialHandle[fallbackMaterial.GetEntityId()];
                            }
                            else
                            {
                                materialHandles[i] = instanceIDToMaterialHandle[materials[i].GetEntityId()];
                            }
                        }

                        if (materialChanged)
                            world.UpdateInstanceMaterials(instance, materialHandles);
                        if (enableEmissiveSampling)
                            world.UpdateInstanceEmission(instance, instanceUpdate.meshRenderer.GetComponent<MeshFilter>().sharedMesh, instanceUpdate.meshRenderer.bounds, materialHandles, Util.IsStatic(gameObject), renderedGameObjects);
                        if (maskPropertiesChanged || materialChanged)
                        {
                            bool[] visibility = new bool[materials.Length];
                            for (int i = 0; i < materials.Length; i++)
                                visibility[i] = materials[i] != null;
                            uint[] masks = new uint[materials.Length];
                            for (int i = 0; i < masks.Length; i++)
                            {
#if UNITY_EDITOR
                                bool hasLightmaps = (renderer.receiveGI == ReceiveGI.Lightmaps);
#else
                                bool hasLightmaps = true;
#endif
                                var mask = World.GetInstanceMask(renderer.shadowCastingMode, Util.IsStatic(renderer.gameObject), renderedGameObjects, hasLightmaps);
                                masks[i] = visibility[i] ? mask : 0u;
                            }
                            world.UpdateInstanceMask(instance, masks);
                        }
                    }
                }
                catch (Exception e)
                {
                    Debug.LogError($"Failed to modify instance {instanceUpdate.meshRenderer.name}: {e}");
                }
            }
        }
#endregion
    }
}
#endif