using System; using UnityEngine; using UnityEngine.Experimental.Rendering; using IDataProvider = UnityEngine.Rendering.LookDev.IDataProvider; namespace UnityEditor.Rendering.LookDev { enum ShadowCompositionPass { MainView, ShadowMask } enum CompositionFinal { First, Second } class RenderTextureCache : IDisposable { const int k_PassPerViewCount = 3; const int k_ViewCount = 2; const int k_TextureCacheSize = k_PassPerViewCount * k_ViewCount; //RenderTextures are packed this way: //0: ViewIndex.First, ShadowCompositionPass.MainView //1: ViewIndex.First, ShadowCompositionPass.ShadowMask //2: CompositionFinal.First //3: ViewIndex.Second, ShadowCompositionPass.MainView //4: ViewIndex.Second, ShadowCompositionPass.ShadowMask //5: CompositionFinal.Second RenderTexture[] m_RTs = new RenderTexture[k_TextureCacheSize]; public RenderTexture this[ViewIndex index, ShadowCompositionPass passIndex] { get => m_RTs[computeIndex(index, passIndex)]; set => m_RTs[computeIndex(index, passIndex)] = value; } public RenderTexture this[CompositionFinal index] { get => m_RTs[computeIndex(index)]; set => m_RTs[computeIndex(index)] = value; } int computeIndex(ViewIndex index, ShadowCompositionPass passIndex) => (int)index * k_PassPerViewCount + (int)(passIndex); int computeIndex(CompositionFinal index) => (k_PassPerViewCount - 1) + (int)(index) * k_PassPerViewCount; void UpdateSize(int index, Rect rect, bool pixelPerfect, Camera renderingCamera, string renderDocName = "LookDevRT") { bool nullRect = rect.IsNullOrInverted(); GraphicsFormat format = SystemInfo.IsFormatSupported(GraphicsFormat.R16G16B16A16_SFloat, FormatUsage.Render) ? GraphicsFormat.R16G16B16A16_SFloat : SystemInfo.GetGraphicsFormat(DefaultFormat.LDR); if (m_RTs[index] != null && (nullRect || m_RTs[index].graphicsFormat != format)) { m_RTs[index].Release(); UnityEngine.Object.DestroyImmediate(m_RTs[index]); m_RTs[index] = null; } if (nullRect) return; int width = (int)rect.width; int height = (int)rect.height; if (m_RTs[index] == null) { m_RTs[index] = new RenderTexture(0, 0, 24, format); m_RTs[index].name = renderDocName; m_RTs[index].antiAliasing = 1; m_RTs[index].hideFlags = HideFlags.HideAndDontSave; } if (m_RTs[index].width != width || m_RTs[index].height != height) { m_RTs[index].Release(); m_RTs[index].width = width; m_RTs[index].height = height; m_RTs[index].Create(); } if (renderingCamera != null) renderingCamera.targetTexture = m_RTs[index]; } public void UpdateSize(Rect rect, ViewIndex index, bool pixelPerfect, Camera renderingCamera) { UpdateSize(computeIndex(index, ShadowCompositionPass.MainView), rect, pixelPerfect, renderingCamera, $"LookDevRT-{index}-MainView"); UpdateSize(computeIndex(index, ShadowCompositionPass.ShadowMask), rect, pixelPerfect, renderingCamera, $"LookDevRT-{index}-ShadowMask"); } public void UpdateSize(Rect rect, CompositionFinal index, bool pixelPerfect, Camera renderingCamera) => UpdateSize(computeIndex(index), rect, pixelPerfect, renderingCamera, $"LookDevRT-Final-{index}"); bool m_Disposed = false; public void Dispose() { if (m_Disposed) return; m_Disposed = true; for (int index = 0; index < k_TextureCacheSize; ++index) { if (m_RTs[index] == null || m_RTs[index].Equals(null)) continue; UnityEngine.Object.DestroyImmediate(m_RTs[index]); m_RTs[index] = null; } } } class Compositer : IDisposable { public static readonly Color firstViewGizmoColor = new Color32(0, 154, 154, 255); public static readonly Color secondViewGizmoColor = new Color32(255, 37, 4, 255); static Material s_Material; static Material material { get { if (s_Material == null || s_Material.Equals(null)) s_Material = new Material(Shader.Find("Hidden/LookDev/Compositor")); return s_Material; } } IViewDisplayer m_Displayer; Context m_Contexts; RenderTextureCache m_RenderTextures = new RenderTextureCache(); Renderer m_Renderer = new Renderer(); RenderingData[] m_RenderDataCache; bool m_pixelPerfect; bool m_Disposed; public bool pixelPerfect { get => m_pixelPerfect; set => m_Renderer.pixelPerfect = m_pixelPerfect = value; } Color m_AmbientColor = new Color(0.0f, 0.0f, 0.0f, 0.0f); bool m_RenderDocAcquisitionRequested; public Compositer( IViewDisplayer displayer, IDataProvider dataProvider, StageCache stages) { m_Displayer = displayer; m_RenderDataCache = new RenderingData[2] { new RenderingData() { stage = stages[ViewIndex.First] }, new RenderingData() { stage = stages[ViewIndex.Second] } }; m_Displayer.OnRenderDocAcquisitionTriggered += RenderDocAcquisitionRequested; m_Displayer.OnUpdateRequested += Render; } void RenderDocAcquisitionRequested() => m_RenderDocAcquisitionRequested = true; void CleanUp() { for (int index = 0; index < 2; ++index) { m_RenderDataCache[index]?.Dispose(); m_RenderDataCache[index] = null; } m_RenderTextures.Dispose(); m_Displayer.OnRenderDocAcquisitionTriggered -= RenderDocAcquisitionRequested; m_Displayer.OnUpdateRequested -= Render; } public void Dispose() { if (m_Disposed) return; m_Disposed = true; CleanUp(); GC.SuppressFinalize(this); } ~Compositer() => CleanUp(); public void Render() { // This can happen when entering/leaving playmode. if (LookDev.dataProvider == null) return; m_Contexts = LookDev.currentContext; //TODO: make integration EditorWindow agnostic! if (UnityEditorInternal.RenderDoc.IsLoaded() && UnityEditorInternal.RenderDoc.IsSupported() && m_RenderDocAcquisitionRequested) UnityEditorInternal.RenderDoc.BeginCaptureRenderDoc(m_Displayer as EditorWindow); switch (m_Contexts.layout.viewLayout) { case Layout.FullFirstView: RenderSingleAndOutput(ViewIndex.First); break; case Layout.FullSecondView: RenderSingleAndOutput(ViewIndex.Second); break; case Layout.HorizontalSplit: case Layout.VerticalSplit: RenderSingleAndOutput(ViewIndex.First); RenderSingleAndOutput(ViewIndex.Second); break; case Layout.CustomSplit: RenderCompositeAndOutput(); break; } //TODO: make integration EditorWindow agnostic! if (UnityEditorInternal.RenderDoc.IsLoaded() && UnityEditorInternal.RenderDoc.IsSupported() && m_RenderDocAcquisitionRequested) UnityEditorInternal.RenderDoc.EndCaptureRenderDoc(m_Displayer as EditorWindow); //stating that RenderDoc do not need to acquire anymore should //allows to gather both view and composition in render doc at once m_RenderDocAcquisitionRequested = false; } void AcquireDataForView(ViewIndex index, Rect viewport) { var renderingData = m_RenderDataCache[(int)index]; renderingData.viewPort = viewport; ViewContext view = m_Contexts.GetViewContent(index); m_RenderTextures.UpdateSize(renderingData.viewPort, index, m_Renderer.pixelPerfect, renderingData.stage.camera); int debugMode = view.debug.viewMode; if (debugMode != -1) LookDev.dataProvider.UpdateDebugMode(debugMode); renderingData.output = m_RenderTextures[index, ShadowCompositionPass.MainView]; renderingData.updater = view.camera; m_Renderer.BeginRendering(renderingData, LookDev.dataProvider); m_Renderer.Acquire(renderingData); if (view.debug.shadow) { RenderTexture tmp = m_RenderTextures[index, ShadowCompositionPass.ShadowMask]; view.environment?.UpdateSunPosition(renderingData.stage.sunLight); renderingData.stage.sunLight.intensity = 1f; LookDev.dataProvider.GetShadowMask(ref tmp, renderingData.stage.runtimeInterface); renderingData.stage.sunLight.intensity = 0f; m_RenderTextures[index, ShadowCompositionPass.ShadowMask] = tmp; } m_Renderer.EndRendering(renderingData, LookDev.dataProvider); if (debugMode != -1) LookDev.dataProvider.UpdateDebugMode(-1); } void RenderSingleAndOutput(ViewIndex index) { Rect viewport = m_Displayer.GetRect((ViewCompositionIndex)index); AcquireDataForView(index, viewport); Compositing(viewport, (int)index, (CompositionFinal)index); m_Displayer.SetTexture((ViewCompositionIndex)index, m_RenderTextures[(CompositionFinal)index]); } void RenderCompositeAndOutput() { Rect viewport = m_Displayer.GetRect(ViewCompositionIndex.Composite); AcquireDataForView(ViewIndex.First, viewport); AcquireDataForView(ViewIndex.Second, viewport); Compositing(viewport, 2 /*split*/, CompositionFinal.First); m_Displayer.SetTexture(ViewCompositionIndex.Composite, m_RenderTextures[CompositionFinal.First]); } void Compositing(Rect rect, int pass, CompositionFinal finalBufferIndex) { bool skipShadowComposition0 = !m_Contexts.GetViewContent(ViewIndex.First).debug.shadow; bool skipShadowComposition1 = !m_Contexts.GetViewContent(ViewIndex.Second).debug.shadow; if (rect.IsNullOrInverted() || (m_Contexts.layout.viewLayout != Layout.FullSecondView && (m_RenderTextures[ViewIndex.First, ShadowCompositionPass.MainView] == null || (!skipShadowComposition0 && m_RenderTextures[ViewIndex.First, ShadowCompositionPass.ShadowMask] == null))) || (m_Contexts.layout.viewLayout != Layout.FullFirstView && (m_RenderTextures[ViewIndex.Second, ShadowCompositionPass.MainView] == null || (!skipShadowComposition1 && m_RenderTextures[ViewIndex.Second, ShadowCompositionPass.ShadowMask] == null)))) { m_RenderTextures[finalBufferIndex] = null; return; } m_RenderTextures.UpdateSize(rect, finalBufferIndex, m_pixelPerfect, null); ComparisonGizmoState gizmo = m_Contexts.layout.gizmoState; Vector4 gizmoPosition = new Vector4(gizmo.center.x, gizmo.center.y, 0.0f, 0.0f); Vector4 gizmoZoneCenter = new Vector4(gizmo.point2.x, gizmo.point2.y, 0.0f, 0.0f); Vector4 gizmoThickness = new Vector4(ComparisonGizmoState.thickness, ComparisonGizmoState.thicknessSelected, 0.0f, 0.0f); Vector4 gizmoCircleRadius = new Vector4(ComparisonGizmoState.circleRadius, ComparisonGizmoState.circleRadiusSelected, 0.0f, 0.0f); Environment env0 = m_Contexts.GetViewContent(ViewIndex.First).environment; Environment env1 = m_Contexts.GetViewContent(ViewIndex.Second).environment; float exposureValue0 = env0?.exposure ?? 0f; float exposureValue1 = env1?.exposure ?? 0f; float dualViewBlendFactor = gizmo.blendFactor; float isCurrentlyLeftEditting = m_Contexts.layout.lastFocusedView == ViewIndex.First ? 1f : -1f; float dragAndDropContext = 0f; //1f left, -1f right, 0f neither float toneMapEnabled = -1f; //1f true, -1f false float shadowMultiplier0 = skipShadowComposition0 ? -1f : 1.0f; float shadowMultiplier1 = skipShadowComposition1 ? -1f : 1.0f; Color shadowColor0 = env0?.shadowColor ?? Color.white; Color shadowColor1 = env1?.shadowColor ?? Color.white; //TODO: handle shadow not at compositing step but in rendering Texture texMainView0 = m_RenderTextures[ViewIndex.First, ShadowCompositionPass.MainView]; Texture texShadowsMask0 = m_RenderTextures[ViewIndex.First, ShadowCompositionPass.ShadowMask]; Texture texMainView1 = m_RenderTextures[ViewIndex.Second, ShadowCompositionPass.MainView]; Texture texShadowsMask1 = m_RenderTextures[ViewIndex.Second, ShadowCompositionPass.ShadowMask]; Vector4 compositingParams = new Vector4(dualViewBlendFactor, exposureValue0, exposureValue1, isCurrentlyLeftEditting); Vector4 compositingParams2 = new Vector4(dragAndDropContext, toneMapEnabled, shadowMultiplier0, shadowMultiplier1); // Those could be tweakable for the neutral tonemapper, but in the case of the LookDev we don't need that const float k_BlackIn = 0.02f; const float k_WhiteIn = 10.0f; const float k_BlackOut = 0.0f; const float k_WhiteOut = 10.0f; const float k_WhiteLevel = 5.3f; const float k_WhiteClip = 10.0f; const float k_DialUnits = 20.0f; const float k_HalfDialUnits = k_DialUnits * 0.5f; const float k_GizmoRenderMode = 4f; //display all // converting from artist dial units to easy shader-lerps (0-1) //TODO: to compute one time only Vector4 tonemapCoeff1 = new Vector4((k_BlackIn * k_DialUnits) + 1.0f, (k_BlackOut * k_HalfDialUnits) + 1.0f, (k_WhiteIn / k_DialUnits), (1.0f - (k_WhiteOut / k_DialUnits))); Vector4 tonemapCoeff2 = new Vector4(0.0f, 0.0f, k_WhiteLevel, k_WhiteClip / k_HalfDialUnits); const float k_ReferenceScale = 1080.0f; Vector4 screenRatio = new Vector4(rect.width / k_ReferenceScale, rect.height / k_ReferenceScale, rect.width, rect.height); RenderTexture oldActive = RenderTexture.active; RenderTexture.active = m_RenderTextures[finalBufferIndex]; material.SetTexture("_Tex0MainView", texMainView0); material.SetTexture("_Tex0Shadows", texShadowsMask0); material.SetColor("_ShadowColor0", shadowColor0); material.SetTexture("_Tex1MainView", texMainView1); material.SetTexture("_Tex1Shadows", texShadowsMask1); material.SetColor("_ShadowColor1", shadowColor1); material.SetVector("_CompositingParams", compositingParams); material.SetVector("_CompositingParams2", compositingParams2); material.SetColor("_FirstViewColor", firstViewGizmoColor); material.SetColor("_SecondViewColor", secondViewGizmoColor); material.SetVector("_GizmoPosition", gizmoPosition); material.SetVector("_GizmoZoneCenter", gizmoZoneCenter); material.SetVector("_GizmoSplitPlane", gizmo.plane); material.SetVector("_GizmoSplitPlaneOrtho", gizmo.planeOrtho); material.SetFloat("_GizmoLength", gizmo.length); material.SetVector("_GizmoThickness", gizmoThickness); material.SetVector("_GizmoCircleRadius", gizmoCircleRadius); material.SetFloat("_BlendFactorCircleRadius", ComparisonGizmoState.blendFactorCircleRadius); material.SetFloat("_GetBlendFactorMaxGizmoDistance", gizmo.blendFactorMaxGizmoDistance); material.SetFloat("_GizmoRenderMode", k_GizmoRenderMode); material.SetVector("_ScreenRatio", screenRatio); material.SetVector("_ToneMapCoeffs1", tonemapCoeff1); material.SetVector("_ToneMapCoeffs2", tonemapCoeff2); material.SetPass(pass); Renderer.DrawFullScreenQuad(new Rect(0, 0, rect.width, rect.height)); RenderTexture.active = oldActive; } public ViewIndex GetViewFromComposition(Vector2 localCoordinate) { Rect compositionRect = m_Displayer.GetRect(ViewCompositionIndex.Composite); Vector2 normalizedLocalCoordinate = ComparisonGizmoController.GetNormalizedCoordinates(localCoordinate, compositionRect); switch (m_Contexts.layout.viewLayout) { case Layout.CustomSplit: return Vector3.Dot(new Vector3(normalizedLocalCoordinate.x, normalizedLocalCoordinate.y, 1), m_Contexts.layout.gizmoState.plane) >= 0 ? ViewIndex.First : ViewIndex.Second; default: throw new Exception("GetViewFromComposition call when not inside a Composition"); } } } }