using System; using UnityEngine.Experimental.Rendering; using System.Collections.Generic; namespace UnityEngine.Rendering.HighDefinition { class PlanarReflectionProbeCache { internal static readonly int s_InputTexID = Shader.PropertyToID("_InputTex"); internal static readonly int s_LoDID = Shader.PropertyToID("_LoD"); internal static readonly int s_FaceIndexID = Shader.PropertyToID("_FaceIndex"); enum ProbeFilteringState { Convolving, Ready } int m_ProbeSize; IBLFilterGGX m_IBLFilterGGX; PowerOfTwoTextureAtlas m_TextureAtlas; RenderTexture m_TempRenderTexture = null; RenderTexture m_ConvolutionTargetTexture; Dictionary m_ProbeBakingState = new Dictionary(); Material m_ConvertTextureMaterial; MaterialPropertyBlock m_ConvertTextureMPB; Dictionary m_TextureHashes = new Dictionary(); int m_FrameProbeIndex; GraphicsFormat m_ProbeFormat; public PlanarReflectionProbeCache(RenderPipelineResources defaultResources, IBLFilterGGX iblFilter, int atlasResolution, GraphicsFormat probeFormat, bool isMipmaped) { m_ConvertTextureMaterial = CoreUtils.CreateEngineMaterial(defaultResources.shaders.blitCubeTextureFacePS); m_ConvertTextureMPB = new MaterialPropertyBlock(); m_ProbeSize = atlasResolution; m_TextureAtlas = new PowerOfTwoTextureAtlas(atlasResolution, 0, probeFormat, useMipMap: isMipmaped, name: "PlanarReflectionProbe Atlas"); m_IBLFilterGGX = iblFilter; m_ProbeFormat = probeFormat; } void Initialize() { if (m_ConvolutionTargetTexture == null) { m_ConvolutionTargetTexture = new RenderTexture(m_ProbeSize, m_ProbeSize, 0, m_ProbeFormat); m_ConvolutionTargetTexture.hideFlags = HideFlags.HideAndDontSave; m_ConvolutionTargetTexture.dimension = TextureDimension.Tex2D; m_ConvolutionTargetTexture.useMipMap = true; m_ConvolutionTargetTexture.autoGenerateMips = false; m_ConvolutionTargetTexture.filterMode = FilterMode.Point; m_ConvolutionTargetTexture.name = CoreUtils.GetRenderTargetAutoName(m_ProbeSize, m_ProbeSize, 0, m_ProbeFormat, "PlanarReflectionConvolution", mips: true); m_ConvolutionTargetTexture.enableRandomWrite = true; m_ConvolutionTargetTexture.Create(); // Clear to avoid garbage in the convolution texture. int mipCount = Mathf.FloorToInt(Mathf.Log(m_ProbeSize, 2)) + 1; for (int mipIdx = 0; mipIdx < mipCount; ++mipIdx) { Graphics.SetRenderTarget(m_ConvolutionTargetTexture, mipIdx, CubemapFace.Unknown); GL.Clear(false, true, Color.clear); } } m_FrameProbeIndex = 0; } public void Release() { m_TextureAtlas.Release(); CoreUtils.Destroy(m_TempRenderTexture); CoreUtils.Destroy(m_ConvolutionTargetTexture); m_ProbeBakingState = null; CoreUtils.Destroy(m_ConvertTextureMaterial); } public void NewFrame() { Initialize(); } // This method is used to convert inputs that are either compressed or not of the right size. // We can't use Graphics.ConvertTexture here because it does not work with a RenderTexture as destination. void ConvertTexture(CommandBuffer cmd, Texture input, RenderTexture target) { m_ConvertTextureMPB.SetTexture(s_InputTexID, input); m_ConvertTextureMPB.SetFloat(s_LoDID, 0.0f); // We want to convert mip 0 to whatever the size of the destination cache is. CoreUtils.SetRenderTarget(cmd, target, ClearFlag.None, Color.black, 0, 0); CoreUtils.DrawFullScreen(cmd, m_ConvertTextureMaterial, m_ConvertTextureMPB); } Texture ConvolveProbeTexture(CommandBuffer cmd, Texture texture, ref IBLFilterBSDF.PlanarTextureFilteringParameters planarTextureFilteringParameters, out Vector4 sourceScaleOffset) { Texture2D texture2D = texture as Texture2D; RenderTexture renderTexture = texture as RenderTexture; if (renderTexture.dimension != TextureDimension.Tex2D) { Debug.LogError("Planar Realtime reflection probe should always be a 2D RenderTexture."); sourceScaleOffset = Vector4.zero; return null; } float scaleX = (float)texture.width / m_ConvolutionTargetTexture.width; float scaleY = (float)texture.height / m_ConvolutionTargetTexture.height; sourceScaleOffset = new Vector4(scaleX, scaleY, 0, 0); m_IBLFilterGGX.FilterPlanarTexture(cmd, renderTexture, ref planarTextureFilteringParameters, m_ConvolutionTargetTexture); return m_ConvolutionTargetTexture; } public Vector4 FetchSlice(CommandBuffer cmd, Texture texture, ref IBLFilterBSDF.PlanarTextureFilteringParameters planarTextureFilteringParameters, out int fetchIndex) { Vector4 scaleOffset = Vector4.zero; fetchIndex = m_FrameProbeIndex++; if (m_TextureAtlas.IsCached(out scaleOffset, texture)) { // If the texture is already in the atlas, we update it only if needed if (NeedsUpdate(texture) || m_ProbeBakingState[scaleOffset] != ProbeFilteringState.Ready) if (!UpdatePlanarTexture(cmd, texture, ref planarTextureFilteringParameters, ref scaleOffset)) Debug.LogError("Can't convolve or update the planar reflection render target"); } else // Either we add it to the atlas if (!UpdatePlanarTexture(cmd, texture, ref planarTextureFilteringParameters, ref scaleOffset)) Debug.LogError("No more space in the planar reflection probe atlas. To solve this issue, increase the size of the Planar Reflection Probe Atlas in the HDRP settings."); return scaleOffset; } bool UpdatePlanarTexture(CommandBuffer cmd, Texture texture, ref IBLFilterBSDF.PlanarTextureFilteringParameters planarTextureFilteringParameters, ref Vector4 scaleOffset) { bool success = false; using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.ConvolvePlanarReflectionProbe))) { m_ProbeBakingState[scaleOffset] = ProbeFilteringState.Convolving; Vector4 sourceScaleOffset; Texture convolvedTexture = ConvolveProbeTexture(cmd, texture, ref planarTextureFilteringParameters, out sourceScaleOffset); if (convolvedTexture == null) return false; if (m_TextureAtlas.IsCached(out scaleOffset, texture)) { success = m_TextureAtlas.UpdateTexture(cmd, texture, convolvedTexture, ref scaleOffset, sourceScaleOffset); } else { // Reserve space for the rendertarget and then blit the result of the convolution at this // location, we don't use the UpdateTexture because it will keep the reference to the // temporary target used to convolve the result of the probe rendering. if (!m_TextureAtlas.AllocateTextureWithoutBlit(texture, texture.width, texture.height, ref scaleOffset)) return false; m_TextureAtlas.BlitTexture(cmd, scaleOffset, convolvedTexture, sourceScaleOffset); success = true; } m_ProbeBakingState[scaleOffset] = ProbeFilteringState.Ready; } return success; } public uint GetTextureHash(Texture texture) { uint textureHash = texture.updateCount; // For baked probes in the editor we need to factor in the actual hash of texture because we can't increment the update count of a texture that's baked on the disk. #if UNITY_EDITOR textureHash += (uint)texture.imageContentsHash.GetHashCode(); #endif return textureHash; } bool NeedsUpdate(Texture texture) { uint savedTextureHash; uint currentTextureHash = GetTextureHash(texture); int instanceId = texture.GetInstanceID(); bool needsUpdate = false; if (!m_TextureHashes.TryGetValue(instanceId, out savedTextureHash) || savedTextureHash != currentTextureHash) { m_TextureHashes[instanceId] = currentTextureHash; needsUpdate = true; } return needsUpdate; } public Texture GetTexCache() => m_TextureAtlas.AtlasTexture; public void Clear(CommandBuffer cmd) { m_TextureAtlas.ResetAllocator(); m_TextureAtlas.ClearTarget(cmd); } public void ClearAtlasAllocator() => m_TextureAtlas.ResetAllocator(); internal static long GetApproxCacheSizeInByte(int nbElement, int atlasResolution, GraphicsFormat format) => PowerOfTwoTextureAtlas.GetApproxCacheSizeInByte(nbElement, atlasResolution, true, format); internal static int GetMaxCacheSizeForWeightInByte(int weight, GraphicsFormat format) => PowerOfTwoTextureAtlas.GetMaxCacheSizeForWeightInByte(weight, true, format); internal Vector4 GetAtlasDatas() { float padding = Mathf.Pow(2.0f, m_TextureAtlas.mipPadding) * 2.0f; return new Vector4( m_TextureAtlas.AtlasTexture.rt.width, padding / (float)m_TextureAtlas.AtlasTexture.rt.width, 0, 0 ); } } }