using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor.Inspector.GraphicsSettingsInspectors;
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 QualitySettingsSheetPath =
@"Packages/com.unity.render-pipelines.high-definition/Editor/USS/QualitySettings";
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. #from(2021.1)")]
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 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 == -1 ? (int)uiGroupSection : 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);
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(Camera camera, FrameSettingsField field, string displayName)
{
OpenRenderingDebugger(camera.name);
// Doesn't work for some reason
//CoreEditorUtils.Highlight("Rendering Debugger", displayName, HighlightSearchMode.Auto);
//GUIUtility.ExitGUI();
}
static IEnumerable GetAllCameras()
{
foreach (SceneView sceneView in SceneView.sceneViews)
yield return sceneView.camera;
foreach (Camera camera in Camera.allCameras)
if (camera.cameraType == CameraType.Game)
yield return camera;
}
static IEnumerable<(Camera camera, FrameSettings @default, IFrameSettingsHistoryContainer historyContainer)> SelectFrameSettingsStages(IEnumerable cameras)
{
var supportedFeatures = HDRenderPipeline.currentAsset.currentPlatformRenderPipelineSettings;
var defaultSettings = GraphicsSettings.GetRenderPipelineSettings().GetDefaultFrameSettings(FrameSettingsRenderType.Camera);
foreach (var camera in cameras)
{
var additionalCameraData = HDUtils.TryGetAdditionalCameraDataOrDefault(camera);
var historyContainer = camera.cameraType == CameraType.SceneView ? FrameSettingsHistory.sceneViewFrameSettingsContainer : additionalCameraData;
FrameSettings dummy = default;
FrameSettingsHistory.AggregateFrameSettings(ref dummy, camera, historyContainer, ref defaultSettings, supportedFeatures);
yield return (camera, defaultSettings, historyContainer);
}
}
static void FrameSettingsHelpBox(Camera camera, FrameSettingsField field, FrameSettings @default, IFrameSettingsHistoryContainer historyContainer)
{
FrameSettingsHistory history = historyContainer.frameSettingsHistory;
bool finalValue = history.debug.IsEnabled(field);
if (finalValue) return; //must be false to call this method
bool defaultValue = @default.IsEnabled(field);
bool cameraOverrideState = historyContainer.hasCustomFrameSettings && history.customMask.mask[(uint)field];
bool cameraOverridenValue = history.overridden.IsEnabled(field);
bool cameraSanitizedValue = history.sanitazed.IsEnabled(field);
var attribute = FrameSettingsExtractedDatas.GetFieldAttribute(field);
bool dependenciesSanitizedValueOk = attribute.dependencies.All(fs => attribute.IsNegativeDependency(fs) ? !history.sanitazed.IsEnabled(fs) : history.sanitazed.IsEnabled(fs));
bool disabledByDefault = !defaultValue && !cameraOverrideState;
bool disabledByCameraOverride = cameraOverrideState && !cameraOverridenValue;
// If the setting is enabled in the frame settings but is disabled in the HDRP Asset (cameraSanitizedValue), it means the feature is disabled and we should not display anything.
bool disabledbySanitized = (cameraOverrideState ? cameraOverridenValue : defaultValue) && !cameraSanitizedValue;
var textBase = $"The Frame Setting required to render this effect in the {(camera.cameraType == CameraType.SceneView ? "Scene" : "Game")} view ";
if (disabledByDefault)
GlobalSettingsHelpBox(textBase + "is disabled in the HDRP Default Frame Settings.", MessageType.Warning, field, attribute.displayedName);
else if (disabledByCameraOverride)
CoreEditorUtils.DrawFixMeBox(textBase + $"is disabled in the {camera.name}'s Custom Frame Settings.", MessageType.Warning, "Open", () => EditorUtility.OpenPropertyEditor(camera));
else if (!dependenciesSanitizedValueOk)
{
if(cameraOverrideState)
CoreEditorUtils.DrawFixMeBox(textBase + $"depends on a disabled Frame Setting parent in the {camera.name} Custom Frame Settings.", MessageType.Warning, "Open", () => EditorUtility.OpenPropertyEditor(camera));
else
GlobalSettingsHelpBox(textBase + "depends on a disabled Frame Setting parent in the HDRP Default Frame Settings.", MessageType.Warning, field, attribute.displayedName);
}
else if (!finalValue && !disabledbySanitized)
CoreEditorUtils.DrawFixMeBox(textBase + "is disabled in the Rendering Debugger.", MessageType.Warning, "Open", () => HighlightInDebugger(camera, field, attribute.displayedName));
}
internal static bool EnsureFrameSetting(FrameSettingsField field)
{
foreach ((Camera camera, FrameSettings @default, IFrameSettingsHistoryContainer historyContainer) in SelectFrameSettingsStages(GetAllCameras()))
{
if (!historyContainer.frameSettingsHistory.debug.IsEnabled(field))
{
FrameSettingsHelpBox(camera, field, @default, historyContainer);
EditorGUILayout.Space();
return false;
}
}
return true;
}
static IEnumerable<(Camera camera, T component)> SelectVolumeComponent(IEnumerable cameras) where T : VolumeComponent
{
// Wait for volume system to be initialized
if (!VolumeManager.instance.isInitialized)
yield break;
foreach (var camera in GetAllCameras())
{
if (!HDCamera.TryGet(camera, out var hdCamera))
continue;
T component = hdCamera.volumeStack.GetComponent();
if (component == null)
continue;
yield return (camera, component);
}
}
internal static bool EnsureVolume(Func volumeValidator) where T : VolumeComponent
{
foreach ((Camera camera, T component) in SelectVolumeComponent(GetAllCameras()))
{
var errorString = volumeValidator(component);
if (!string.IsNullOrEmpty(errorString))
{
EditorGUILayout.HelpBox(errorString, MessageType.Warning);
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;
}
}
}