diff --git a/com.unity.render-pipelines.high-definition/CHANGELOG.md b/com.unity.render-pipelines.high-definition/CHANGELOG.md
index 69d1b936354..e50057855fb 100644
--- a/com.unity.render-pipelines.high-definition/CHANGELOG.md
+++ b/com.unity.render-pipelines.high-definition/CHANGELOG.md
@@ -802,6 +802,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Increased path tracing BSDFs roughness range from [0.001, 0.999] to [0.00001, 0.99999].
- Changing the default SSGI radius for the all configurations.
- Changed the default parameters for quality RTGI to match expected behavior.
+- Improved DoF UI/UX (case 1240204)
## [7.1.1] - 2019-09-05
diff --git a/com.unity.render-pipelines.high-definition/Editor/PostProcessing/DepthOfFieldEditor.cs b/com.unity.render-pipelines.high-definition/Editor/PostProcessing/DepthOfFieldEditor.cs
index b83fe275edf..eae91f55b7e 100644
--- a/com.unity.render-pipelines.high-definition/Editor/PostProcessing/DepthOfFieldEditor.cs
+++ b/com.unity.render-pipelines.high-definition/Editor/PostProcessing/DepthOfFieldEditor.cs
@@ -60,66 +60,182 @@ public override void OnInspectorGUI()
int mode = m_FocusMode.value.intValue;
if (mode == (int)DepthOfFieldMode.Off)
- return;
+ {
+ GUI.enabled = false;
+ }
+
+ // Draw the focus mode controls
+ HDEditorUtils.BeginIndent();
+ DrawFocusSettings(mode);
+ HDEditorUtils.EndIndent();
+
+ EditorGUILayout.Space();
+ // Draw the quality controls
base.OnInspectorGUI();
+ HDEditorUtils.BeginIndent();
+ GUI.enabled = GUI.enabled && base.overrideState;
+ DrawQualitySettings();
+ HDEditorUtils.EndIndent();
- bool advanced = isInAdvancedMode;
+ GUI.enabled = true;
+ }
- if (mode == (int)DepthOfFieldMode.UsePhysicalCamera)
+ void DrawFocusSettings(int mode)
+ {
+ if (mode == (int)DepthOfFieldMode.Off)
+ {
+ // When DoF is off, display a focus distance at infinity
+ var val = m_FocusDistance.value.floatValue;
+ m_FocusDistance.value.floatValue = Mathf.Infinity;
+ PropertyField(m_FocusDistance);
+ m_FocusDistance.value.floatValue = val;
+ }
+ else if (mode == (int)DepthOfFieldMode.UsePhysicalCamera)
{
PropertyField(m_FocusDistance);
-
- if (advanced)
- {
- GUI.enabled = useCustomValue;
- EditorGUILayout.LabelField("Near Blur", EditorStyles.miniLabel);
- PropertyField(m_NearSampleCount, EditorGUIUtility.TrTextContent("Sample Count"));
- PropertyField(m_NearMaxBlur, EditorGUIUtility.TrTextContent("Max Radius"));
-
- EditorGUILayout.LabelField("Far Blur", EditorStyles.miniLabel);
- PropertyField(m_FarSampleCount, EditorGUIUtility.TrTextContent("Sample Count"));
- PropertyField(m_FarMaxBlur, EditorGUIUtility.TrTextContent("Max Radius"));
- GUI.enabled = true;
- }
}
else if (mode == (int)DepthOfFieldMode.Manual)
{
- EditorGUILayout.Space();
-
- EditorGUILayout.LabelField("Near Blur", EditorStyles.miniLabel);
+ EditorGUILayout.LabelField("Near Range", EditorStyles.miniLabel);
PropertyField(m_NearFocusStart, EditorGUIUtility.TrTextContent("Start"));
PropertyField(m_NearFocusEnd, EditorGUIUtility.TrTextContent("End"));
- if (advanced)
- {
- GUI.enabled = useCustomValue;
- PropertyField(m_NearSampleCount, EditorGUIUtility.TrTextContent("Sample Count"));
- PropertyField(m_NearMaxBlur, EditorGUIUtility.TrTextContent("Max Radius"));
- GUI.enabled = true;
- }
-
- EditorGUILayout.LabelField("Far Blur", EditorStyles.miniLabel);
+ EditorGUILayout.LabelField("Far Range", EditorStyles.miniLabel);
PropertyField(m_FarFocusStart, EditorGUIUtility.TrTextContent("Start"));
PropertyField(m_FarFocusEnd, EditorGUIUtility.TrTextContent("End"));
-
- if (advanced)
- {
- GUI.enabled = useCustomValue;
- PropertyField(m_FarSampleCount, EditorGUIUtility.TrTextContent("Sample Count"));
- PropertyField(m_FarMaxBlur, EditorGUIUtility.TrTextContent("Max Radius"));
- GUI.enabled = true;
- }
}
+ }
+
+ void DrawQualitySettings()
+ {
+ object oldSettings = SaveCustomQualitySettingsAsObject();
+ EditorGUI.BeginChangeCheck();
+ EditorGUILayout.LabelField("Near Blur", EditorStyles.miniLabel);
+ PropertyField(m_NearSampleCount, EditorGUIUtility.TrTextContent("Sample Count"));
+ PropertyField(m_NearMaxBlur, EditorGUIUtility.TrTextContent("Max Radius"));
+
+ EditorGUILayout.LabelField("Far Blur", EditorStyles.miniLabel);
+ PropertyField(m_FarSampleCount, EditorGUIUtility.TrTextContent("Sample Count"));
+ PropertyField(m_FarMaxBlur, EditorGUIUtility.TrTextContent("Max Radius"));
- if (advanced)
+ if (isInAdvancedMode)
{
- GUI.enabled = useCustomValue;
EditorGUILayout.LabelField("Advanced Tweaks", EditorStyles.miniLabel);
PropertyField(m_Resolution);
PropertyField(m_HighQualityFiltering);
- GUI.enabled = true;
}
+
+ if (EditorGUI.EndChangeCheck())
+ {
+ object newSettings = SaveCustomQualitySettingsAsObject();
+
+ if (!QualitySettingsBlob.IsEqual(oldSettings as QualitySettingsBlob, newSettings as QualitySettingsBlob))
+ QualitySettingsWereChanged();
+ }
+ }
+
+ /// An opaque binary blob storing preset settings (used to remember what were the last custom settings that were used).
+ /// For the functionality to save and restore the settings
+ class QualitySettingsBlob
+ {
+ public int nearSampleCount;
+ public float nearMaxBlur;
+ public int farSampleCount;
+ public float farMaxBlur;
+ public DepthOfFieldResolution resolution;
+ public bool hqFiltering;
+
+ public bool[] overrideState = new bool[6];
+
+ public static bool IsEqual(QualitySettingsBlob left, QualitySettingsBlob right)
+ {
+ if ((right == null && left != null) || (right != null && left == null))
+ {
+ return false;
+ }
+
+ if (right == null && left == null)
+ {
+ return true;
+ }
+
+ for (int i=0; i < left.overrideState.Length; ++i)
+ {
+ if (left.overrideState[i] != right.overrideState[i])
+ {
+ return false;
+ }
+ }
+
+ return left.nearSampleCount == right.nearSampleCount
+ && left.nearMaxBlur == right.nearMaxBlur
+ && left.farSampleCount == right.farSampleCount
+ && left.farMaxBlur == right.farMaxBlur
+ && left.resolution == right.resolution
+ && left.hqFiltering == right.hqFiltering;
+ }
+ }
+
+ public override void LoadSettingsFromObject(object settings)
+ {
+ QualitySettingsBlob qualitySettings = settings as QualitySettingsBlob;
+
+ m_NearSampleCount.value.intValue = qualitySettings.nearSampleCount;
+ m_NearMaxBlur.value.floatValue = qualitySettings.nearMaxBlur;
+ m_FarSampleCount.value.intValue = qualitySettings.farSampleCount;
+ m_FarMaxBlur.value.floatValue = qualitySettings.farMaxBlur;
+ m_Resolution.value.intValue = (int) qualitySettings.resolution;
+ m_HighQualityFiltering.value.boolValue = qualitySettings.hqFiltering;
+
+ m_NearSampleCount.overrideState.boolValue = qualitySettings.overrideState[0];
+ m_NearMaxBlur.overrideState.boolValue = qualitySettings.overrideState[1];
+ m_FarSampleCount.overrideState.boolValue = qualitySettings.overrideState[2];
+ m_FarMaxBlur.overrideState.boolValue = qualitySettings.overrideState[3];
+ m_Resolution.overrideState.boolValue = qualitySettings.overrideState[4];
+ m_HighQualityFiltering.overrideState.boolValue = qualitySettings.overrideState[5];
+ }
+
+ public override void LoadSettingsFromQualityPreset(RenderPipelineSettings settings, int level)
+ {
+ m_NearSampleCount.value.intValue = settings.postProcessQualitySettings.NearBlurSampleCount[level];
+ m_NearMaxBlur.value.floatValue = settings.postProcessQualitySettings.NearBlurMaxRadius[level];
+
+ m_FarSampleCount.value.intValue = settings.postProcessQualitySettings.FarBlurSampleCount[level];
+ m_FarMaxBlur.value.floatValue = settings.postProcessQualitySettings.FarBlurMaxRadius[level];
+
+ m_Resolution.value.intValue = (int) settings.postProcessQualitySettings.DoFResolution[level];
+ m_HighQualityFiltering.value.boolValue = settings.postProcessQualitySettings.DoFHighQualityFiltering[level];
+
+ // set all quality override states to true, to indicate that these values are actually used
+ m_NearSampleCount.overrideState.boolValue = true;
+ m_NearMaxBlur.overrideState.boolValue = true;
+ m_FarSampleCount.overrideState.boolValue = true;
+ m_FarMaxBlur.overrideState.boolValue = true;
+ m_Resolution.overrideState.boolValue = true;
+ m_HighQualityFiltering.overrideState.boolValue = true;
+
+ }
+
+ public override object SaveCustomQualitySettingsAsObject(object history = null)
+ {
+ QualitySettingsBlob qualitySettings = (history != null) ? history as QualitySettingsBlob : new QualitySettingsBlob();
+
+ qualitySettings.nearSampleCount = m_NearSampleCount.value.intValue;
+ qualitySettings.nearMaxBlur = m_NearMaxBlur.value.floatValue;
+ qualitySettings.farSampleCount = m_FarSampleCount.value.intValue;
+ qualitySettings.farMaxBlur = m_FarMaxBlur.value.floatValue;
+ qualitySettings.resolution = (DepthOfFieldResolution) m_Resolution.value.intValue;
+ qualitySettings.hqFiltering = m_HighQualityFiltering.value.boolValue;
+
+ qualitySettings.overrideState[0] = m_NearSampleCount.overrideState.boolValue;
+ qualitySettings.overrideState[1] = m_NearMaxBlur.overrideState.boolValue;
+ qualitySettings.overrideState[2] = m_FarSampleCount.overrideState.boolValue;
+ qualitySettings.overrideState[3] = m_FarMaxBlur.overrideState.boolValue;
+ qualitySettings.overrideState[4] = m_Resolution.overrideState.boolValue;
+ qualitySettings.overrideState[5] = m_HighQualityFiltering.overrideState.boolValue;
+
+ return qualitySettings;
}
}
}
diff --git a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDEditorUtils.cs b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDEditorUtils.cs
index 29c7745ea9b..3007a4a775f 100644
--- a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDEditorUtils.cs
+++ b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDEditorUtils.cs
@@ -277,6 +277,30 @@ internal static void HandlePrefixLabelWithIndent(Rect totalPosition, Rect labelP
labelPosition.x += EditorGUI.indentLevel * 15;
EditorGUI.HandlePrefixLabel(totalPosition, labelPosition, label);
}
+
+ ///
+ /// Like EditorGUI.indentLevel++ but this one will also indent the override checkboxes
+ ///
+ internal static void BeginIndent()
+ {
+ // When using EditorGUI.indentLevel++, the clicking on the checkboxes does not work properly due to some issues on the C++ side.
+ // This function is a work-around for this issue.
+ const float offset = 15f;
+ GUILayout.BeginHorizontal();
+ EditorGUILayout.Space(offset, false);
+ GUILayout.BeginVertical();
+ EditorGUIUtility.labelWidth -= offset;
+ }
+
+ ///
+ /// To be used for resetting the indentation after calling BeginIndent
+ ///
+ internal static void EndIndent()
+ {
+ EditorGUIUtility.labelWidth = 0f;
+ GUILayout.EndVertical();
+ GUILayout.EndHorizontal();
+ }
}
internal static partial class SerializedPropertyExtension
diff --git a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/VolumeComponentWithQualityEditor.cs b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/VolumeComponentWithQualityEditor.cs
index c790e1c7918..3d4cbb9e06e 100644
--- a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/VolumeComponentWithQualityEditor.cs
+++ b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/VolumeComponentWithQualityEditor.cs
@@ -1,3 +1,5 @@
+using System.Runtime.CompilerServices;
+using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
namespace UnityEditor.Rendering.HighDefinition
@@ -7,15 +9,106 @@ internal abstract class VolumeComponentWithQualityEditor : VolumeComponentEditor
// Quality settings
SerializedDataParameter m_QualitySetting;
+ // Note: Editors are refreshed on gui changes by the volume system, so any state that we want to store here needs to be a static (or in a serialized variable)
+ // We use ConditionalWeakTable instead of a Dictionary of InstanceIDs to get automatic clean-up of dead entries in the table
+ static ConditionalWeakTable s_CustomSettingsHistory = new ConditionalWeakTable();
+
+ static readonly int k_CustomQuality = ScalableSettingLevelParameter.LevelCount;
+
public override void OnEnable()
{
var o = new PropertyFetcher(serializedObject);
m_QualitySetting = Unpack(o.Find(x => x.quality));
}
- public override void OnInspectorGUI() =>PropertyField(m_QualitySetting);
+ public override void OnInspectorGUI()
+ {
+ int prevQualityLevel = m_QualitySetting.value.intValue;
+
+ EditorGUI.BeginChangeCheck();
+ PropertyField(m_QualitySetting);
+
+ // When a quality preset changes, we want to detect and reflect the settings in the UI. PropertyFields mirror the contents of one memory loccation, so
+ // the idea is that we copy the presets to that location. This logic is optional, if volume components don't override the helper functions at the end,
+ // they will continue to work, but the preset settings will not be reflected in the UI.
+ if (EditorGUI.EndChangeCheck())
+ {
+ // The ScalableSettingLevelParameterEditor updates the value of the referenced object directly (and not the reflected one),
+ // so this is what we have to do to get the new value selected by the user:
+ var o = m_QualitySetting.GetObjectRef();
+ var (preset, custom) = o.levelAndOverride;
+ int newQualityLevel = custom ? 3 : preset;
+ m_QualitySetting.value.intValue = newQualityLevel;
+
+ if (newQualityLevel == k_CustomQuality)
+ {
+ // If we have switched to custom quality from a preset, then load the last custom quality settings the user has used in this volume
+ if (prevQualityLevel != k_CustomQuality)
+ {
+ object history = null;
+ s_CustomSettingsHistory.TryGetValue(serializedObject.targetObject, out history);
+ if (history != null)
+ {
+ LoadSettingsFromObject(history);
+ }
+ }
+ }
+ else
+ {
+ // If we are going to use a quality preset, then load the preset values so they are reflected in the UI
+ var pipeline = (HDRenderPipeline)RenderPipelineManager.currentPipeline;
+ if (pipeline != null)
+ {
+ // If we switch from a custom quality level, then save these values so we can re-use them if teh user switches back
+ if (prevQualityLevel == k_CustomQuality)
+ {
+ int key = serializedObject.targetObject.GetInstanceID();
+ object history = null;
+ s_CustomSettingsHistory.TryGetValue(serializedObject.targetObject, out history);
+ if (history != null)
+ {
+ SaveCustomQualitySettingsAsObject(history);
+ }
+ else
+ {
+ // Only keep track of custom settings for components that implement the new interface (and return not null)
+ history = SaveCustomQualitySettingsAsObject();
+ if (history != null)
+ {
+ s_CustomSettingsHistory.Add(serializedObject.targetObject, history);
+ }
+
+ }
+ }
+ LoadSettingsFromQualityPreset(pipeline.currentPlatformRenderPipelineSettings, newQualityLevel);
+ }
+ }
+ }
+ }
+
+ protected bool useCustomValue => m_QualitySetting.value.intValue == k_CustomQuality;
+ protected bool overrideState => m_QualitySetting.overrideState.boolValue;
+
+ ///
+ /// This should be called after the user manually edits a quality setting that appears in a preset. After calling this function, the quality preset will change to Custom.
+ ///
+ public void QualitySettingsWereChanged() { m_QualitySetting.value.intValue = k_CustomQuality; }
+
+ ///
+ /// This function should be overriden by a volume component to load preset settings from RenderPipelineSettings
+ ///
+ public virtual void LoadSettingsFromQualityPreset(RenderPipelineSettings settings, int level) { }
+
+ ///
+ /// This function should be overriden by a volume component to return an opaque object (binary blob) with the custom quality settings currently in use.
+ ///
+ public virtual object SaveCustomQualitySettingsAsObject(object history = null) { return null; }
+
+ ///
+ /// This function should be overriden by a volume component to load a custom preset setting from an opaque binary blob (as returned from SaveCustomQualitySettingsAsObject)
+ ///
+ public virtual void LoadSettingsFromObject(object settings) { }
- protected bool useCustomValue => m_QualitySetting.value.intValue == ScalableSettingLevelParameter.LevelCount;
}
}