diff --git a/CHANGELOG.md b/CHANGELOG.md index a3cd907..188e1b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Support for the PlayStation 5 platform has been added. - Support for the XboxSeries platform has been added. +- New API in DynamicResolutionHandler to handle multicamera rendering for hardware mode. Changing cameras and resetting scaling per camera should be safe. +- New API functions with no side effects in DynamicResolutionHandler, to retrieve resolved drs scale and to apply DRS on a size. ### Fixed - Fixed the default background color for previews to use the original color. @@ -19,7 +21,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Fixed ACES tonemaping for Nintendo Switch by forcing some shader color conversion functions to full float precision. - Fixed missing warning UI about Projector component being unsupported (case 1300327). - Fixed the display name of a Volume Parameter when is defined the attribute InspectorName +- Fixed ACES tonemaping on mobile platforms by forcing some shader color conversion functions to full float precision. - Fix crash on VolumeComponentWithQualityEditor when the current Pipeline is not HDRP +- Calculating correct rtHandleScale by considering the possible pixel rounding when DRS is on ## [10.2.0] - 2020-10-19 diff --git a/Editor/Volume/VolumeComponentProvider.cs b/Editor/Volume/VolumeComponentProvider.cs index 4a74aef..661d923 100644 --- a/Editor/Volume/VolumeComponentProvider.cs +++ b/Editor/Volume/VolumeComponentProvider.cs @@ -51,7 +51,7 @@ public void CreateComponentTree(List tree) { tree.Add(new GroupElement(0, "Volume Overrides")); - var types = VolumeManager.instance.baseComponentTypes; + var types = VolumeManager.instance.baseComponentTypeArray; var rootNode = new PathNode(); foreach (var t in types) diff --git a/Runtime/Common/DynamicResolutionHandler.cs b/Runtime/Common/DynamicResolutionHandler.cs index 342e6f3..f6723f5 100644 --- a/Runtime/Common/DynamicResolutionHandler.cs +++ b/Runtime/Common/DynamicResolutionHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace UnityEngine.Rendering { @@ -30,20 +31,38 @@ public enum DynamicResScalePolicyType /// public class DynamicResolutionHandler { - private bool m_Enabled = false; - private float m_MinScreenFraction = 1.0f; - private float m_MaxScreenFraction = 1.0f; - private float m_CurrentFraction = 1.0f; - private float m_PrevFraction = -1.0f; - private bool m_ForcingRes = false; - private bool m_CurrentCameraRequest = true; - private bool m_ForceSoftwareFallback = false; - - private float m_PrevHWScaleWidth = 1.0f; - private float m_PrevHWScaleHeight = 1.0f; - private Vector2Int m_LastScaledSize = new Vector2Int(0, 0); + private bool m_Enabled; + private float m_MinScreenFraction; + private float m_MaxScreenFraction; + private float m_CurrentFraction; + private bool m_ForcingRes; + private bool m_CurrentCameraRequest; + private float m_PrevFraction; + private bool m_ForceSoftwareFallback; + + private float m_PrevHWScaleWidth; + private float m_PrevHWScaleHeight; + private Vector2Int m_LastScaledSize; + + private void Reset() + { + m_Enabled = false; + m_MinScreenFraction = 1.0f; + m_MaxScreenFraction = 1.0f; + m_CurrentFraction = 1.0f; + m_ForcingRes = false; + m_CurrentCameraRequest = true; + m_PrevFraction = -1.0f; + m_ForceSoftwareFallback = false; + + m_PrevHWScaleWidth = 1.0f; + m_PrevHWScaleHeight = 1.0f; + m_LastScaledSize = new Vector2Int(0, 0); + filter = DynamicResUpscaleFilter.Bilinear; + } - private DynamicResScalePolicyType m_ScalerType = DynamicResScalePolicyType.ReturnsMinMaxLerpFactor; + private static DynamicResScalePolicyType s_ScalerType = DynamicResScalePolicyType.ReturnsMinMaxLerpFactor; + private static PerformDynamicRes s_DynamicResMethod = DefaultDynamicResMethod; // Debug private Vector2Int cachedOriginalSize; @@ -59,22 +78,96 @@ public class DynamicResolutionHandler /// public Vector2Int finalViewport { get; set; } - private DynamicResolutionType type; - private PerformDynamicRes m_DynamicResMethod = null; - private static DynamicResolutionHandler s_Instance = new DynamicResolutionHandler(); + private GlobalDynamicResolutionSettings m_CachedSettings = GlobalDynamicResolutionSettings.NewDefault(); + + private const int CameraDictionaryMaxcCapacity = 32; + private WeakReference m_OwnerCameraWeakRef = null; + private static Dictionary s_CameraInstances = new Dictionary(CameraDictionaryMaxcCapacity); + private static DynamicResolutionHandler s_DefaultInstance = new DynamicResolutionHandler(); + + private static int s_ActiveCameraId = 0; + private static DynamicResolutionHandler s_ActiveInstance = s_DefaultInstance; + + //private global state of ScalableBufferManager + private static bool s_ActiveInstanceDirty = true; + private static float s_GlobalHwFraction = 1.0f; + private static bool s_GlobalHwUpresActive = false; + + private bool FlushScalableBufferManagerState() + { + if (s_GlobalHwUpresActive == HardwareDynamicResIsEnabled() && s_GlobalHwFraction == m_CurrentFraction) + return false; + + s_GlobalHwUpresActive = HardwareDynamicResIsEnabled(); + s_GlobalHwFraction = m_CurrentFraction; + ScalableBufferManager.ResizeBuffers(s_GlobalHwFraction, s_GlobalHwFraction); + return true; + } + + private static DynamicResolutionHandler GetOrCreateDrsInstanceHandler(Camera camera) + { + if (camera == null) + return null; + + DynamicResolutionHandler instance = null; + var key = camera.GetInstanceID(); + if (!s_CameraInstances.TryGetValue(key, out instance)) + { + //if this camera is not available in the map of cameras lets try creating one. + + //first and foremost, if we exceed the dictionary capacity, lets try and recycle an object that is dead. + if (s_CameraInstances.Count >= CameraDictionaryMaxcCapacity) + { + int recycledInstanceKey = 0; + DynamicResolutionHandler recycledInstance = null; + foreach (var kv in s_CameraInstances) + { + //is this object dead? that is, belongs to a camera that was destroyed? + if (kv.Value.m_OwnerCameraWeakRef == null || !kv.Value.m_OwnerCameraWeakRef.IsAlive) + { + recycledInstance = kv.Value; + recycledInstanceKey = kv.Key; + break; + } + } + + if (recycledInstance != null) + { + instance = recycledInstance; + s_CameraInstances.Remove(recycledInstanceKey); + } + } + + //if we didnt find a dead object, we create one from scratch. + if (instance == null) + { + instance = new DynamicResolutionHandler(); + instance.m_OwnerCameraWeakRef = new WeakReference(camera); + } + else + { + //otherwise, we found a dead object, lets reset it, and have a weak ref to this camera, + //so we can possibly recycle it in the future by checking the camera's weak pointer state. + instance.Reset(); + instance.m_OwnerCameraWeakRef.Target = camera; + } + + s_CameraInstances.Add(key, instance); + } + return instance; + } /// /// Get the instance of the global dynamic resolution handler. /// - public static DynamicResolutionHandler instance { get { return s_Instance; } } + public static DynamicResolutionHandler instance { get { return s_ActiveInstance; } } private DynamicResolutionHandler() { - m_DynamicResMethod = DefaultDynamicResMethod; - filter = DynamicResUpscaleFilter.Bilinear; + Reset(); } // TODO: Eventually we will need to provide a good default implementation for this. @@ -107,6 +200,25 @@ private void ProcessSettings(GlobalDynamicResolutionSettings settings) m_CurrentFraction = fraction; } } + m_CachedSettings = settings; + } + + public Vector2 GetResolvedScale() + { + if (!m_Enabled || !m_CurrentCameraRequest) + { + return new Vector2(1.0f, 1.0f); + } + + float scaleFractionX = m_CurrentFraction; + float scaleFractionY = m_CurrentFraction; + if (!m_ForceSoftwareFallback && type == DynamicResolutionType.Hardware) + { + scaleFractionX = ScalableBufferManager.widthScaleFactor; + scaleFractionY = ScalableBufferManager.heightScaleFactor; + } + + return new Vector2(scaleFractionX, scaleFractionY); } /// @@ -116,8 +228,18 @@ private void ProcessSettings(GlobalDynamicResolutionSettings settings) /// The type of scaler that is used, this is used to indicate the return type of the scaler to the dynamic resolution system. static public void SetDynamicResScaler(PerformDynamicRes scaler, DynamicResScalePolicyType scalerType = DynamicResScalePolicyType.ReturnsMinMaxLerpFactor) { - s_Instance.m_ScalerType = scalerType; - s_Instance.m_DynamicResMethod = scaler; + s_ScalerType = scalerType; + s_DynamicResMethod = scaler; + } + + /// + /// Will clear the currently used camera. Use this function to restore the default instance when UpdateAndUseCamera is called. + /// + public static void ClearSelectedCamera() + { + s_ActiveInstance = s_DefaultInstance; + s_ActiveCameraId = 0; + s_ActiveInstanceDirty = true; } /// @@ -129,6 +251,34 @@ public void SetCurrentCameraRequest(bool cameraRequest) m_CurrentCameraRequest = cameraRequest; } + /// + /// Update the state of the dynamic resolution system for a specific camera. + /// Call this function also to switch context between cameras (will set the current camera as active). + // Passing a null camera has the same effect as calling Update without the camera parameter. + /// + /// Camera used to select a specific instance tied to this DynamicResolutionHandler instance. + /// + /// (optional) The settings that are to be used by the dynamic resolution system. passing null for the settings will result in the last update's settings used. + /// An action that will be called every time the dynamic resolution system triggers a change in resolution. + public static void UpdateAndUseCamera(Camera camera, GlobalDynamicResolutionSettings? settings = null, Action OnResolutionChange = null) + { + int newCameraId; + if (camera == null) + { + s_ActiveInstance = s_DefaultInstance; + newCameraId = 0; + } + else + { + s_ActiveInstance = GetOrCreateDrsInstanceHandler(camera); + newCameraId = camera.GetInstanceID(); + } + + s_ActiveInstanceDirty = newCameraId != s_ActiveCameraId; + s_ActiveCameraId = newCameraId; + s_ActiveInstance.Update(settings.HasValue ? settings.Value : s_ActiveInstance.m_CachedSettings, OnResolutionChange); + } + /// /// Update the state of the dynamic resolution system. /// @@ -138,49 +288,47 @@ public void Update(GlobalDynamicResolutionSettings settings, Action OnResolution { ProcessSettings(settings); - if (!m_Enabled) return; + if (!m_Enabled && !s_ActiveInstanceDirty) + { + s_ActiveInstanceDirty = false; + return; + } if (!m_ForcingRes) { - if (m_ScalerType == DynamicResScalePolicyType.ReturnsMinMaxLerpFactor) + if (s_ScalerType == DynamicResScalePolicyType.ReturnsMinMaxLerpFactor) { - float currLerp = m_DynamicResMethod(); + float currLerp = s_DynamicResMethod(); float lerpFactor = Mathf.Clamp(currLerp, 0.0f, 1.0f); m_CurrentFraction = Mathf.Lerp(m_MinScreenFraction, m_MaxScreenFraction, lerpFactor); } - else if (m_ScalerType == DynamicResScalePolicyType.ReturnsPercentage) + else if (s_ScalerType == DynamicResScalePolicyType.ReturnsPercentage) { - float percentageRequested = Mathf.Max(m_DynamicResMethod(), 5.0f); + float percentageRequested = Mathf.Max(s_DynamicResMethod(), 5.0f); m_CurrentFraction = Mathf.Clamp(percentageRequested / 100.0f, m_MinScreenFraction, m_MaxScreenFraction); } } - if (m_CurrentFraction != m_PrevFraction) - { - m_PrevFraction = m_CurrentFraction; + bool hardwareResolutionChanged = false; + bool softwareResolutionChanged = m_CurrentFraction != m_PrevFraction; - if (!m_ForceSoftwareFallback && type == DynamicResolutionType.Hardware) - { - ScalableBufferManager.ResizeBuffers(m_CurrentFraction, m_CurrentFraction); - } + m_PrevFraction = m_CurrentFraction; - if (OnResolutionChange != null) - OnResolutionChange(); - } - else + if (!m_ForceSoftwareFallback && type == DynamicResolutionType.Hardware) { - // Unity can change the scale factor by itself so we need to trigger the Action if that happens as well. - if (!m_ForceSoftwareFallback && type == DynamicResolutionType.Hardware) + hardwareResolutionChanged = FlushScalableBufferManagerState(); + if (ScalableBufferManager.widthScaleFactor != m_PrevHWScaleWidth || + ScalableBufferManager.heightScaleFactor != m_PrevHWScaleHeight) { - if (ScalableBufferManager.widthScaleFactor != m_PrevHWScaleWidth || - ScalableBufferManager.heightScaleFactor != m_PrevHWScaleHeight) - { - if (OnResolutionChange != null) - OnResolutionChange(); - } + hardwareResolutionChanged = true; } } + + if ((softwareResolutionChanged || hardwareResolutionChanged) && OnResolutionChange != null) + OnResolutionChange(); + + s_ActiveInstanceDirty = false; m_PrevHWScaleWidth = ScalableBufferManager.widthScaleFactor; m_PrevHWScaleHeight = ScalableBufferManager.heightScaleFactor; } @@ -234,6 +382,7 @@ public void ForceSoftwareFallback() /// /// Applies to the passed size the scale imposed by the dynamic resolution system. + /// Note: this function has the side effect of caching the last scale size. /// /// The starting size of the render target that will be scaled by dynamic resolution. /// The parameter size scaled by the dynamic resolution system. @@ -246,21 +395,26 @@ public Vector2Int GetScaledSize(Vector2Int size) return size; } - float scaleFractionX = m_CurrentFraction; - float scaleFractionY = m_CurrentFraction; - if (!m_ForceSoftwareFallback && type == DynamicResolutionType.Hardware) - { - scaleFractionX = ScalableBufferManager.widthScaleFactor; - scaleFractionY = ScalableBufferManager.heightScaleFactor; - } + Vector2Int scaledSize = ApplyScalesOnSize(size); + m_LastScaledSize = scaledSize; + return scaledSize; + } - Vector2Int scaledSize = new Vector2Int(Mathf.CeilToInt(size.x * scaleFractionX), Mathf.CeilToInt(size.y * scaleFractionY)); + /// + /// Applies to the passed size the scale imposed by the dynamic resolution system. + /// Note: this function is pure (has no side effects), this function does not cache the pre-scale size + /// + /// The size to apply the scaling + /// The parameter size scaled by the dynamic resolution system. + public Vector2Int ApplyScalesOnSize(Vector2Int size) + { + Vector2 resolvedScales = GetResolvedScale(); + Vector2Int scaledSize = new Vector2Int(Mathf.CeilToInt(size.x * resolvedScales.x), Mathf.CeilToInt(size.y * resolvedScales.y)); if (m_ForceSoftwareFallback || type != DynamicResolutionType.Hardware) { scaledSize.x += (1 & scaledSize.x); scaledSize.y += (1 & scaledSize.y); } - m_LastScaledSize = scaledSize; return scaledSize; } diff --git a/Runtime/Textures/RTHandleSystem.cs b/Runtime/Textures/RTHandleSystem.cs index 4ef9424..48ff4f9 100644 --- a/Runtime/Textures/RTHandleSystem.cs +++ b/Runtime/Textures/RTHandleSystem.cs @@ -219,8 +219,12 @@ public void SetReferenceSize(int width, int height, MSAASamples msaaSamples, boo if (DynamicResolutionHandler.instance.HardwareDynamicResIsEnabled() && m_HardwareDynamicResRequested) { - float xScale = (float)DynamicResolutionHandler.instance.finalViewport.x / GetMaxWidth(); - float yScale = (float)DynamicResolutionHandler.instance.finalViewport.y / GetMaxHeight(); + Vector2Int maxSize = new Vector2Int(GetMaxWidth(), GetMaxHeight()); + // Making the final scale in 'drs' space, since the final scale must account for rounding pixel values. + var scaledFinalViewport = DynamicResolutionHandler.instance.ApplyScalesOnSize(DynamicResolutionHandler.instance.finalViewport); + var scaledMaxSize = DynamicResolutionHandler.instance.ApplyScalesOnSize(maxSize); + float xScale = (float)scaledFinalViewport.x / (float)scaledMaxSize.x; + float yScale = (float)scaledFinalViewport.y / (float)scaledMaxSize.y; m_RTHandleProperties.rtHandleScale = new Vector4(xScale, yScale, m_RTHandleProperties.rtHandleScale.x, m_RTHandleProperties.rtHandleScale.y); } else diff --git a/Runtime/Volume/VolumeManager.cs b/Runtime/Volume/VolumeManager.cs index a9e205b..821a8af 100644 --- a/Runtime/Volume/VolumeManager.cs +++ b/Runtime/Volume/VolumeManager.cs @@ -30,7 +30,20 @@ public sealed class VolumeManager /// /// The current list of all available types that derive from . /// - public IEnumerable baseComponentTypes { get; private set; } + [Obsolete("Please use baseComponentTypeArray instead.")] + public IEnumerable baseComponentTypes + { + get + { + return baseComponentTypeArray; + } + private set + { + baseComponentTypeArray = value.ToArray(); + } + } + + public Type[] baseComponentTypeArray { get; private set; } // Max amount of layers available in Unity const int k_MaxLayerCount = 32; @@ -75,7 +88,7 @@ public sealed class VolumeManager public VolumeStack CreateStack() { var stack = new VolumeStack(); - stack.Reload(baseComponentTypes); + stack.Reload(baseComponentTypeArray); return stack; } @@ -95,13 +108,13 @@ void ReloadBaseTypes() m_ComponentsDefaultState.Clear(); // Grab all the component types we can find - baseComponentTypes = CoreUtils.GetAllTypesDerivedFrom() - .Where(t => !t.IsAbstract); + baseComponentTypeArray = CoreUtils.GetAllTypesDerivedFrom() + .Where(t => !t.IsAbstract).ToArray(); var flags = System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic; // Keep an instance of each type to be used in a virtual lowest priority global volume // so that we have a default state to fallback to when exiting volumes - foreach (var type in baseComponentTypes) + foreach (var type in baseComponentTypeArray) { type.GetMethod("Init", flags)?.Invoke(null, null); var inst = (VolumeComponent)ScriptableObject.CreateInstance(type); @@ -262,7 +275,7 @@ public void CheckStack(VolumeStack stack) if (components == null) { - stack.Reload(baseComponentTypes); + stack.Reload(baseComponentTypeArray); return; } @@ -270,7 +283,7 @@ public void CheckStack(VolumeStack stack) { if (kvp.Key == null || kvp.Value == null) { - stack.Reload(baseComponentTypes); + stack.Reload(baseComponentTypeArray); return; } } diff --git a/Runtime/Volume/VolumeStack.cs b/Runtime/Volume/VolumeStack.cs index 86f9508..3f556ef 100644 --- a/Runtime/Volume/VolumeStack.cs +++ b/Runtime/Volume/VolumeStack.cs @@ -18,7 +18,7 @@ internal VolumeStack() { } - internal void Reload(IEnumerable baseTypes) + internal void Reload(Type[] baseTypes) { if (components == null) components = new Dictionary(); diff --git a/ShaderLibrary/Color.hlsl b/ShaderLibrary/Color.hlsl index 9c0cb20..60134b1 100644 --- a/ShaderLibrary/Color.hlsl +++ b/ShaderLibrary/Color.hlsl @@ -254,7 +254,7 @@ real YCoCgCheckBoardEdgeFilter(real centerLum, real2 a0, real2 a1, real2 a2, rea } // Converts linear RGB to LMS -real3 LinearToLMS(real3 x) +float3 LinearToLMS(float3 x) // Full float precision to avoid precision artefact when using ACES tonemapping { const real3x3 LIN_2_LMS_MAT = { 3.90405e-1, 5.49941e-1, 8.92632e-3, @@ -265,7 +265,7 @@ real3 LinearToLMS(real3 x) return mul(LIN_2_LMS_MAT, x); } -real3 LMSToLinear(real3 x) +float3 LMSToLinear(float3 x) // Full float precision to avoid precision artefact when using ACES tonemapping { const real3x3 LMS_2_LIN_MAT = { 2.85847e+0, -1.62879e+0, -2.48910e-2, @@ -395,7 +395,7 @@ real LinearToLogC_Precise(real x) return o; } -real3 LinearToLogC(real3 x) +float3 LinearToLogC(float3 x) // Full float precision to avoid precision artefact when using ACES tonemapping { #if USE_PRECISE_LOGC return real3( @@ -418,7 +418,7 @@ real LogCToLinear_Precise(real x) return o; } -real3 LogCToLinear(real3 x) +float3 LogCToLinear(float3 x) // Full float precision to avoid precision artefact when using ACES tonemapping { #if USE_PRECISE_LOGC return real3(