Rasagar/Library/PackageCache/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/VolumeComponentWithQualityEditor.cs
2024-08-26 23:07:20 +03:00

252 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
namespace UnityEditor.Rendering.HighDefinition
{
internal abstract class VolumeComponentWithQualityEditor : VolumeComponentEditor
{
// Quality settings
SerializedDataParameter m_QualitySetting;
/// <summary>
/// An opaque blob storing preset settings (used to remember what were the last custom settings that were used).
/// </summary>
internal class QualitySettingsBlob
{
private struct QualitySetting
{
public bool state;
public object value;
}
Dictionary<int, QualitySetting> settings = new Dictionary<int, QualitySetting>();
public static bool IsEqual(QualitySettingsBlob left, QualitySettingsBlob right)
{
if (right == null && left == null)
{
return true;
}
if ((right == null && left != null) || (right != null && left == null))
{
return false;
}
if (left.settings.Count != right.settings.Count)
{
return false;
}
foreach (var key in left.settings.Keys)
{
if (!left.settings[key].Equals(right.settings[key]))
{
return false;
}
}
return true;
}
// TODO: Should be an override of GetHashCode (in core)?
int Hash(SerializedDataParameter setting)
{
int hash = setting.GetHashCode();
unchecked
{
hash = 23 * setting.value.GetHashCode();
hash = 23 * setting.overrideState.GetHashCode();
}
return hash;
}
// Save a setting to the quality blob.
public void Save<T>(SerializedDataParameter setting) where T : struct
{
QualitySetting s;
s.state = setting.overrideState.boolValue;
s.value = setting.value.GetInline<T>();
int key = Hash(setting);
if (settings.ContainsKey(key))
{
// If the setting exists, overwrite the value
settings[key] = s;
}
else
{
// Otherwise, cache a new setting
settings.Add(key, s);
}
}
// Attempts to fill a quality setting with its custom value and override state.
public void TryLoad<T>(ref SerializedDataParameter setting) where T : struct
{
if (settings.TryGetValue(Hash(setting), out QualitySetting s))
{
setting.value.SetInline((T)s.value);
setting.overrideState.boolValue = s.state;
}
}
}
/// <summary>
/// Scoped quality setting change checker.
/// </summary>
public struct QualityScope : IDisposable
{
VolumeComponentWithQualityEditor m_QualityComponent;
QualitySettingsBlob m_Settings;
// Cache the quality setting
public QualityScope(VolumeComponentWithQualityEditor component)
{
m_QualityComponent = component;
m_Settings = m_QualityComponent.SaveCustomQualitySettingsAsObject();
EditorGUI.BeginChangeCheck();
}
public void Dispose()
{
if (EditorGUI.EndChangeCheck())
{
QualitySettingsBlob newSettings = m_QualityComponent?.SaveCustomQualitySettingsAsObject();
if (!QualitySettingsBlob.IsEqual(m_Settings, newSettings))
m_QualityComponent?.QualitySettingsWereChanged();
}
}
}
// 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<UnityEngine.Object, QualitySettingsBlob> s_CustomSettingsHistory = new ConditionalWeakTable<UnityEngine.Object, QualitySettingsBlob>();
static readonly int k_CustomQuality = ScalableSettingLevelParameter.LevelCount;
public override void OnEnable()
{
var o = new PropertyFetcher<VolumeComponentWithQuality>(serializedObject);
m_QualitySetting = Unpack(o.Find(x => x.quality));
// Ensure we reflect presets in the pipeline asset, not the hardcoded defaults.
// Warning: base.OnEnable must be called after VolumeComponentWithQuality has unpacked SerializedData.
if (RenderPipelineManager.currentPipeline is HDRenderPipeline pipeline)
{
serializedObject.Update();
if (m_QualitySetting.value.intValue < k_CustomQuality && QualityEnabled())
LoadSettingsFromQualityPreset(pipeline.currentPlatformRenderPipelineSettings, m_QualitySetting.value.intValue);
serializedObject.ApplyModifiedProperties();
}
}
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() && QualityEnabled())
{
int newQualityLevel = m_QualitySetting.value.intValue;
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)
{
QualitySettingsBlob 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)
{
QualitySettingsBlob 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;
/// <summary>
/// This utility can be used to copy a value into a volume component setting visible in the inspector.
/// </summary>
protected void CopySetting<T>(ref SerializedDataParameter setting, T value) where T : struct
{
setting.value.SetInline(value);
// Force enable the override state to match the state of the quality setting.
setting.overrideState.boolValue = m_QualitySetting.overrideState.boolValue;
}
/// <summary>
/// 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.
/// </summary>
public void QualitySettingsWereChanged() { m_QualitySetting.value.intValue = k_CustomQuality; }
/// <summary>
/// This function should be overriden by a volume component to load preset settings from RenderPipelineSettings
/// </summary>
public virtual void LoadSettingsFromQualityPreset(RenderPipelineSettings settings, int level) { }
/// <summary>
/// This function should be overriden by a volume component to return an opaque object (binary blob) with the custom quality settings currently in use.
/// </summary>
public virtual QualitySettingsBlob SaveCustomQualitySettingsAsObject(QualitySettingsBlob history = null) { return null; }
/// <summary>
/// This function should be overriden by a volume component to load a custom preset setting from an opaque binary blob (as returned from SaveCustomQualitySettingsAsObject)
/// </summary>
public virtual void LoadSettingsFromObject(QualitySettingsBlob settings) { }
/// <summary>
/// This function should be overriden by a volume component to enable the quality setting functionality only in certain cases.
/// </summary>
/// <returns></returns>
public virtual bool QualityEnabled() => true;
}
}