using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor.Inspector.GraphicsSettingsInspectors; using UnityEditorInternal; using UnityEngine; using UnityEngine.Rendering.HighDefinition; using UnityEngine.UIElements; using RenderingLayerMask = UnityEngine.RenderingLayerMask; using UnityEngine.Rendering; namespace UnityEditor.Rendering.HighDefinition { /// /// A collection of utilities used by editor code of the HDRP. /// class HDEditorUtils { internal const string FormatingPath = @"Packages/com.unity.render-pipelines.high-definition/Editor/USS/Formating"; internal const string QualitySettingsSheetPath = @"Packages/com.unity.render-pipelines.high-definition/Editor/USS/QualitySettings"; internal const string WizardSheetPath = @"Packages/com.unity.render-pipelines.high-definition/Editor/USS/Wizard"; internal const string HDRPAssetBuildLabel = "HDRP:IncludeInBuild"; internal static bool NeedsToBeIncludedInBuild(HDRenderPipelineAsset hdRenderPipelineAsset) { var labelList = AssetDatabase.GetLabels(hdRenderPipelineAsset); foreach (string item in labelList) { if (item == HDUtils.k_HdrpAssetBuildLabel) { return true; } } return false; } private static (StyleSheet baseSkin, StyleSheet professionalSkin, StyleSheet personalSkin) LoadStyleSheets(string basePath) => ( AssetDatabase.LoadAssetAtPath($"{basePath}.uss"), AssetDatabase.LoadAssetAtPath($"{basePath}Light.uss"), AssetDatabase.LoadAssetAtPath($"{basePath}Dark.uss") ); internal static void AddStyleSheets(VisualElement element, string baseSkinPath) { (StyleSheet @base, StyleSheet personal, StyleSheet professional) = LoadStyleSheets(baseSkinPath); element.styleSheets.Add(@base); if (EditorGUIUtility.isProSkin) { if (professional != null && !professional.Equals(null)) element.styleSheets.Add(professional); } else { if (personal != null && !personal.Equals(null)) element.styleSheets.Add(personal); } } static readonly Action k_DefaultDrawer = (p, l) => EditorGUILayout.PropertyField(p, l); internal static T LoadAsset(string relativePath) where T : UnityEngine.Object => AssetDatabase.LoadAssetAtPath(HDUtils.GetHDRenderPipelinePath() + relativePath); /// /// Reset the dedicated Keyword and Pass regarding the shader kind. /// Also re-init the drawers and set the material dirty for the engine. /// /// The material that nees to be setup /// /// True: managed to do the operation. /// False: unknown shader used in material /// [Obsolete("Use HDShaderUtils.ResetMaterialKeywords instead")] public static bool ResetMaterialKeywords(Material material) => HDShaderUtils.ResetMaterialKeywords(material); static readonly GUIContent s_OverrideTooltip = EditorGUIUtility.TrTextContent("", "Override this setting in component."); internal static bool FlagToggle(TEnum v, SerializedProperty property) where TEnum : struct, IConvertible // restrict to ~enum { var intV = (int)(object)v; var isOn = (property.intValue & intV) != 0; var rect = ReserveAndGetFlagToggleRect(); isOn = GUI.Toggle(rect, isOn, s_OverrideTooltip, CoreEditorStyles.smallTickbox); if (isOn) property.intValue |= intV; else property.intValue &= ~intV; return isOn; } internal static Rect ReserveAndGetFlagToggleRect() { var rect = GUILayoutUtility.GetRect(11, 17, GUILayout.ExpandWidth(false)); rect.y += 4; return rect; } internal static bool IsAssetPath(string path) { var isPathRooted = Path.IsPathRooted(path); return isPathRooted && path.StartsWith(Application.dataPath) || !isPathRooted && path.StartsWith("Assets"); } // Copy texture from cache internal static bool CopyFileWithRetryOnUnauthorizedAccess(string s, string path) { UnauthorizedAccessException exception = null; for (var k = 0; k < 20; ++k) { try { File.Copy(s, path, true); exception = null; } catch (UnauthorizedAccessException e) { exception = e; } } if (exception != null) { Debug.LogException(exception); // Abort the update, something else is preventing the copy return false; } return true; } internal static void PropertyFieldWithoutToggle( TEnum v, SerializedProperty property, GUIContent label, TEnum displayed, Action drawer = null, int indent = 0 ) where TEnum : struct, IConvertible // restrict to ~enum { var intDisplayed = (int)(object)displayed; var intV = (int)(object)v; if ((intDisplayed & intV) == intV) { EditorGUILayout.BeginHorizontal(); var i = EditorGUI.indentLevel; EditorGUI.indentLevel = i + indent; (drawer ?? k_DefaultDrawer)(property, label); EditorGUI.indentLevel = i; EditorGUILayout.EndHorizontal(); } } internal static void DrawToolBarButton( TEnum button, Editor owner, Dictionary toolbarMode, Dictionary toolbarContent, params GUILayoutOption[] options ) where TEnum : struct, IConvertible { var intButton = (int)(object)button; bool enabled = toolbarMode[button] == EditMode.editMode; EditorGUI.BeginChangeCheck(); enabled = GUILayout.Toggle(enabled, toolbarContent[button], EditorStyles.miniButton, options); if (EditorGUI.EndChangeCheck()) { EditMode.SceneViewEditMode targetMode = EditMode.editMode == toolbarMode[button] ? EditMode.SceneViewEditMode.None : toolbarMode[button]; EditMode.ChangeEditMode(targetMode, GetBoundsGetter(owner)(), owner); } } internal static Func GetBoundsGetter(Editor o) { return () => { var bounds = new Bounds(); var rp = ((Component)o.target).transform; var b = rp.position; bounds.Encapsulate(b); return bounds; }; } /// /// Give a human readable string representing the inputed weight given in byte. /// /// The weigth in byte /// Human readable weight internal static string HumanizeWeight(long weightInByte) { if (weightInByte < 500) { return weightInByte + " B"; } else if (weightInByte < 500000L) { float res = weightInByte / 1000f; return res.ToString("n2") + " KB"; } else if (weightInByte < 500000000L) { float res = weightInByte / 1000000f; return res.ToString("n2") + " MB"; } else { float res = weightInByte / 1000000000f; return res.ToString("n2") + " GB"; } } /// /// Should be placed between BeginProperty / EndProperty /// internal static uint DrawRenderingLayerMask(Rect rect, uint renderingLayer, GUIContent label = null, bool allowHelpBox = true) { var value = EditorGUI.RenderingLayerMaskField(rect, label ?? GUIContent.none, renderingLayer); return value; } internal static void DrawRenderingLayerMask(Rect rect, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(rect, label, property); EditorGUI.BeginChangeCheck(); var renderingLayer = DrawRenderingLayerMask(rect, property.uintValue, label); if (EditorGUI.EndChangeCheck()) { if(property.numericType == SerializedPropertyNumericType.UInt32) property.uintValue = renderingLayer; else property.intValue = unchecked((int)renderingLayer); } EditorGUI.EndProperty(); } internal static void DrawRenderingLayerMask(SerializedProperty property, GUIContent style) { Rect rect = EditorGUILayout.GetControlRect(true); DrawRenderingLayerMask(rect, property, style); } // IsPreset is an internal API - lets reuse the usable part of this function // 93 is a "magic number" and does not represent a combination of other flags here internal static bool IsPresetEditor(UnityEditor.Editor editor) { return (int)((editor.target as Component).gameObject.hideFlags) == 93; } internal static void QualitySettingsHelpBox(string message, MessageType type, HDRenderPipelineUI.ExpandableGroup uiGroupSection, string propertyPath) { CoreEditorUtils.DrawFixMeBox(message, type, "Open", () => { SettingsService.OpenProjectSettings("Project/Quality/HDRP"); HDRenderPipelineUI.Inspector.Expand((int)uiGroupSection); CoreEditorUtils.Highlight("Project Settings", propertyPath, HighlightSearchMode.Identifier); GUIUtility.ExitGUI(); }); } internal static void QualitySettingsHelpBox(string message, MessageType type, HDRenderPipelineUI.ExpandableGroup uiGroupSection, TEnum uiSection, string propertyPath) where TEnum : struct, IConvertible { QualitySettingsHelpBoxForReflection(message, type, uiGroupSection, uiSection.ToInt32(System.Globalization.CultureInfo.InvariantCulture), propertyPath); } internal static void QualitySettingsHelpBoxForReflection(string message, MessageType type, HDRenderPipelineUI.ExpandableGroup uiGroupSection, int uiSection, string propertyPath) { CoreEditorUtils.DrawFixMeBox(message, type, "Open", () => { SettingsService.OpenProjectSettings("Project/Quality/HDRP"); HDRenderPipelineUI.SubInspectors[uiGroupSection].Expand(uiSection); CoreEditorUtils.Highlight("Project Settings", propertyPath, HighlightSearchMode.Identifier); GUIUtility.ExitGUI(); }); } internal static void GlobalSettingsHelpBox(string message, MessageType type) where TGraphicsSettings: IRenderPipelineGraphicsSettings { CoreEditorUtils.DrawFixMeBox(message, type, "Open", () => { GraphicsSettingsInspectorUtility.OpenAndScrollTo(); }); } internal static void GlobalSettingsHelpBox(string message, MessageType type, FrameSettingsField field, string displayName) { CoreEditorUtils.DrawFixMeBox(message, type, "Open", () => { var attribute = FrameSettingsExtractedDatas.GetFieldAttribute(field); GraphicsSettingsInspectorUtility.OpenAndScrollTo(line => { if (line.name != $"line-field-{field}") return false; FrameSettingsPropertyDrawer.SetExpended(FrameSettingsRenderType.Camera.ToString(), attribute.group, true); return true; }); }); } // This is used through reflection by inspector in srp core static bool DataDrivenLensFlareHelpBox() { if (!HDRenderPipeline.currentAsset?.currentPlatformRenderPipelineSettings.supportDataDrivenLensFlare ?? false) { EditorGUILayout.Space(); HDEditorUtils.QualitySettingsHelpBox("The current HDRP Asset does not support Data Driven Lens Flare.", MessageType.Error, HDRenderPipelineUI.ExpandableGroup.PostProcess, HDRenderPipelineUI.ExpandablePostProcess.LensFlare, "m_RenderPipelineSettings.supportDataDrivenLensFlare"); return false; } HDEditorUtils.EnsureFrameSetting(FrameSettingsField.LensFlareDataDriven, "Lens Flare Data Driven"); return true; } static void OpenRenderingDebugger(string panelName) { EditorApplication.ExecuteMenuItem("Window/Analysis/Rendering Debugger"); if (panelName != null) { var manager = DebugManager.instance; manager.RequestEditorWindowPanelIndex(manager.FindPanelIndex(panelName)); } } static void HighlightInDebugger(HDCamera hdCamera, FrameSettingsField field, string displayName) { OpenRenderingDebugger(hdCamera.camera.name); // Doesn't work for some reason //CoreEditorUtils.Highlight("Rendering Debugger", displayName, HighlightSearchMode.Auto); //GUIUtility.ExitGUI(); } internal static void FrameSettingsHelpBox(HDCamera hdCamera, FrameSettingsField field, string displayName) { var data = HDUtils.TryGetAdditionalCameraDataOrDefault(hdCamera.camera); var defaults = GraphicsSettings.GetRenderPipelineSettings().GetDefaultFrameSettings(FrameSettingsRenderType.Camera); var type = MessageType.Warning; var attribute = FrameSettingsExtractedDatas.GetFieldAttribute(field); bool disabledInGlobal = !defaults.IsEnabled(field); bool disabledByCamera = data.renderingPathCustomFrameSettingsOverrideMask.mask[(uint)field] && !data.renderingPathCustomFrameSettings.IsEnabled(field); bool disabledByDependency = !attribute.dependencies.All(hdCamera.frameSettings.IsEnabled); var historyContainer = hdCamera.camera.cameraType == CameraType.SceneView ? FrameSettingsHistory.sceneViewFrameSettingsContainer : HDUtils.TryGetAdditionalCameraDataOrDefault(hdCamera.camera); bool disabledByDebug = FrameSettingsHistory.enabled && !historyContainer.frameSettingsHistory.debug.IsEnabled(field) && historyContainer.frameSettingsHistory.sanitazed.IsEnabled(field); var textBase = $"The FrameSetting required to render this effect in the {(hdCamera.camera.cameraType == CameraType.SceneView ? "Scene" : "Game")} view "; if (disabledByDebug) CoreEditorUtils.DrawFixMeBox(textBase + "is disabled in the Rendering Debugger.", type, "Open", () => HighlightInDebugger(hdCamera, field, displayName)); else if (disabledByCamera) CoreEditorUtils.DrawFixMeBox(textBase + "is disabled on a Camera.", type, "Open", () => EditorUtility.OpenPropertyEditor(hdCamera.camera)); else if (disabledInGlobal) GlobalSettingsHelpBox(textBase + "is disabled in the HDRP Global Settings.", type, field, displayName); else if (disabledByDependency) GlobalSettingsHelpBox(textBase + "depends on a disabled FrameSetting.", type, field, displayName); } internal static HDCamera[] GetDisplayedCameras() { HashSet visibleCamera = new(); foreach (SceneView sceneView in SceneView.sceneViews) { if (!sceneView.hasFocus) continue; visibleCamera.Add(HDCamera.GetOrCreate(sceneView.camera)); } var assembly = typeof(EditorWindow).Assembly; var type = assembly.GetType("UnityEditor.GameView"); var targetDisplayProp = type.GetProperty("targetDisplay"); foreach (EditorWindow gameView in Resources.FindObjectsOfTypeAll(type)) { if (!gameView.hasFocus) continue; var targetDisplay = (int)targetDisplayProp.GetValue(gameView); foreach (var camera in HDCamera.GetHDCameras()) { if (camera == null || camera.camera == null) continue; if (camera.camera.cameraType == CameraType.Game && camera.camera.targetDisplay == targetDisplay) visibleCamera.Add(camera); } } return visibleCamera.ToArray(); } internal static bool EnsureFrameSetting(FrameSettingsField field, string displayName) { foreach (var camera in GetDisplayedCameras()) { if (!camera.frameSettings.IsEnabled(field)) { FrameSettingsHelpBox(camera, field, displayName); EditorGUILayout.Space(); return false; } } return true; } internal static bool EnsureVolumeAndFrameSetting(Func volumeValidator, FrameSettingsField field, string displayName) where T : UnityEngine.Rendering.VolumeComponent { // Wait for volume system to be initialized if (VolumeManager.instance.baseComponentTypeArray == null) return true; var cameras = GetDisplayedCameras(); foreach (var camera in cameras) { var errorString = volumeValidator(camera.volumeStack.GetComponent()); if (!string.IsNullOrEmpty(errorString)) { EditorGUILayout.HelpBox(errorString, MessageType.Warning); EditorGUILayout.Space(); return false; } } foreach (var camera in cameras) { if (!camera.frameSettings.IsEnabled(field)) { FrameSettingsHelpBox(camera, field, displayName); EditorGUILayout.Space(); return false; } } return true; } } // Due to a UI bug/limitation, we have to do it this way to support bold labels internal class BoldLabelScope : GUI.Scope { FontStyle origFontStyle; public BoldLabelScope() { origFontStyle = EditorStyles.label.fontStyle; EditorStyles.label.fontStyle = FontStyle.Bold; } protected override void CloseScope() { EditorStyles.label.fontStyle = origFontStyle; } } }