diff --git a/CHANGELOG.md b/CHANGELOG.md index 23c2c23..4b2d91f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,37 +4,25 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [8.2.0] - 2020-07-08 +## [10.1.0] - 2020-10-12 -Version Updated -The version number for this package has increased due to a version update of a related graphics package. - -## [8.1.0] - 2020-04-21 +### Added +- Added context options "Move to Top", "Move to Bottom", "Expand All" and "Collapse All" for volume components. ### Added -- Add tooltips in LookDev's toolbar. +- Added the support of input system V2 ### Fixed -- Fixed issue when LookDev window is opened and the CoreRP Package is updated to a newer version. -- Fixed copy/pasting of Volume Components when loading a new scene -- Fix LookDev's camera button layout. -- Fix LookDev's layout vanishing on domain reload. -- Fixed null reference exception in LookDev when setting the SRP to one not implementing LookDev (case 1245086) -- Fix LookDev's undo/redo on EnvironmentLibrary (case 1234725) -- Fixed a wrong condition in CameraSwitcher, potentially causing out of bound exceptions. -- Fixed issue with blue line in prefabs for volume mode. -- Fix hierarchicalbox gizmo outside facing check in symetry or homothety mode no longer move the center +- Fixed the scene view to scale correctly when hardware dynamic resolution is enabled (case 1158661) +- Fixed game view artifacts on resizing when hardware dynamic resolution was enabled -## [8.0.1] - 2020-02-25 - -Version Updated -The version number for this package has increased due to a version update of a related graphics package. - -## [8.0.0] - 2020-02-25 +## [10.0.0] - 2019-06-10 ### Added - Add rough version of ContextualMenuDispatcher to solve conflict amongst SRP. - Add api documentation for TextureCombiner. +- Add tooltips in LookDev's toolbar. +- Add XRGraphicsAutomatedTests helper class. ### Fixed - Fixed compile errors for platforms with no VR support @@ -55,11 +43,24 @@ The version number for this package has increased due to a version update of a r - Fix LookDev EnvironmentLibrary tab when asset is deleted - Fix LookDev used Cubemap when asset is deleted - Fixed the definition of `rcp()` for GLES2. +- Fixed copy/pasting of Volume Components when loading a new scene - Fix LookDev issue when adding a GameObject containing a Volume into the LookDev's view. - Fixed duplicated entry for com.unity.modules.xr in the runtime asmdef file - Fixed the texture curve being destroyed from another thread than main (case 1211754) - Fixed unreachable code in TextureXR.useTexArray - Fixed GC pressure caused by `VolumeParameter.GetHashCode()` +- Fixed issue when LookDev window is opened and the CoreRP Package is updated to a newer version. +- Fix LookDev's camera button layout. +- Fix LookDev's layout vanishing on domain reload. +- Fixed issue with the shader TransformWorldToHClipDir function computing the wrong result. +- Fixed division by zero in `V_SmithJointGGX` function. +- Fixed null reference exception in LookDev when setting the SRP to one not implementing LookDev (case 1245086) +- Fix LookDev's undo/redo on EnvironmentLibrary (case 1234725) +- Fix a compil error on OpenGL ES2 in directional lightmap sampling shader code +- Fix hierarchicalbox gizmo outside facing check in symetry or homothety mode no longer move the center +- Fix artifacts on Adreno 630 GPUs when using ACES Tonemapping +- Fixed a null ref in the volume component list when there is no volume components in the project. +- Fixed issue with volume manager trying to access a null volume. ### Changed - Restored usage of ENABLE_VR to fix compilation errors on some platforms. @@ -69,6 +70,8 @@ The version number for this package has increased due to a version update of a r - Replaced calls to deprecated PlayerSettings.virtualRealitySupported property. - Enable RWTexture2D, RWTexture2DArray, RWTexture3D in gles 3.1 - Updated macros to be compatible with the new shader preprocessor. +- Updated shaders to be compatible with Microsoft's DXC. +- Changed CommandBufferPool.Get() to create an unnamed CommandBuffer. (No profiling markers) ## [7.1.1] - 2019-09-05 diff --git a/Editor/CoreEditorUtils.cs b/Editor/CoreEditorUtils.cs index e01ed42..7ab3d81 100644 --- a/Editor/CoreEditorUtils.cs +++ b/Editor/CoreEditorUtils.cs @@ -227,6 +227,7 @@ public static void DrawMultipleFields(GUIContent label, SerializedProperty[] ppt public static void DrawSplitter(bool isBoxed = false) { var rect = GUILayoutUtility.GetRect(1f, 1f); + float xMin = rect.xMin; // Splitter rect should be full-width rect.xMin = 0f; @@ -234,7 +235,7 @@ public static void DrawSplitter(bool isBoxed = false) if (isBoxed) { - rect.xMin = EditorGUIUtility.singleLineHeight; + rect.xMin = xMin == 7.0 ? 4.0f : EditorGUIUtility.singleLineHeight; rect.width -= 1; } @@ -299,6 +300,7 @@ public static bool DrawHeaderFoldout(GUIContent title, bool state, bool isBoxed { const float height = 17f; var backgroundRect = GUILayoutUtility.GetRect(1f, height); + float xMin = backgroundRect.xMin; var labelRect = backgroundRect; labelRect.xMin += 16f; @@ -328,7 +330,7 @@ public static bool DrawHeaderFoldout(GUIContent title, bool state, bool isBoxed { labelRect.xMin += 5; foldoutRect.xMin += 5; - backgroundRect.xMin = EditorGUIUtility.singleLineHeight; + backgroundRect.xMin = xMin == 7.0 ? 4.0f : EditorGUIUtility.singleLineHeight; backgroundRect.width -= 1; } diff --git a/Editor/Debugging/DebugUIDrawer.Builtins.cs b/Editor/Debugging/DebugUIDrawer.Builtins.cs index 86489e0..b9f90f2 100644 --- a/Editor/Debugging/DebugUIDrawer.Builtins.cs +++ b/Editor/Debugging/DebugUIDrawer.Builtins.cs @@ -369,7 +369,13 @@ public override bool OnGUI(DebugUI.Widget widget, DebugState state) EditorGUI.BeginChangeCheck(); Enum value = w.GetValue(); var rect = PrepareControlRect(); - value = EditorGUI.EnumFlagsField(rect, EditorGUIUtility.TrTextContent(w.displayName), value); + + // Skip first element (with value 0) because EditorGUI.MaskField adds a 'Nothing' field anyway + var enumNames = new string[w.enumNames.Length - 1]; + for (int i = 0; i < enumNames.Length; i++) + enumNames[i] = w.enumNames[i + 1].text; + var index = EditorGUI.MaskField(rect, EditorGUIUtility.TrTextContent(w.displayName), (int)Convert.ToInt32(value), enumNames); + value = Enum.Parse(value.GetType(), index.ToString()) as Enum; if (EditorGUI.EndChangeCheck()) Apply(w, s, value); @@ -640,4 +646,113 @@ public override void End(DebugUI.Widget widget, DebugState state) EditorGUILayout.EndVertical(); } } + + /// + /// Builtin Drawer for Table Debug Items. + /// + [DebugUIDrawer(typeof(DebugUI.Table))] + public sealed class DebugUIDrawerTable : DebugUIDrawer + { + /// + /// OnGUI implementation for Table DebugUIDrawer. + /// + /// DebugUI Widget. + /// Debug State associated with the Debug Item. + /// The state of the widget. + public override bool OnGUI(DebugUI.Widget widget, DebugState state) + { + var w = Cast(widget); + var header = w.Header; + + // Put some space before the array + PrepareControlRect(EditorGUIUtility.singleLineHeight * 0.5f); + + // Draw an outline around the table + var rect = EditorGUI.IndentedRect(PrepareControlRect(header.height + (w.children.Count + 1) * EditorGUIUtility.singleLineHeight)); + rect = DrawOutline(rect); + + // Compute rects + var headerRect = new Rect(rect.x, rect.y, rect.width, header.height); + var contentRect = new Rect(rect.x, headerRect.yMax, rect.width, rect.height - headerRect.height); + var viewRect = new Rect(contentRect.x, contentRect.y, header.state.widthOfAllVisibleColumns, contentRect.height); + var rowRect = contentRect; + rowRect.height = EditorGUIUtility.singleLineHeight; + viewRect.height -= EditorGUIUtility.singleLineHeight; + + // Show header + header.OnGUI(headerRect, Mathf.Max(w.scroll.x, 0f)); + + // Show array content + w.scroll = GUI.BeginScrollView(contentRect, w.scroll, viewRect); + { + var columns = header.state.columns; + var visible = header.state.visibleColumns; + for (int r = 0; r < w.children.Count; r++) + { + var row = Cast(w.children[r]); + rowRect.x = contentRect.x; + rowRect.width = columns[0].width; + + rowRect.xMin += 2; + rowRect.xMax -= 2; + EditorGUI.LabelField(rowRect, GUIContent.none, EditorGUIUtility.TrTextContent(row.displayName)); + rowRect.xMin -= 2; + rowRect.xMax += 2; + + for (int c = 1; c < visible.Length; c++) + { + rowRect.x += rowRect.width; + rowRect.width = columns[visible[c]].width; + DisplayChild(rowRect, row.children[visible[c] - 1], w.isReadOnly); + } + rowRect.y += rowRect.height; + } + } + GUI.EndScrollView(false); + + return false; + } + + internal Rect DrawOutline(Rect rect) + { + if (Event.current.type != EventType.Repaint) + return rect; + + float size = 1.0f; + var color = EditorGUIUtility.isProSkin ? new Color(0.12f, 0.12f, 0.12f, 1.333f) : new Color(0.6f, 0.6f, 0.6f, 1.333f); + + Color orgColor = GUI.color; + GUI.color = GUI.color * color; + GUI.DrawTexture(new Rect(rect.x, rect.y, rect.width, size), EditorGUIUtility.whiteTexture); + GUI.DrawTexture(new Rect(rect.x, rect.yMax - size, rect.width, size), EditorGUIUtility.whiteTexture); + GUI.DrawTexture(new Rect(rect.x, rect.y + 1, size, rect.height - 2 * size), EditorGUIUtility.whiteTexture); + GUI.DrawTexture(new Rect(rect.xMax - size, rect.y + 1, size, rect.height - 2 * size), EditorGUIUtility.whiteTexture); + + GUI.color = orgColor; + return new Rect(rect.x + size, rect.y + size, rect.width - 2 * size, rect.height - 2 * size); + } + + internal void DisplayChild(Rect rect, DebugUI.Widget child, bool disable) + { + rect.xMin += 2; + rect.xMax -= 2; + if (child.GetType() == typeof(DebugUI.Value)) + { + var widget = Cast(child); + EditorGUI.LabelField(rect, GUIContent.none, EditorGUIUtility.TrTextContent(widget.GetValue().ToString())); + } + else if (child.GetType() == typeof(DebugUI.ColorField)) + { + var widget = Cast(child); + using (new EditorGUI.DisabledScope(disable)) + EditorGUI.ColorField(rect, GUIContent.none, widget.GetValue(), false, widget.showAlpha, widget.hdr); + } + else if (child.GetType() == typeof(DebugUI.BoolField)) + { + var widget = Cast(child); + using (new EditorGUI.DisabledScope(disable)) + EditorGUI.Toggle(rect, GUIContent.none, widget.GetValue()); + } + } + } } diff --git a/Editor/Debugging/DebugWindow.cs b/Editor/Debugging/DebugWindow.cs index 55fe363..a031581 100644 --- a/Editor/Debugging/DebugWindow.cs +++ b/Editor/Debugging/DebugWindow.cs @@ -381,12 +381,6 @@ void OnGUI() DebugManager.instance.Reset(); GUILayout.EndHorizontal(); - // We check if the legacy input manager is not here because we can have both the new and old input system at the same time - // and in this case the debug menu works correctly. -#if !ENABLE_LEGACY_INPUT_MANAGER - EditorGUILayout.HelpBox("The debug menu does not support the new Unity Input package yet. inputs will be disabled in play mode and build.", MessageType.Error); -#endif - using (new EditorGUILayout.HorizontalScope()) { // Side bar diff --git a/Editor/FilterWindow.cs b/Editor/FilterWindow.cs index 1c87826..ecb6a39 100644 --- a/Editor/FilterWindow.cs +++ b/Editor/FilterWindow.cs @@ -232,6 +232,8 @@ void OnDisable() s_FilterWindow = null; } + void OnLostFocus() => Close(); + internal static bool ValidateAddComponentMenuItem() { return true; @@ -374,7 +376,6 @@ internal void OnGUI() if (!m_ActiveParent.WantsFocus) { EditorGUI.FocusTextInControl("ComponentSearch"); - Focus(); } var searchRect = GUILayoutUtility.GetRect(10, 20); @@ -455,8 +456,11 @@ void HandleKeyboard() if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter) { - GoToChild(m_ActiveElement, true); - evt.Use(); + if (m_ActiveElement != null) + { + GoToChild(m_ActiveElement, true); + evt.Use(); + } } // Do these if we're not in search mode diff --git a/Editor/Lighting/IESEngine.cs b/Editor/Lighting/IESEngine.cs new file mode 100644 index 0000000..f2b072d --- /dev/null +++ b/Editor/Lighting/IESEngine.cs @@ -0,0 +1,448 @@ +using System.IO; +using Unity.Collections; +using UnityEditor; +#if UNITY_2020_2_OR_NEWER +using UnityEditor.AssetImporters; +#else +using UnityEditor.Experimental.AssetImporters; +#endif +using UnityEngine; + +namespace UnityEditor.Rendering +{ + // Photometric type coordinate system references: + // https://www.ies.org/product/approved-method-guide-to-goniometer-measurements-and-types-and-photometric-coordinate-systems/ + // https://support.agi32.com/support/solutions/articles/22000209748-type-a-type-b-and-type-c-photometry + /// + /// IES class which is common for the Importers + /// + + [System.Serializable] + public class IESEngine + { + const float k_HalfPi = 0.5f * Mathf.PI; + const float k_TwoPi = 2.0f * Mathf.PI; + + internal IESReader m_iesReader = new IESReader(); + + internal string FileFormatVersion { get => m_iesReader.FileFormatVersion; } + + internal TextureImporterType m_TextureGenerationType = TextureImporterType.Cookie; + + /// + /// setter for the Texture generation Type + /// + public TextureImporterType TextureGenerationType + { + set { m_TextureGenerationType = value; } + } + + /// + /// Method to read the IES File + /// + /// Path to the IES file in the Disk. + /// An error message or warning otherwise null if no error + public string ReadFile(string iesFilePath) + { + if (!File.Exists(iesFilePath)) + { + return "IES file does not exist."; + } + + string errorMessage; + + try + { + errorMessage = m_iesReader.ReadFile(iesFilePath); + } + catch (IOException ioEx) + { + return ioEx.Message; + } + + return errorMessage; + } + + /// + /// Check a keyword + /// + /// A keyword to check if exist. + /// A Keyword if exist inside the internal Dictionary + public string GetKeywordValue(string keyword) + { + return m_iesReader.GetKeywordValue(keyword); + } + + /// + /// Getter (as a string) for the Photometric Type + /// + /// The current Photometric Type + public string GetPhotometricType() + { + switch (m_iesReader.PhotometricType) + { + case 3: // type A + return "Type A"; + case 2: // type B + return "Type B"; + default: // type C + return "Type C"; + } + } + + /// + /// Get the CUrrent Max intensity + /// + /// A pair of the intensity follow by the used unit (candelas or lumens) + public (float, string) GetMaximumIntensity() + { + if (m_iesReader.TotalLumens == -1f) // absolute photometry + { + return (m_iesReader.MaxCandelas, "Candelas"); + } + else + { + return (m_iesReader.TotalLumens, "Lumens"); + } + } + + /// + /// Generated a Cube texture based on the internal PhotometricType + /// + /// Compression parameter requestted. + /// The resquested size. + /// A Cubemap representing this IES + public (string, Texture) GenerateCubeCookie(TextureImporterCompression compression, int textureSize) + { + int width = 2 * textureSize; + int height = 2 * textureSize; + + NativeArray colorBuffer; + + switch (m_iesReader.PhotometricType) + { + case 3: // type A + colorBuffer = BuildTypeACylindricalTexture(width, height); + break; + case 2: // type B + colorBuffer = BuildTypeBCylindricalTexture(width, height); + break; + default: // type C + colorBuffer = BuildTypeCCylindricalTexture(width, height); + break; + } + + return GenerateTexture(m_TextureGenerationType, TextureImporterShape.TextureCube, compression, width, height, colorBuffer); + } + + // Gnomonic projection reference: + // http://speleotrove.com/pangazer/gnomonic_projection.html + /// + /// Generating a 2D Texture of this cookie, using a Gnomonic projection of the bottom of the IES + /// + /// Compression parameter requestted. + /// Cone angle used to performe the Gnomonic projection. + /// The resquested size. + /// Bool to enable or not the Light Attenuation based on the squared distance. + /// A Generated 2D texture doing the projection of the IES using the Gnomonic projection of the bottom half hemisphere with the given 'cone angle' + public (string, Texture) Generate2DCookie(TextureImporterCompression compression, float coneAngle, int textureSize, bool applyLightAttenuation) + { + NativeArray colorBuffer; + + switch (m_iesReader.PhotometricType) + { + case 3: // type A + colorBuffer = BuildTypeAGnomonicTexture(coneAngle, textureSize, applyLightAttenuation); + break; + case 2: // type B + colorBuffer = BuildTypeBGnomonicTexture(coneAngle, textureSize, applyLightAttenuation); + break; + default: // type C + colorBuffer = BuildTypeCGnomonicTexture(coneAngle, textureSize, applyLightAttenuation); + break; + } + + return GenerateTexture(m_TextureGenerationType, TextureImporterShape.Texture2D, compression, textureSize, textureSize, colorBuffer); + } + + private (string, Texture) GenerateCylindricalTexture(TextureImporterCompression compression, int textureSize) + { + int width = 2 * textureSize; + int height = textureSize; + + NativeArray colorBuffer; + + switch (m_iesReader.PhotometricType) + { + case 3: // type A + colorBuffer = BuildTypeACylindricalTexture(width, height); + break; + case 2: // type B + colorBuffer = BuildTypeBCylindricalTexture(width, height); + break; + default: // type C + colorBuffer = BuildTypeCCylindricalTexture(width, height); + break; + } + + return GenerateTexture(TextureImporterType.Default, TextureImporterShape.Texture2D, compression, width, height, colorBuffer); + } + + (string, Texture) GenerateTexture(TextureImporterType type, TextureImporterShape shape, TextureImporterCompression compression, int width, int height, NativeArray colorBuffer) + { + // Default values set by the TextureGenerationSettings constructor can be found in this file on GitHub: + // https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/AssetPipeline/TextureGenerator.bindings.cs + + var settings = new TextureGenerationSettings(type); + + SourceTextureInformation textureInfo = settings.sourceTextureInformation; + textureInfo.containsAlpha = true; + textureInfo.height = height; + textureInfo.width = width; + + TextureImporterSettings textureImporterSettings = settings.textureImporterSettings; + textureImporterSettings.alphaSource = TextureImporterAlphaSource.FromInput; + textureImporterSettings.aniso = 0; + textureImporterSettings.borderMipmap = (textureImporterSettings.textureType == TextureImporterType.Cookie); + textureImporterSettings.filterMode = FilterMode.Bilinear; + textureImporterSettings.generateCubemap = TextureImporterGenerateCubemap.Cylindrical; + textureImporterSettings.mipmapEnabled = false; + textureImporterSettings.npotScale = TextureImporterNPOTScale.None; + textureImporterSettings.readable = true; + textureImporterSettings.sRGBTexture = false; + textureImporterSettings.textureShape = shape; + textureImporterSettings.wrapMode = textureImporterSettings.wrapModeU = textureImporterSettings.wrapModeV = textureImporterSettings.wrapModeW = TextureWrapMode.Clamp; + + TextureImporterPlatformSettings platformSettings = settings.platformSettings; + platformSettings.maxTextureSize = 2048; + platformSettings.resizeAlgorithm = TextureResizeAlgorithm.Bilinear; + platformSettings.textureCompression = compression; + + TextureGenerationOutput output = TextureGenerator.GenerateTexture(settings, colorBuffer); + + if (output.importWarnings.Length > 0) + { + Debug.LogWarning("Cannot properly generate IES texture:\n" + string.Join("\n", output.importWarnings)); + } + + return (output.importInspectorWarnings, output.texture); + } + + NativeArray BuildTypeACylindricalTexture(int width, int height) + { + float stepU = 360f / (width - 1); + float stepV = 180f / (height - 1); + + var textureBuffer = new NativeArray(width * height, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + for (int y = 0; y < height; y++) + { + var slice = new NativeSlice(textureBuffer, y * width, width); + + float latitude = y * stepV - 90f; // in range [-90..+90] degrees + + float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude); + + for (int x = 0; x < width; x++) + { + float longitude = x * stepU - 180f; // in range [-180..+180] degrees + + float horizontalAnglePosition = m_iesReader.ComputeTypeAorBHorizontalAnglePosition(longitude); + + byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / m_iesReader.MaxCandelas) * 255); + slice[x] = new Color32(value, value, value, value); + } + } + + return textureBuffer; + } + + NativeArray BuildTypeBCylindricalTexture(int width, int height) + { + float stepU = k_TwoPi / (width - 1); + float stepV = Mathf.PI / (height - 1); + + var textureBuffer = new NativeArray(width * height, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + for (int y = 0; y < height; y++) + { + var slice = new NativeSlice(textureBuffer, y * width, width); + + float v = y * stepV - k_HalfPi; // in range [-90..+90] degrees + + float sinV = Mathf.Sin(v); + float cosV = Mathf.Cos(v); + + for (int x = 0; x < width; x++) + { + float u = Mathf.PI - x * stepU; // in range [+180..-180] degrees + + float sinU = Mathf.Sin(u); + float cosU = Mathf.Cos(u); + + // Since a type B luminaire is turned on its side, rotate it to make its polar axis horizontal. + float longitude = Mathf.Atan2(sinV, cosU * cosV) * Mathf.Rad2Deg; // in range [-180..+180] degrees + float latitude = Mathf.Asin(-sinU * cosV) * Mathf.Rad2Deg; // in range [-90..+90] degrees + + float horizontalAnglePosition = m_iesReader.ComputeTypeAorBHorizontalAnglePosition(longitude); + float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude); + + byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / m_iesReader.MaxCandelas) * 255); + slice[x] = new Color32(value, value, value, value); + } + } + + return textureBuffer; + } + + NativeArray BuildTypeCCylindricalTexture(int width, int height) + { + float stepU = k_TwoPi / (width - 1); + float stepV = Mathf.PI / (height - 1); + + var textureBuffer = new NativeArray(width * height, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + for (int y = 0; y < height; y++) + { + var slice = new NativeSlice(textureBuffer, y * width, width); + + float v = y * stepV - k_HalfPi; // in range [-90..+90] degrees + + float sinV = Mathf.Sin(v); + float cosV = Mathf.Cos(v); + + for (int x = 0; x < width; x++) + { + float u = Mathf.PI - x * stepU; // in range [+180..-180] degrees + + float sinU = Mathf.Sin(u); + float cosU = Mathf.Cos(u); + + // Since a type C luminaire is generally aimed at nadir, orient it toward +Z at the center of the cylindrical texture. + float longitude = ((Mathf.Atan2(sinU * cosV, sinV) + k_TwoPi) % k_TwoPi) * Mathf.Rad2Deg; // in range [0..360] degrees + float latitude = (Mathf.Asin(-cosU * cosV) + k_HalfPi) * Mathf.Rad2Deg; // in range [0..180] degrees + + float horizontalAnglePosition = m_iesReader.ComputeTypeCHorizontalAnglePosition(longitude); + float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude); + + byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / m_iesReader.MaxCandelas) * 255); + slice[x] = new Color32(value, value, value, value); + } + } + + return textureBuffer; + } + + NativeArray BuildTypeAGnomonicTexture(float coneAngle, int size, bool applyLightAttenuation) + { + float limitUV = Mathf.Tan(0.5f * coneAngle * Mathf.Deg2Rad); + float stepUV = (2 * limitUV) / (size - 3); + + var textureBuffer = new NativeArray(size * size, Allocator.Temp, NativeArrayOptions.ClearMemory); + + // Leave a one-pixel black border around the texture to avoid cookie spilling. + for (int y = 1; y < size - 1; y++) + { + var slice = new NativeSlice(textureBuffer, y * size, size); + + float v = (y - 1) * stepUV - limitUV; + + for (int x = 1; x < size - 1; x++) + { + float u = (x - 1) * stepUV - limitUV; + + float rayLengthSquared = u * u + v * v + 1; + + float longitude = Mathf.Atan(u) * Mathf.Rad2Deg; // in range [-90..+90] degrees + float latitude = Mathf.Asin(v / Mathf.Sqrt(rayLengthSquared)) * Mathf.Rad2Deg; // in range [-90..+90] degrees + + float horizontalAnglePosition = m_iesReader.ComputeTypeCHorizontalAnglePosition(longitude); + float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude); + + // Factor in the light attenuation further from the texture center. + float lightAttenuation = applyLightAttenuation ? rayLengthSquared : 1f; + + byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / (m_iesReader.MaxCandelas * lightAttenuation)) * 255); + slice[x] = new Color32(value, value, value, value); + } + } + + return textureBuffer; + } + + NativeArray BuildTypeBGnomonicTexture(float coneAngle, int size, bool applyLightAttenuation) + { + float limitUV = Mathf.Tan(0.5f * coneAngle * Mathf.Deg2Rad); + float stepUV = (2 * limitUV) / (size - 3); + + var textureBuffer = new NativeArray(size * size, Allocator.Temp, NativeArrayOptions.ClearMemory); + + // Leave a one-pixel black border around the texture to avoid cookie spilling. + for (int y = 1; y < size - 1; y++) + { + var slice = new NativeSlice(textureBuffer, y * size, size); + + float v = (y - 1) * stepUV - limitUV; + + for (int x = 1; x < size - 1; x++) + { + float u = (x - 1) * stepUV - limitUV; + + float rayLengthSquared = u * u + v * v + 1; + + // Since a type B luminaire is turned on its side, U and V are flipped. + float longitude = Mathf.Atan(v) * Mathf.Rad2Deg; // in range [-90..+90] degrees + float latitude = Mathf.Asin(u / Mathf.Sqrt(rayLengthSquared)) * Mathf.Rad2Deg; // in range [-90..+90] degrees + + float horizontalAnglePosition = m_iesReader.ComputeTypeCHorizontalAnglePosition(longitude); + float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude); + + // Factor in the light attenuation further from the texture center. + float lightAttenuation = applyLightAttenuation ? rayLengthSquared : 1f; + + byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / (m_iesReader.MaxCandelas * lightAttenuation)) * 255); + slice[x] = new Color32(value, value, value, value); + } + } + + return textureBuffer; + } + + NativeArray BuildTypeCGnomonicTexture(float coneAngle, int size, bool applyLightAttenuation) + { + float limitUV = Mathf.Tan(0.5f * coneAngle * Mathf.Deg2Rad); + float stepUV = (2 * limitUV) / (size - 3); + + var textureBuffer = new NativeArray(size * size, Allocator.Temp, NativeArrayOptions.ClearMemory); + + // Leave a one-pixel black border around the texture to avoid cookie spilling. + for (int y = 1; y < size - 1; y++) + { + var slice = new NativeSlice(textureBuffer, y * size, size); + + float v = (y - 1) * stepUV - limitUV; + + for (int x = 1; x < size - 1; x++) + { + float u = (x - 1) * stepUV - limitUV; + + float uvLength = Mathf.Sqrt(u * u + v * v); + + float longitude = ((Mathf.Atan2(v, u) - k_HalfPi + k_TwoPi) % k_TwoPi) * Mathf.Rad2Deg; // in range [0..360] degrees + float latitude = Mathf.Atan(uvLength) * Mathf.Rad2Deg; // in range [0..90] degrees + + float horizontalAnglePosition = m_iesReader.ComputeTypeCHorizontalAnglePosition(longitude); + float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude); + + // Factor in the light attenuation further from the texture center. + float lightAttenuation = applyLightAttenuation ? (uvLength * uvLength + 1) : 1f; + + byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / (m_iesReader.MaxCandelas * lightAttenuation)) * 255); + slice[x] = new Color32(value, value, value, value); + } + } + + return textureBuffer; + } + } +} diff --git a/Runtime/RenderGraph/RenderGraphResource.cs.meta b/Editor/Lighting/IESEngine.cs.meta similarity index 80% rename from Runtime/RenderGraph/RenderGraphResource.cs.meta rename to Editor/Lighting/IESEngine.cs.meta index 8e59197..7dec0ba 100644 --- a/Runtime/RenderGraph/RenderGraphResource.cs.meta +++ b/Editor/Lighting/IESEngine.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 55fda6d61a2814c4e972bb0034ccb063 +guid: acb96f1a980ed8a42a75ee14ffb92af1 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Editor/Lighting/IESImporter.cs b/Editor/Lighting/IESImporter.cs new file mode 100644 index 0000000..21b8441 --- /dev/null +++ b/Editor/Lighting/IESImporter.cs @@ -0,0 +1,113 @@ +using System.IO; +using UnityEditor; +using UnityEngine; +#if UNITY_2020_2_OR_NEWER +using UnityEditor.AssetImporters; +#else +using UnityEditor.Experimental.AssetImporters; +#endif + +namespace UnityEditor.Rendering +{ + /// + /// Common class use to share code between implementation of IES Importeres + /// + [System.Serializable] + [ScriptedImporter(1, "ies")] + public partial class IESImporter : ScriptedImporter + { + /// + /// IES Engine + /// + public IESEngine engine = new IESEngine(); + + /// + /// IES Meta data stored in the ies file + /// + public IESMetaData iesMetaData = new IESMetaData(); + + /// + /// Delegate prototype which will be sent by the pipeline implementation of the IES Importer + /// Must be initialized during the creation of the SRP + /// + public static event System.Action createRenderPipelinePrefabLight; + + /// + /// Common method performing the import of the asset + /// + /// Asset importer context. + public override void OnImportAsset(AssetImportContext ctx) + { + engine.TextureGenerationType = TextureImporterType.Default; + + Texture cookieTextureCube = null; + Texture cookieTexture2D = null; + + string iesFilePath = Path.Combine(Path.GetDirectoryName(Application.dataPath), ctx.assetPath); + string errorMessage = engine.ReadFile(iesFilePath); + + if (string.IsNullOrEmpty(errorMessage)) + { + iesMetaData.FileFormatVersion = engine.FileFormatVersion; + iesMetaData.IESPhotometricType = engine.GetPhotometricType(); + iesMetaData.Manufacturer = engine.GetKeywordValue("MANUFAC"); + iesMetaData.LuminaireCatalogNumber = engine.GetKeywordValue("LUMCAT"); + iesMetaData.LuminaireDescription = engine.GetKeywordValue("LUMINAIRE"); + iesMetaData.LampCatalogNumber = engine.GetKeywordValue("LAMPCAT"); + iesMetaData.LampDescription = engine.GetKeywordValue("LAMP"); + + (iesMetaData.IESMaximumIntensity, iesMetaData.IESMaximumIntensityUnit) = engine.GetMaximumIntensity(); + + string warningMessage; + + (warningMessage, cookieTextureCube) = engine.GenerateCubeCookie(iesMetaData.CookieCompression, (int)iesMetaData.iesSize); + if (!string.IsNullOrEmpty(warningMessage)) + { + ctx.LogImportWarning($"Cannot properly generate IES Cube texture: {warningMessage}"); + } + cookieTextureCube.IncrementUpdateCount(); + + (warningMessage, cookieTexture2D) = engine.Generate2DCookie(iesMetaData.CookieCompression, iesMetaData.SpotAngle, (int)iesMetaData.iesSize, iesMetaData.ApplyLightAttenuation); + if (!string.IsNullOrEmpty(warningMessage)) + { + ctx.LogImportWarning($"Cannot properly generate IES 2D texture: {warningMessage}"); + } + cookieTexture2D.IncrementUpdateCount(); + } + else + { + ctx.LogImportError($"Cannot read IES file '{iesFilePath}': {errorMessage}"); + } + + string iesFileName = Path.GetFileNameWithoutExtension(ctx.assetPath); + + var iesObject = ScriptableObject.CreateInstance(); + iesObject.iesMetaData = iesMetaData; + GameObject lightObject = new GameObject(iesFileName); + + lightObject.transform.localEulerAngles = new Vector3(90f, 0f, iesMetaData.LightAimAxisRotation); + + Light light = lightObject.AddComponent(); + light.type = (iesMetaData.PrefabLightType == IESLightType.Point) ? LightType.Point : LightType.Spot; + light.intensity = 1f; // would need a better intensity value formula + light.range = 10f; // would need a better range value formula + light.spotAngle = iesMetaData.SpotAngle; + + ctx.AddObjectToAsset("IES", iesObject); + ctx.SetMainObject(iesObject); + + IESImporter.createRenderPipelinePrefabLight?.Invoke(ctx, iesFileName, iesMetaData.UseIESMaximumIntensity, iesMetaData.IESMaximumIntensityUnit, iesMetaData.IESMaximumIntensity, light, (iesMetaData.PrefabLightType == IESLightType.Point) ? cookieTextureCube : cookieTexture2D); + + if (cookieTextureCube != null) + { + cookieTextureCube.name = iesFileName + "-Cube-IES"; + ctx.AddObjectToAsset(cookieTextureCube.name, cookieTextureCube); + } + if (cookieTexture2D != null) + { + cookieTexture2D.name = iesFileName + "-2D-IES"; + ctx.AddObjectToAsset(cookieTexture2D.name, cookieTexture2D); + } + } + } +} diff --git a/Editor/Lighting/IESImporter.cs.meta b/Editor/Lighting/IESImporter.cs.meta new file mode 100644 index 0000000..11d1edf --- /dev/null +++ b/Editor/Lighting/IESImporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9801397f829a1ed42984340315cbf362 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Lighting/IESImporterEditor.cs b/Editor/Lighting/IESImporterEditor.cs new file mode 100644 index 0000000..90912cd --- /dev/null +++ b/Editor/Lighting/IESImporterEditor.cs @@ -0,0 +1,332 @@ +using System.Reflection; +using UnityEditor; +#if UNITY_2020_2_OR_NEWER +using UnityEditor.AssetImporters; +#else +using UnityEditor.Experimental.AssetImporters; +#endif +using UnityEngine; +using UnityEngine.Rendering; + +namespace UnityEditor.Rendering +{ + /// + /// Common class for IES Importer Editor (currently implemented only on HDRP) + /// + public class IESImporterEditor + { + GUIStyle m_WordWrapStyle = new GUIStyle(); + + SerializedProperty m_FileFormatVersionProp; + SerializedProperty m_IESPhotometricTypeProp; + SerializedProperty m_IESMaximumIntensityProp; + SerializedProperty m_IESMaximumIntensityUnitProp; + + SerializedProperty m_ManufacturerProp; + SerializedProperty m_LuminaireCatalogNumberProp; + SerializedProperty m_LuminaireDescriptionProp; + SerializedProperty m_LampCatalogNumberProp; + SerializedProperty m_LampDescriptionProp; + + SerializedProperty m_PrefabLightTypeProp; + SerializedProperty m_SpotAngleProp; + SerializedProperty m_IESSizeProp; + SerializedProperty m_ApplyLightAttenuationProp; + SerializedProperty m_UseIESMaximumIntensityProp; + SerializedProperty m_CookieCompressionProp; + + /// + /// Property to the aim axis rotation for projection + /// + protected SerializedProperty m_LightAimAxisRotationProp; + + bool m_ShowLuminaireProductInformation = true; + bool m_ShowLightProperties = true; + + /// + /// Object used to setup Preview renderer + /// + protected PreviewRenderUtility m_PreviewRenderUtility = null; + + /// + /// Delegate prototype sent by the specialization of the IESImporterEditor per Render Pipeline + /// + public delegate void LayoutRenderPipelineUseIesMaximumIntensity(); + /// + /// Delegate prototype sent by the specialization of the IESImporterEditor per Render Pipeline + /// + /// Current camera + public delegate void SetupRenderPipelinePreviewCamera(Camera camera); + /// + /// Delegate prototype sent by the specialization of the IESImporterEditor per Render Pipeline + /// + /// Light will be setuped, specialization for a given SRP + public delegate void SetupRenderPipelinePreviewLight(Light light); + /// + /// Delegate prototype sent by the specialization of the IESImporterEditor per Render Pipeline + /// + /// Setup a wall for the preview + public delegate void SetupRenderPipelinePreviewWallRenderer(MeshRenderer wallRenderer); + /// + /// Delegate prototype sent by the specialization of the IESImporterEditor per Render Pipeline + /// + /// Setup a floor for the preview + public delegate void SetupRenderPipelinePreviewFloorRenderer(MeshRenderer floorRenderer); + /// + /// Delegate prototype sent by the specialization of the IESImporterEditor per Render Pipeline + /// + /// Light used to setup the IES + /// Serialized property to the "useIESMaximumIntensity" property + /// Serialized property to the "iesMaximumIntensityUnit" property + /// Serialized property to the "iesMaximumIntensity" property + public delegate void SetupRenderPipelinePreviewLightIntensity(Light light, SerializedProperty useIESMaximumIntensityProp, SerializedProperty iesMaximumIntensityUnitProp, SerializedProperty iesMaximumIntensityProp); + + /// + /// Callback called on the Implemented IESImporterEditor (currently on HDRP Only) + /// + /// Serialized object which can be linked to IESMetadata + public void CommonOnEnable(SerializedProperty serializedObject) + { + m_WordWrapStyle.wordWrap = true; + + m_FileFormatVersionProp = serializedObject.FindPropertyRelative("FileFormatVersion"); + m_IESPhotometricTypeProp = serializedObject.FindPropertyRelative("IESPhotometricType"); + m_IESMaximumIntensityProp = serializedObject.FindPropertyRelative("IESMaximumIntensity"); + m_IESMaximumIntensityUnitProp = serializedObject.FindPropertyRelative("IESMaximumIntensityUnit"); + + m_ManufacturerProp = serializedObject.FindPropertyRelative("Manufacturer"); + m_LuminaireCatalogNumberProp = serializedObject.FindPropertyRelative("LuminaireCatalogNumber"); + m_LuminaireDescriptionProp = serializedObject.FindPropertyRelative("LuminaireDescription"); + m_LampCatalogNumberProp = serializedObject.FindPropertyRelative("LampCatalogNumber"); + m_LampDescriptionProp = serializedObject.FindPropertyRelative("LampDescription"); + + m_PrefabLightTypeProp = serializedObject.FindPropertyRelative("PrefabLightType"); + m_SpotAngleProp = serializedObject.FindPropertyRelative("SpotAngle"); + m_IESSizeProp = serializedObject.FindPropertyRelative("iesSize"); + m_ApplyLightAttenuationProp = serializedObject.FindPropertyRelative("ApplyLightAttenuation"); + m_UseIESMaximumIntensityProp = serializedObject.FindPropertyRelative("UseIESMaximumIntensity"); + m_CookieCompressionProp = serializedObject.FindPropertyRelative("CookieCompression"); + m_LightAimAxisRotationProp = serializedObject.FindPropertyRelative("LightAimAxisRotation"); + } + + /// + /// Callback called on the Implemented IESImporterEditor (currently on HDRP Only) + /// + /// The current specialized scripted importer using the common code + public void CommonOnInspectorGUI(ScriptedImporterEditor scriptedImporter) + { + scriptedImporter.serializedObject.Update(); + + EditorGUILayout.LabelField("File Format Version", m_FileFormatVersionProp.stringValue); + EditorGUILayout.LabelField("Photometric Type", m_IESPhotometricTypeProp.stringValue); + EditorGUILayout.LabelField("Maximum Intensity", $"{m_IESMaximumIntensityProp.floatValue} {m_IESMaximumIntensityUnitProp.stringValue}"); + + if (m_ShowLuminaireProductInformation = EditorGUILayout.Foldout(m_ShowLuminaireProductInformation, "Luminaire Product Information")) + { + EditorGUILayout.LabelField(m_ManufacturerProp.displayName, m_ManufacturerProp.stringValue, m_WordWrapStyle); + EditorGUILayout.LabelField(m_LuminaireCatalogNumberProp.displayName, m_LuminaireCatalogNumberProp.stringValue, m_WordWrapStyle); + EditorGUILayout.LabelField(m_LuminaireDescriptionProp.displayName, m_LuminaireDescriptionProp.stringValue, m_WordWrapStyle); + EditorGUILayout.LabelField(m_LampCatalogNumberProp.displayName, m_LampCatalogNumberProp.stringValue, m_WordWrapStyle); + EditorGUILayout.LabelField(m_LampDescriptionProp.displayName, m_LampDescriptionProp.stringValue, m_WordWrapStyle); + } + + if (m_ShowLightProperties = EditorGUILayout.Foldout(m_ShowLightProperties, "Light and Cookie Properties")) + { + EditorGUILayout.PropertyField(m_PrefabLightTypeProp, new GUIContent("Light Type")); + EditorGUILayout.PropertyField(m_SpotAngleProp); + EditorGUILayout.PropertyField(m_IESSizeProp, new GUIContent("IES Size")); + EditorGUILayout.PropertyField(m_ApplyLightAttenuationProp); + EditorGUILayout.PropertyField(m_CookieCompressionProp, new GUIContent("IES Compression")); + + // Before enabling this feature, more experimentation is needed with the addition of a Volume in the PreviewRenderUtility scene. + EditorGUILayout.PropertyField(m_UseIESMaximumIntensityProp, new GUIContent("Use IES Maximum Intensity")); + + using (new EditorGUILayout.HorizontalScope()) + { + EditorGUILayout.PropertyField(m_LightAimAxisRotationProp, new GUIContent("Aim Axis Rotation")); + + if (GUILayout.Button("Reset", GUILayout.Width(44))) + { + m_LightAimAxisRotationProp.floatValue = -90f; + } + } + } + + scriptedImporter.serializedObject.ApplyModifiedProperties(); + } + + /// + /// Callback called on the Implemented IESImporterEditor (currently on HDRP Only) + /// + public void CommonApply() + { + if (m_PreviewRenderUtility != null) + { + m_PreviewRenderUtility.Cleanup(); + m_PreviewRenderUtility = null; + } + } + + /// + /// Callback called on the Implemented IESImporterEditor (currently on HDRP Only) + /// + /// Delegate provided by the Render pipeline to setup the Preview Camera + /// Delegate provided by the Render pipeline to setup the Preview Light + /// Delegate provided by the Render pipeline to setup the Preview Wall + /// Delegate provided by the Render pipeline to setup the Preview Floor + /// true to specified IES has a Preview + public bool CommonHasPreviewGUI(SetupRenderPipelinePreviewCamera setupRenderPipelinePreviewCamera, + SetupRenderPipelinePreviewLight setupRenderPipelinePreviewLight, + SetupRenderPipelinePreviewWallRenderer setupRenderPipelinePreviewWallRenderer, + SetupRenderPipelinePreviewFloorRenderer setupRenderPipelinePreviewFloorRenderer) + { + if (m_PreviewRenderUtility == null) + { + m_PreviewRenderUtility = new PreviewRenderUtility(); + + m_PreviewRenderUtility.ambientColor = Color.black; + + m_PreviewRenderUtility.camera.fieldOfView = 60f; + m_PreviewRenderUtility.camera.nearClipPlane = 0.1f; + m_PreviewRenderUtility.camera.farClipPlane = 10f; + m_PreviewRenderUtility.camera.transform.localPosition = new Vector3(1.85f, 0.71f, 0f); + m_PreviewRenderUtility.camera.transform.localEulerAngles = new Vector3(15f, -90f, 0f); + + setupRenderPipelinePreviewCamera(m_PreviewRenderUtility.camera); + + m_PreviewRenderUtility.lights[0].type = (m_PrefabLightTypeProp.enumValueIndex == (int)IESLightType.Point) ? LightType.Point : LightType.Spot; + m_PreviewRenderUtility.lights[0].color = Color.white; + m_PreviewRenderUtility.lights[0].intensity = 1f; + m_PreviewRenderUtility.lights[0].range = 10f; + m_PreviewRenderUtility.lights[0].spotAngle = m_SpotAngleProp.floatValue; + m_PreviewRenderUtility.lights[0].transform.localPosition = new Vector3(0.14f, 1f, 0f); + m_PreviewRenderUtility.lights[0].transform.localEulerAngles = new Vector3(90f, 0f, -90f); + + setupRenderPipelinePreviewLight(m_PreviewRenderUtility.lights[0]); + + m_PreviewRenderUtility.lights[1].intensity = 0f; + + GameObject previewWall = GameObject.CreatePrimitive(PrimitiveType.Plane); + previewWall.name = "IESPreviewWall"; + previewWall.hideFlags = HideFlags.HideAndDontSave; + previewWall.transform.localPosition = new Vector3(0f, 4f, 0f); + previewWall.transform.localEulerAngles = new Vector3(0f, 0f, -90f); + previewWall.transform.localScale = new Vector3(1f, 1f, 10f); + MeshRenderer previewWallRenderer = previewWall.GetComponent(); + previewWallRenderer.lightProbeUsage = LightProbeUsage.Off; + previewWallRenderer.reflectionProbeUsage = ReflectionProbeUsage.Off; + previewWallRenderer.material = AssetDatabase.GetBuiltinExtraResource("Default-Material.mat"); + + setupRenderPipelinePreviewWallRenderer(previewWallRenderer); + + m_PreviewRenderUtility.AddSingleGO(previewWall); + + GameObject previewFloor = GameObject.CreatePrimitive(PrimitiveType.Plane); + previewFloor.name = "IESPreviewFloor"; + previewFloor.hideFlags = HideFlags.HideAndDontSave; + previewFloor.transform.localPosition = new Vector3(4f, 0f, 0f); + previewFloor.transform.localEulerAngles = new Vector3(0f, 0f, 0f); + previewFloor.transform.localScale = new Vector3(1f, 1f, 10f); + MeshRenderer previewFloorRenderer = previewFloor.GetComponent(); + previewFloorRenderer.lightProbeUsage = LightProbeUsage.Off; + previewFloorRenderer.reflectionProbeUsage = ReflectionProbeUsage.Off; + previewFloorRenderer.material = AssetDatabase.GetBuiltinExtraResource("Default-Diffuse.mat"); + + setupRenderPipelinePreviewFloorRenderer(previewFloorRenderer); + + m_PreviewRenderUtility.AddSingleGO(previewFloor); + } + + return true; + } + + /// + /// Callback called on the Implemented IESImporterEditor (currently on HDRP Only) + /// + /// The title of the Preview + public GUIContent CommonGetPreviewTitle() + { + return new GUIContent("IES Luminaire Profile"); + } + + /// + /// Callback called on the Implemented IESImporterEditor (currently on HDRP Only) + /// + /// Background of the Preview + /// Rect of the Preview + /// ScriptedImporter targeted + /// Delegate provided by the Rendering Pipeline to setup the Light Intensity + public void CommonOnPreviewGUI(Rect r, GUIStyle background, ScriptedImporter target, + SetupRenderPipelinePreviewLightIntensity setupRenderPipelinePreviewLightIntensity) + { + if (Event.current.type == EventType.Repaint) + { + Texture cookieTexture = null; + Texture previewTexture = null; + + if (m_PrefabLightTypeProp.enumValueIndex == (int)IESLightType.Point) + { + foreach (var subAsset in AssetDatabase.LoadAllAssetRepresentationsAtPath(target.assetPath)) + { + if (subAsset.name.EndsWith("-Cube-IES")) + { + cookieTexture = subAsset as Texture; + break; + } + } + } + else // LightType.Spot + { + foreach (var subAsset in AssetDatabase.LoadAllAssetRepresentationsAtPath(target.assetPath)) + { + if (subAsset.name.EndsWith("-2D-IES")) + { + cookieTexture = subAsset as Texture; + break; + } + } + } + + if (cookieTexture != null) + { + m_PreviewRenderUtility.lights[0].transform.localEulerAngles = new Vector3(90f, 0f, m_LightAimAxisRotationProp.floatValue); + setupRenderPipelinePreviewLightIntensity(m_PreviewRenderUtility.lights[0], m_UseIESMaximumIntensityProp, m_IESMaximumIntensityUnitProp, m_IESMaximumIntensityProp); + m_PreviewRenderUtility.lights[0].cookie = cookieTexture; + m_PreviewRenderUtility.lights[0].type = m_PrefabLightTypeProp.enumValueIndex == (int)IESLightType.Point ? LightType.Point : LightType.Spot; + + m_PreviewRenderUtility.BeginPreview(r, background); + + bool fog = RenderSettings.fog; + Unsupported.SetRenderSettingsUseFogNoDirty(false); + + m_PreviewRenderUtility.camera.Render(); + + Unsupported.SetRenderSettingsUseFogNoDirty(fog); + + previewTexture = m_PreviewRenderUtility.EndPreview(); + } + + if (previewTexture == null) + { + GUI.DrawTexture(r, Texture2D.blackTexture, ScaleMode.StretchToFill, false); + } + else + { + GUI.DrawTexture(r, previewTexture, ScaleMode.ScaleToFit, false); + } + } + } + + /// + /// Callback called on the Implemented IESImporterEditor (currently on HDRP Only) + /// + public void CommonOnDisable() + { + if (m_PreviewRenderUtility != null) + { + m_PreviewRenderUtility.Cleanup(); + m_PreviewRenderUtility = null; + } + } + } +} diff --git a/Editor/Lighting/IESImporterEditor.cs.meta b/Editor/Lighting/IESImporterEditor.cs.meta new file mode 100644 index 0000000..f2b51de --- /dev/null +++ b/Editor/Lighting/IESImporterEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b913694f5cbdba14f8d9ba1e8c028276 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Lighting/IESObject.cs b/Editor/Lighting/IESObject.cs new file mode 100644 index 0000000..83505be --- /dev/null +++ b/Editor/Lighting/IESObject.cs @@ -0,0 +1,171 @@ +using System.IO; +using UnityEditor; +using UnityEngine; + +namespace UnityEditor.Rendering +{ + /// + /// Various possible type for IES, in HDRP for Rectangular light we use spot version + /// + public enum IESLightType + { + /// + /// Point for the IES + /// + Point, + /// + /// Spot for IES (compatible with Area Light) + /// + Spot, + } + + /// + /// Possible values for the IES Size. + /// + public enum IESResolution + { + /// Size 16 + IESResolution16 = 16, + /// Size 32 + IESResolution32 = 32, + /// Size 64 + IESResolution64 = 64, + /// Size 128 + IESResolution128 = 128, + /// Size 256 + IESResolution256 = 256, + /// Size 512 + IESResolution512 = 512, + /// Size 1024 + IESResolution1024 = 1024, + /// Size 2048 + IESResolution2048 = 2048, + /// Size 4096 + IESResolution4096 = 4096 + } + + /// + /// Common class to store metadata of an IES file + /// + [System.Serializable] + public class IESMetaData + { + /// + /// Version of the IES File + /// + public string FileFormatVersion; + /// + /// Total light intensity (in Lumens) stored on the file, usage of it is optional (through the prefab subasset inside the IESObject) + /// + public string IESPhotometricType; + /// + /// IES Max Intensity depends on the various information stored on the IES file + /// + public float IESMaximumIntensity; + /// + /// Unit used to measure the IESMaximumIntensity + /// + public string IESMaximumIntensityUnit; + + // IES luminaire product information. + /// + /// Manufacturer of the current IES file + /// + public string Manufacturer; // IES keyword MANUFAC + /// + /// Luninaire Catalog Number + /// + public string LuminaireCatalogNumber; // IES keyword LUMCAT + /// + /// Luminaire Description + /// + public string LuminaireDescription; // IES keyword LUMINAIRE + /// + /// Lamp Catalog Number + /// + public string LampCatalogNumber; // IES keyword LAMPCAT + /// + /// Lamp Description + /// + public string LampDescription; // IES keyword LAMP + + /// + /// Prefab Light Type (optional to generate the texture used by the renderer) + /// + public IESLightType PrefabLightType = IESLightType.Point; + + /// + /// Spot angle used for the Gnomonic projection of the IES. This parameter will be responsible of the pixel footprint in the 2D Texture + /// https://en.wikipedia.org/wiki/Gnomonic_projection + /// + [Range(1f, 179f)] + public float SpotAngle = 120f; + + /// + /// IES Size of the texture used (same parameter for Point and Spot) + /// + public IESResolution iesSize = IESResolution.IESResolution128; + + /// + /// Enable attenuation used for Spot recommanded to be true, particulary with large angle of "SpotAngle" (cf. Gnomonic Projection) + /// + public bool ApplyLightAttenuation = true; + /// + /// Enable max intensity for the texture generation + /// + public bool UseIESMaximumIntensity = true; + + /// + /// Compression used to generate the texture (CompressedHQ by default (BC7)) + /// + public TextureImporterCompression CookieCompression = TextureImporterCompression.CompressedHQ; + + /// + /// Internally we use 2D projection, we have to choose one axis to project the IES propertly + /// + [Range(-180f, 180f)] + public float LightAimAxisRotation = -90f; + + /// + /// Get Hash describing an unique IES + /// + /// The Hash of the IES Object + public override int GetHashCode() + { + int hash = base.GetHashCode(); + + hash = hash * 23 + FileFormatVersion.GetHashCode(); + hash = hash * 23 + IESPhotometricType.GetHashCode(); + hash = hash * 23 + IESMaximumIntensity.GetHashCode(); + hash = hash * 23 + IESMaximumIntensityUnit.GetHashCode(); + + hash = hash * 23 + Manufacturer.GetHashCode(); + hash = hash * 23 + LuminaireCatalogNumber.GetHashCode(); + hash = hash * 23 + LuminaireDescription.GetHashCode(); + hash = hash * 23 + LampCatalogNumber.GetHashCode(); + hash = hash * 23 + LampDescription.GetHashCode(); + + hash = hash * 23 + PrefabLightType.GetHashCode(); + + hash = hash * 23 + SpotAngle.GetHashCode(); + + hash = hash * 23 + iesSize.GetHashCode(); + hash = hash * 23 + ApplyLightAttenuation.GetHashCode(); + hash = hash * 23 + UseIESMaximumIntensity.GetHashCode(); + + return hash; + } + } + + /// + /// IESObject manipulated internally (in the UI) + /// + [System.Serializable] + public class IESObject : ScriptableObject + { + /// + /// Metadata of the IES file + /// + public IESMetaData iesMetaData = new IESMetaData(); + } +} diff --git a/Editor/Lighting/IESObject.cs.meta b/Editor/Lighting/IESObject.cs.meta new file mode 100644 index 0000000..27183ef --- /dev/null +++ b/Editor/Lighting/IESObject.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 901e3026412fba146ab1b63bfc229b78 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 5abbb538d495cae43bdd23328a40f90b, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Lighting/IESReader.cs b/Editor/Lighting/IESReader.cs new file mode 100644 index 0000000..c72c8a6 --- /dev/null +++ b/Editor/Lighting/IESReader.cs @@ -0,0 +1,541 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text.RegularExpressions; +using UnityEngine; + +namespace UnityEditor.Rendering +{ + /// + /// Class to Parse IES File + /// + [System.Serializable] + public class IESReader + { + string m_FileFormatVersion; + /// + /// Version of the IES File + /// + public string FileFormatVersion + { + get { return m_FileFormatVersion; } + } + + float m_TotalLumens; + /// + /// Total light intensity (in Lumens) stored on the file, usage of it is optional (through the prefab subasset inside the IESObject) + /// + public float TotalLumens + { + get { return m_TotalLumens; } + } + + float m_MaxCandelas; + /// + /// Maximum of Candela in the IES File + /// + public float MaxCandelas + { + get { return m_MaxCandelas; } + } + + int m_PhotometricType; + + /// + /// Type of Photometric light in the IES file, varying per IES-Type and version + /// + public int PhotometricType + { + get { return m_PhotometricType; } + } + + Dictionary m_KeywordDictionary = new Dictionary(); + + int m_VerticalAngleCount; + int m_HorizontalAngleCount; + float[] m_VerticalAngles; + float[] m_HorizontalAngles; + float[] m_CandelaValues; + + float m_MinDeltaVerticalAngle; + float m_MinDeltaHorizontalAngle; + float m_FirstHorizontalAngle; + float m_LastHorizontalAngle; + + // File format references: + // https://www.ies.org/product/standard-file-format-for-electronic-transfer-of-photometric-data/ + // http://lumen.iee.put.poznan.pl/kw/iesna.txt + // https://seblagarde.wordpress.com/2014/11/05/ies-light-format-specification-and-reader/ + /// + /// Main function to read the file + /// + /// The path to the IES File on disk. + /// Return the error during the import otherwise null if no error + public string ReadFile(string iesFilePath) + { + + using (var iesReader = File.OpenText(iesFilePath)) + { + string versionLine = iesReader.ReadLine(); + + if (versionLine == null) + { + return "Premature end of file (empty file)."; + } + + switch (versionLine.Trim()) + { + case "IESNA91": + m_FileFormatVersion = "LM-63-1991"; + break; + case "IESNA:LM-63-1995": + m_FileFormatVersion = "LM-63-1995"; + break; + case "IESNA:LM-63-2002": + m_FileFormatVersion = "LM-63-2002"; + break; + case "IES:LM-63-2019": + m_FileFormatVersion = "LM-63-2019"; + break; + default: + m_FileFormatVersion = "LM-63-1986"; + break; + } + + var keywordRegex = new Regex(@"\s*\[(?\w+)\]\s*(?.*)", RegexOptions.Compiled); + var tiltRegex = new Regex(@"TILT=(?.*)", RegexOptions.Compiled); + + string currentKeyword = string.Empty; + + for (string keywordLine = (m_FileFormatVersion == "LM-63-1986") ? versionLine : iesReader.ReadLine(); true; keywordLine = iesReader.ReadLine()) + { + if (keywordLine == null) + { + return "Premature end of file (missing TILT=NONE)."; + } + + if (string.IsNullOrWhiteSpace(keywordLine)) + { + continue; + } + + Match keywordMatch = keywordRegex.Match(keywordLine); + + if (keywordMatch.Success) + { + string keyword = keywordMatch.Groups["keyword"].Value; + string data = keywordMatch.Groups["data"].Value.Trim(); + + if (keyword == currentKeyword || keyword == "MORE") + { + m_KeywordDictionary[currentKeyword] += $" {data}"; + } + else + { + // Many separate occurrences of keyword OTHER will need to be handled properly once exposed in the inspector. + currentKeyword = keyword; + m_KeywordDictionary[currentKeyword] = data; + } + + continue; + } + + Match tiltMatch = tiltRegex.Match(keywordLine); + + if (tiltMatch.Success) + { + string data = tiltMatch.Groups["data"].Value.Trim(); + + if (data == "NONE") + { + break; + } + + return $"TILT format not supported: TILT={data}"; + } + } + + string[] iesDataTokens = Regex.Split(iesReader.ReadToEnd().Trim(), @"[\s,]+"); + var iesDataTokenEnumerator = iesDataTokens.GetEnumerator(); + string iesDataToken; + + + if (iesDataTokens.Length == 1 && string.IsNullOrWhiteSpace(iesDataTokens[0])) + { + return "Premature end of file (missing IES data)."; + } + + if (!iesDataTokenEnumerator.MoveNext()) + { + return "Premature end of file (missing lamp count value)."; + } + + int lampCount; + iesDataToken = iesDataTokenEnumerator.Current.ToString(); + if (!int.TryParse(iesDataToken, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out lampCount)) + { + return $"Invalid lamp count value: {iesDataToken}"; + } + if (lampCount < 1) lampCount = 1; + + if (!iesDataTokenEnumerator.MoveNext()) + { + return "Premature end of file (missing lumens per lamp value)."; + } + + float lumensPerLamp; + iesDataToken = iesDataTokenEnumerator.Current.ToString(); + if (!float.TryParse(iesDataToken, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out lumensPerLamp)) + { + return $"Invalid lumens per lamp value: {iesDataToken}"; + } + m_TotalLumens = (lumensPerLamp < 0f) ? -1f : lampCount * lumensPerLamp; + + if (!iesDataTokenEnumerator.MoveNext()) + { + return "Premature end of file (missing candela multiplier value)."; + } + + float candelaMultiplier; + iesDataToken = iesDataTokenEnumerator.Current.ToString(); + if (!float.TryParse(iesDataToken, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out candelaMultiplier)) + { + return $"Invalid candela multiplier value: {iesDataToken}"; + } + if (candelaMultiplier < 0f) candelaMultiplier = 0f; + + if (!iesDataTokenEnumerator.MoveNext()) + { + return "Premature end of file (missing vertical angle count value)."; + } + + iesDataToken = iesDataTokenEnumerator.Current.ToString(); + if (!int.TryParse(iesDataToken, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out m_VerticalAngleCount)) + { + return $"Invalid vertical angle count value: {iesDataToken}"; + } + if (m_VerticalAngleCount < 1) + { + return $"Invalid number of vertical angles: {m_VerticalAngleCount}"; + } + + if (!iesDataTokenEnumerator.MoveNext()) + { + return "Premature end of file (missing horizontal angle count value)."; + } + + iesDataToken = iesDataTokenEnumerator.Current.ToString(); + if (!int.TryParse(iesDataToken, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out m_HorizontalAngleCount)) + { + return $"Invalid horizontal angle count value: {iesDataToken}"; + } + if (m_HorizontalAngleCount < 1) + { + return $"Invalid number of horizontal angles: {m_HorizontalAngleCount}"; + } + + if (!iesDataTokenEnumerator.MoveNext()) + { + return "Premature end of file (missing photometric type value)."; + } + + iesDataToken = iesDataTokenEnumerator.Current.ToString(); + if (!int.TryParse(iesDataToken, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out m_PhotometricType)) + { + return $"Invalid photometric type value: {iesDataToken}"; + } + if (m_PhotometricType < 1 || m_PhotometricType > 3) + { + return $"Invalid photometric type: {m_PhotometricType}"; + } + + // Skip luminous dimension unit type. + if (!iesDataTokenEnumerator.MoveNext()) + { + return "Premature end of file (missing luminous dimension unit type value)."; + } + + // Skip luminous dimension width. + if (!iesDataTokenEnumerator.MoveNext()) + { + return "Premature end of file (missing luminous dimension width value)."; + } + + // Skip luminous dimension length. + if (!iesDataTokenEnumerator.MoveNext()) + { + return "Premature end of file (missing luminous dimension length value)."; + } + + // Skip luminous dimension height. + if (!iesDataTokenEnumerator.MoveNext()) + { + return "Premature end of file (missing luminous dimension height value)."; + } + + if (!iesDataTokenEnumerator.MoveNext()) + { + return "Premature end of file (missing ballast factor value)."; + } + + float ballastFactor; + iesDataToken = iesDataTokenEnumerator.Current.ToString(); + if (!float.TryParse(iesDataToken, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out ballastFactor)) + { + return $"Invalid ballast factor value: {iesDataToken}"; + } + if (ballastFactor < 0f) ballastFactor = 0f; + + // Skip future use. + if (!iesDataTokenEnumerator.MoveNext()) + { + return "Premature end of file (missing future use value)."; + } + + // Skip input watts. + if (!iesDataTokenEnumerator.MoveNext()) + { + return "Premature end of file (missing input watts value)."; + } + + m_VerticalAngles = new float[m_VerticalAngleCount]; + float previousVerticalAngle = float.MinValue; + + m_MinDeltaVerticalAngle = 180f; + + for (int v = 0; v < m_VerticalAngleCount; ++v) + { + if (!iesDataTokenEnumerator.MoveNext()) + { + return "Premature end of file (missing vertical angle values)."; + } + + float angle; + iesDataToken = iesDataTokenEnumerator.Current.ToString(); + if (!float.TryParse(iesDataToken, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out angle)) + { + return $"Invalid vertical angle value: {iesDataToken}"; + } + + if (angle <= previousVerticalAngle) + { + return $"Vertical angles are not in ascending order near: {angle}"; + } + + float deltaVerticalAngle = angle - previousVerticalAngle; + if (deltaVerticalAngle < m_MinDeltaVerticalAngle) + { + m_MinDeltaVerticalAngle = deltaVerticalAngle; + } + + m_VerticalAngles[v] = previousVerticalAngle = angle; + } + + m_HorizontalAngles = new float[m_HorizontalAngleCount]; + float previousHorizontalAngle = float.MinValue; + + m_MinDeltaHorizontalAngle = 360f; + + for (int h = 0; h < m_HorizontalAngleCount; ++h) + { + if (!iesDataTokenEnumerator.MoveNext()) + { + return "Premature end of file (missing horizontal angle values)."; + } + + float angle; + iesDataToken = iesDataTokenEnumerator.Current.ToString(); + if (!float.TryParse(iesDataToken, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out angle)) + { + return $"Invalid horizontal angle value: {iesDataToken}"; + } + + if (angle <= previousHorizontalAngle) + { + return $"Horizontal angles are not in ascending order near: {angle}"; + } + + float deltaHorizontalAngle = angle - previousHorizontalAngle; + if (deltaHorizontalAngle < m_MinDeltaHorizontalAngle) + { + m_MinDeltaHorizontalAngle = deltaHorizontalAngle; + } + + m_HorizontalAngles[h] = previousHorizontalAngle = angle; + } + + m_FirstHorizontalAngle = m_HorizontalAngles[0]; + m_LastHorizontalAngle = m_HorizontalAngles[m_HorizontalAngleCount - 1]; + + m_CandelaValues = new float[m_HorizontalAngleCount * m_VerticalAngleCount]; + m_MaxCandelas = 0f; + + for (int h = 0; h < m_HorizontalAngleCount; ++h) + { + for (int v = 0; v < m_VerticalAngleCount; ++v) + { + if (!iesDataTokenEnumerator.MoveNext()) + { + return "Premature end of file (missing candela values)."; + } + + float value; + iesDataToken = iesDataTokenEnumerator.Current.ToString(); + if (!float.TryParse(iesDataToken, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out value)) + { + return $"Invalid candela value: {iesDataToken}"; + } + value *= candelaMultiplier * ballastFactor; + + m_CandelaValues[h * m_VerticalAngleCount + v] = value; + + if (value > m_MaxCandelas) + { + m_MaxCandelas = value; + } + } + } + } + + return null; + } + + internal string GetKeywordValue(string keyword) + { + return m_KeywordDictionary.ContainsKey(keyword) ? m_KeywordDictionary[keyword] : string.Empty; + } + + internal int GetMinVerticalSampleCount() + { + if (m_PhotometricType == 2) // type B + { + // Factor in the 90 degree rotation that will be done when building the cylindrical texture. + return 1 + (int)Mathf.Ceil(360 / m_MinDeltaHorizontalAngle); // 360 is 2 * 180 degrees + } + else // type A or C + { + return 1 + (int)Mathf.Ceil(360 / m_MinDeltaVerticalAngle); // 360 is 2 * 180 degrees + } + } + + internal int GetMinHorizontalSampleCount() + { + switch (m_PhotometricType) + { + case 3: // type A + return 1 + (int)Mathf.Ceil(720 / m_MinDeltaHorizontalAngle); // 720 is 2 * 360 degrees + case 2: // type B + // Factor in the 90 degree rotation that will be done when building the cylindrical texture. + return 1 + (int)Mathf.Ceil(720 / m_MinDeltaVerticalAngle); // 720 is 2 * 360 degrees + default: // type C + // Factor in the 90 degree rotation that will be done when building the cylindrical texture. + return 1 + (int)Mathf.Ceil(720 / Mathf.Min(m_MinDeltaHorizontalAngle, m_MinDeltaVerticalAngle)); // 720 is 2 * 360 degrees + } + } + + internal float ComputeVerticalAnglePosition(float angle) + { + return ComputeAnglePosition(angle, m_VerticalAngles); + } + + internal float ComputeTypeAorBHorizontalAnglePosition(float angle) // angle in range [-180..+180] degrees + { + return ComputeAnglePosition(((m_FirstHorizontalAngle == 0f) ? Mathf.Abs(angle) : angle), m_HorizontalAngles); + } + + internal float ComputeTypeCHorizontalAnglePosition(float angle) // angle in range [0..360] degrees + { + switch (m_LastHorizontalAngle) + { + case 0f: // the luminaire is assumed to be laterally symmetric in all planes + angle = 0f; + break; + case 90f: // the luminaire is assumed to be symmetric in each quadrant + angle = 90f - Mathf.Abs(Mathf.Abs(angle - 180f) - 90f); + break; + case 180f: // the luminaire is assumed to be symmetric about the 0 to 180 degree plane + angle = 180f - Mathf.Abs(angle - 180f); + break; + default: // the luminaire is assumed to exhibit no lateral symmetry + break; + } + + return ComputeAnglePosition(angle, m_HorizontalAngles); + } + + internal float ComputeAnglePosition(float value, float[] angles) + { + int start = 0; + int end = angles.Length - 1; + + if (value < angles[start]) + { + return start; + } + + if (value > angles[end]) + { + return end; + } + + while (start < end) + { + int index = (start + end + 1) / 2; + + float angle = angles[index]; + + if (value >= angle) + { + start = index; + } + else + { + end = index - 1; + } + } + + float leftValue = angles[start]; + float fraction = 0f; + + if (start + 1 < angles.Length) + { + float rightValue = angles[start + 1]; + float deltaValue = rightValue - leftValue; + + if (deltaValue > 0.0001f) + { + fraction = (value - leftValue) / deltaValue; + } + } + + return start + fraction; + } + + internal float InterpolateBilinear(float x, float y) + { + int ix = (int)Mathf.Floor(x); + int iy = (int)Mathf.Floor(y); + + float fractionX = x - ix; + float fractionY = y - iy; + + float p00 = InterpolatePoint(ix + 0, iy + 0); + float p10 = InterpolatePoint(ix + 1, iy + 0); + float p01 = InterpolatePoint(ix + 0, iy + 1); + float p11 = InterpolatePoint(ix + 1, iy + 1); + + float p0 = Mathf.Lerp(p00, p01, fractionY); + float p1 = Mathf.Lerp(p10, p11, fractionY); + + return Mathf.Lerp(p0, p1, fractionX); + } + + internal float InterpolatePoint(int x, int y) + { + x %= m_HorizontalAngles.Length; + y %= m_VerticalAngles.Length; + + return m_CandelaValues[y + x * m_VerticalAngles.Length]; + } + } +} diff --git a/Editor/Lighting/IESReader.cs.meta b/Editor/Lighting/IESReader.cs.meta new file mode 100644 index 0000000..f43da84 --- /dev/null +++ b/Editor/Lighting/IESReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 28e75f38a2dd403448c94e1663867a24 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Lighting/Icons.meta b/Editor/Lighting/Icons.meta new file mode 100644 index 0000000..b7a0d9a --- /dev/null +++ b/Editor/Lighting/Icons.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 77235ff89f4625d44ac5ad118d1913fa +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Lighting/Icons/IES Profile.png b/Editor/Lighting/Icons/IES Profile.png new file mode 100644 index 0000000..ed29ade Binary files /dev/null and b/Editor/Lighting/Icons/IES Profile.png differ diff --git a/Editor/Lighting/Icons/IES Profile.png.meta b/Editor/Lighting/Icons/IES Profile.png.meta new file mode 100644 index 0000000..526f9ff --- /dev/null +++ b/Editor/Lighting/Icons/IES Profile.png.meta @@ -0,0 +1,96 @@ +fileFormatVersion: 2 +guid: 0283fe60f65159b4a86ebabf444e23f9 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: -1 + mipBias: -100 + wrapU: -1 + wrapV: -1 + wrapW: -1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Lighting/Icons/IES Profile2.png b/Editor/Lighting/Icons/IES Profile2.png new file mode 100644 index 0000000..3503917 Binary files /dev/null and b/Editor/Lighting/Icons/IES Profile2.png differ diff --git a/Editor/Lighting/Icons/IES Profile2.png.meta b/Editor/Lighting/Icons/IES Profile2.png.meta new file mode 100644 index 0000000..f17cea9 --- /dev/null +++ b/Editor/Lighting/Icons/IES Profile2.png.meta @@ -0,0 +1,96 @@ +fileFormatVersion: 2 +guid: 5abbb538d495cae43bdd23328a40f90b +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: -1 + mipBias: -100 + wrapU: -1 + wrapV: -1 + wrapW: -1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/LookDev/Compositor.shader b/Editor/LookDev/Compositor.shader index 826a88d..9ddfb00 100644 --- a/Editor/LookDev/Compositor.shader +++ b/Editor/LookDev/Compositor.shader @@ -391,7 +391,7 @@ Shader "Hidden/LookDev/Compositor" #pragma target 3.0 float4 frag(float2 texcoord : TEXCOORD0, - UNITY_VPOS_TYPE vpos : VPOS) : COLOR + UNITY_VPOS_TYPE vpos : VPOS) : SV_Target { float4 color = float4(ComputeColor(_Tex0MainView, _Tex0Shadows, ShadowMultiplier0, _ShadowColor0, texcoord) * exp2(ExposureValue1), 1.0); color.rgb = ApplyToneMap(color.rgb); @@ -409,7 +409,7 @@ Shader "Hidden/LookDev/Compositor" #pragma target 3.0 float4 frag(float2 texcoord : TEXCOORD0, - UNITY_VPOS_TYPE vpos : VPOS) : COLOR + UNITY_VPOS_TYPE vpos : VPOS) : SV_Target { float4 color = float4(ComputeColor(_Tex1MainView, _Tex1Shadows, ShadowMultiplier1, _ShadowColor1, texcoord) * exp2(ExposureValue2), 1.0); color.rgb = ApplyToneMap(color.rgb); @@ -427,7 +427,7 @@ Shader "Hidden/LookDev/Compositor" #pragma target 3.0 float4 frag(float2 texcoord : TEXCOORD0, - UNITY_VPOS_TYPE vpos : VPOS) : COLOR + UNITY_VPOS_TYPE vpos : VPOS) : SV_Target { float3 color1 = ComputeColor(_Tex0MainView, _Tex0Shadows, ShadowMultiplier0, _ShadowColor0, texcoord) * exp2(ExposureValue1); float3 color2 = ComputeColor(_Tex1MainView, _Tex1Shadows, ShadowMultiplier1, _ShadowColor1, texcoord) * exp2(ExposureValue2); diff --git a/Editor/LookDev/CubeToLatlong.shader b/Editor/LookDev/CubeToLatlong.shader index 745faea..51be6a7 100644 --- a/Editor/LookDev/CubeToLatlong.shader +++ b/Editor/LookDev/CubeToLatlong.shader @@ -45,7 +45,7 @@ Shader "Hidden/LookDev/CubeToLatlong" float4 frag( float2 texcoord : TEXCOORD0, UNITY_VPOS_TYPE vpos : VPOS - ) : COLOR + ) : SV_Target { float2 texCoord = texcoord.xy; float theta = texCoord.y * UNITY_PI; diff --git a/Editor/LookDev/DisplayWindow.EnvironmentLibrarySidePanel.cs b/Editor/LookDev/DisplayWindow.EnvironmentLibrarySidePanel.cs index dd10f59..b042666 100644 --- a/Editor/LookDev/DisplayWindow.EnvironmentLibrarySidePanel.cs +++ b/Editor/LookDev/DisplayWindow.EnvironmentLibrarySidePanel.cs @@ -250,40 +250,47 @@ void ScrollToEnd(int attemptRemaining = 5) void RefreshLibraryDisplay() { - int itemMax = LookDev.currentContext.environmentLibrary?.Count ?? 0; - if (itemMax == 0 || m_EnvironmentList.selectedIndex == -1) - { - m_EnvironmentInspector.style.visibility = Visibility.Hidden; - m_EnvironmentInspector.style.height = 0; - } - else - { - m_EnvironmentInspector.style.visibility = Visibility.Visible; - m_EnvironmentInspector.style.height = new StyleLength(StyleKeyword.Auto); - } - var items = new List(itemMax); - for (int i = 0; i < itemMax; i++) - items.Add(i); - m_EnvironmentList.itemsSource = items; - if (LookDev.currentContext.environmentLibrary == null) - { - m_EnvironmentList - .Q(className: "unity-scroll-view__vertical-scroller") - .Q("unity-dragger") - .style.visibility = Visibility.Hidden; - m_EnvironmentListToolbar.style.visibility = Visibility.Hidden; - m_NoEnvironmentList.style.display = DisplayStyle.Flex; - } - else + if (m_LibraryField != null) + m_LibraryField.SetValueWithoutNotify(LookDev.currentContext.environmentLibrary); + + if (m_EnvironmentInspector != null && m_EnvironmentList != null) { - m_EnvironmentList - .Q(className: "unity-scroll-view__vertical-scroller") - .Q("unity-dragger") - .style.visibility = itemMax == 0 - ? Visibility.Hidden - : Visibility.Visible; - m_EnvironmentListToolbar.style.visibility = Visibility.Visible; - m_NoEnvironmentList.style.display = DisplayStyle.None; + int itemMax = LookDev.currentContext.environmentLibrary?.Count ?? 0; + if (itemMax == 0 || m_EnvironmentList.selectedIndex == -1) + { + m_EnvironmentInspector.style.visibility = Visibility.Hidden; + m_EnvironmentInspector.style.height = 0; + } + else + { + m_EnvironmentInspector.style.visibility = Visibility.Visible; + m_EnvironmentInspector.style.height = new StyleLength(StyleKeyword.Auto); + } + + var items = new List(itemMax); + for (int i = 0; i < itemMax; i++) + items.Add(i); + m_EnvironmentList.itemsSource = items; + if (LookDev.currentContext.environmentLibrary == null) + { + m_EnvironmentList + .Q(className: "unity-scroll-view__vertical-scroller") + .Q("unity-dragger") + .style.visibility = Visibility.Hidden; + m_EnvironmentListToolbar.style.visibility = Visibility.Hidden; + m_NoEnvironmentList.style.display = DisplayStyle.Flex; + } + else + { + m_EnvironmentList + .Q(className: "unity-scroll-view__vertical-scroller") + .Q("unity-dragger") + .style.visibility = itemMax == 0 + ? Visibility.Hidden + : Visibility.Visible; + m_EnvironmentListToolbar.style.visibility = Visibility.Visible; + m_NoEnvironmentList.style.display = DisplayStyle.None; + } } } diff --git a/Editor/LookDev/DisplayWindow.cs b/Editor/LookDev/DisplayWindow.cs index f421aa8..0d499f7 100644 --- a/Editor/LookDev/DisplayWindow.cs +++ b/Editor/LookDev/DisplayWindow.cs @@ -216,30 +216,54 @@ SidePanel sidePanel StyleSheet styleSheet = null; StyleSheet styleSheetLight = null; - void OnEnable() + void ReloadStyleSheets() { - //Stylesheet - // Try to load stylesheet. Timing can be odd while upgrading packages (case 1219692). - // In this case, it will be fixed in OnGUI. Though it can spawn error while reimporting assets. - // Waiting for filter on stylesheet (case 1228706) to remove last error. - if (styleSheet == null || styleSheet.Equals(null)) + if(styleSheet == null || styleSheet.Equals(null)) { styleSheet = AssetDatabase.LoadAssetAtPath(Style.k_uss); - if (styleSheet != null && !styleSheet.Equals(null)) - rootVisualElement.styleSheets.Add(styleSheet); + if(styleSheet == null || styleSheet.Equals(null)) + { + //Debug.LogWarning("[LookDev] Could not load Stylesheet."); + return; + } } - if (!EditorGUIUtility.isProSkin && styleSheetLight != null && !styleSheetLight.Equals(null)) + + if(!rootVisualElement.styleSheets.Contains(styleSheet)) + rootVisualElement.styleSheets.Add(styleSheet); + + //Additively load Light Skin + if(!EditorGUIUtility.isProSkin) { - styleSheetLight = AssetDatabase.LoadAssetAtPath(Style.k_uss_personal_overload); - if (styleSheetLight != null && !styleSheetLight.Equals(null)) + if(styleSheetLight == null || styleSheetLight.Equals(null)) + { + styleSheetLight = AssetDatabase.LoadAssetAtPath(Style.k_uss_personal_overload); + if(styleSheetLight == null || styleSheetLight.Equals(null)) + { + //Debug.LogWarning("[LookDev] Could not load Light skin."); + return; + } + } + + if(!rootVisualElement.styleSheets.Contains(styleSheetLight)) rootVisualElement.styleSheets.Add(styleSheetLight); } + } + + void OnEnable() + { + //Stylesheet + // Try to load stylesheet. Timing can be odd while upgrading packages (case 1219692). + // In this case, it will be fixed in OnGUI. Though it can spawn error while reimporting assets. + // Waiting for filter on stylesheet (case 1228706) to remove last error. + // On Editor Skin change, OnEnable is called and stylesheets need to be reloaded (case 1278802). + if(EditorApplication.isUpdating) + ReloadStyleSheets(); //Call the open function to configure LookDev // in case the window where open when last editor session finished. // (Else it will open at start and has nothing to display). if (!LookDev.open) - LookDev.Open(); + LookDev.Initialize(this); titleContent = Style.k_WindowTitleAndIcon; @@ -625,6 +649,19 @@ IStyle GetEnvironmentContenairDraggerStyle() } } + void Update() + { + // [case 1245086] Guard in case the SRP asset is set to null (or to a not supported SRP) when the lookdev window is already open + // Note: After an editor reload, we might get a null OnUpdateRequestedInternal and null SRP for a couple of frames, hence the check. + if (!LookDev.supported && OnUpdateRequestedInternal != null) + { + // Print an error and close the Lookdev window (to avoid spamming the console) + Debug.LogError($"LookDev is not supported by this Scriptable Render Pipeline: " + + (RenderPipelineManager.currentPipeline == null ? "No SRP in use" : RenderPipelineManager.currentPipeline.ToString())); + LookDev.Close(); + } + } + void OnGUI() { //Stylesheet @@ -681,25 +718,11 @@ void OnGUI() // rootVisualElement.styleSheets.Add(styleSheetLight); //} } - else - { - //deal with missing style when domain reload... - if (!rootVisualElement.styleSheets.Contains(styleSheet)) - rootVisualElement.styleSheets.Add(styleSheet); - if (!EditorGUIUtility.isProSkin && !rootVisualElement.styleSheets.Contains(styleSheetLight)) - rootVisualElement.styleSheets.Add(styleSheetLight); - } - - // [case 1245086] Guard in case the SRP asset is set to null (or to a not supported SRP) when the lookdev window is already open - // Note: After an editor reload, we might get a null OnUpdateRequestedInternal and null SRP for a couple of frames, hence the check. - if (!LookDev.supported && OnUpdateRequestedInternal !=null) - { - // Print an error and close the Lookdev window (to avoid spamming the console) - Debug.LogError($"LookDev is not supported by this Scriptable Render Pipeline: " - + (RenderPipelineManager.currentPipeline == null ? "No SRP in use" : RenderPipelineManager.currentPipeline.ToString())); - LookDev.Close(); + if(EditorApplication.isUpdating) return; - } + + //deal with missing style on domain reload... + ReloadStyleSheets(); OnUpdateRequestedInternal?.Invoke(); } diff --git a/Editor/LookDev/DisplayWindow.uss b/Editor/LookDev/DisplayWindow.uss index 27cb7d1..a5c2b13 100644 --- a/Editor/LookDev/DisplayWindow.uss +++ b/Editor/LookDev/DisplayWindow.uss @@ -63,13 +63,13 @@ .showEnvironmentPanel > #environmentContainer { - width: 249px; + width: 255px; visibility: visible; } .showDebugPanel > #debugContainer { - width: 250px; /*219px;*/ + width: 256px; /*219px;*/ visibility: visible; } @@ -322,9 +322,9 @@ MultipleSourcePopupField > MultipleDifferentValue:hover #tabsRadio { - width: 250px; - min-width: 250px; - max-width: 250px; + width: 256px; + min-width: 256px; + max-width: 256px; flex: 1; flex-direction: row; -unity-text-align: middle-center; diff --git a/Editor/LookDev/LookDev.cs b/Editor/LookDev/LookDev.cs index afafcb4..3fa39b5 100644 --- a/Editor/LookDev/LookDev.cs +++ b/Editor/LookDev/LookDev.cs @@ -93,10 +93,7 @@ internal static void SaveConfig(string path = lastRenderingDataSavePath) /// Open the LookDev window public static void Open() { - var Window = EditorWindow.GetWindow(); - s_ViewDisplayer = Window; - s_EnvironmentDisplayer = Window; - ConfigureLookDev(reloadWithTemporaryID: false); + EditorWindow.GetWindow(); } /// Close the LookDev window @@ -108,6 +105,14 @@ public static void Close() s_EnvironmentDisplayer = null; } + internal static void Initialize(DisplayWindow window) + { + s_ViewDisplayer = window; + s_EnvironmentDisplayer = window; + open = true; + ConfigureLookDev(reloadWithTemporaryID: false); + } + [Callbacks.DidReloadScripts] static void OnEditorReload() { @@ -158,8 +163,6 @@ static void ConfigureRenderer(bool reloadWithTemporaryID) static void LinkViewDisplayer() { - EditorApplication.playModeStateChanged += state => Close(); - s_ViewDisplayer.OnClosed += () => { s_Compositor?.Dispose(); diff --git a/Editor/LookDev/LookDevRenderer.cs b/Editor/LookDev/LookDevRenderer.cs index 0115ca3..954740e 100644 --- a/Editor/LookDev/LookDevRenderer.cs +++ b/Editor/LookDev/LookDevRenderer.cs @@ -30,7 +30,6 @@ public void Dispose() return; disposed = true; - stage?.Dispose(); stage = null; updater = null; output?.Release(); diff --git a/Editor/LookDev/Stage.cs b/Editor/LookDev/Stage.cs index 72bffa7..5615344 100644 --- a/Editor/LookDev/Stage.cs +++ b/Editor/LookDev/Stage.cs @@ -196,7 +196,7 @@ static void InitAddedObjectsRecursively(GameObject go) var lineRenderer = go.GetComponent(); if (lineRenderer != null) lineRenderer.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off; - + var volumes = go.GetComponents(); foreach (var volume in volumes) volume.UpdateLayer(); //force update of layer now as the Update can be called after we unregister volume from manager @@ -217,16 +217,28 @@ void SetGameObjectVisible(bool visible) if (go == null || go.Equals(null)) continue; foreach (UnityEngine.Renderer renderer in go.GetComponentsInChildren()) - renderer.enabled = visible; + { + if((renderer.hideFlags & HideFlags.HideInInspector) == 0 && ((renderer.hideFlags & HideFlags.HideAndDontSave) == 0)) + renderer.enabled = visible; + } foreach (Light light in go.GetComponentsInChildren()) - light.enabled = visible; + { + if ((light.hideFlags & HideFlags.HideInInspector) == 0 && ((light.hideFlags & HideFlags.HideAndDontSave) == 0)) + light.enabled = visible; + } } // in case we add camera frontal light and such foreach (UnityEngine.Renderer renderer in m_Camera.GetComponentsInChildren()) - renderer.enabled = visible; + { + if ((renderer.hideFlags & HideFlags.HideInInspector) == 0 && ((renderer.hideFlags & HideFlags.HideAndDontSave) == 0)) + renderer.enabled = visible; + } foreach (Light light in m_Camera.GetComponentsInChildren()) - light.enabled = visible; + { + if ((light.hideFlags & HideFlags.HideInInspector) == 0 && ((light.hideFlags & HideFlags.HideAndDontSave) == 0)) + light.enabled = visible; + } } public void OnBeginRendering(IDataProvider dataProvider) @@ -273,6 +285,7 @@ class StageCache : IDisposable Stage[] m_Stages; Context m_Contexts; + IDataProvider m_CurrentDataProvider; public Stage this[ViewIndex index] => m_Stages[(int)index]; @@ -310,6 +323,8 @@ Stage InitStage(ViewIndex index, IDataProvider dataProvider) } dataProvider.FirstInitScene(stage.runtimeInterface); + + m_CurrentDataProvider = dataProvider; return stage; } @@ -345,7 +360,10 @@ void CleanUp() if (!disposedValue) { foreach (Stage stage in m_Stages) + { + m_CurrentDataProvider.Cleanup(stage.runtimeInterface); stage.Dispose(); + } disposedValue = true; } diff --git a/Editor/MaterialUpgrader.cs b/Editor/MaterialUpgrader.cs index 702a589..a831291 100644 --- a/Editor/MaterialUpgrader.cs +++ b/Editor/MaterialUpgrader.cs @@ -361,6 +361,13 @@ public static void UpgradeProjectFolder(List upgraders, HashSe } } + // Upgrade terrain specifically since it is a builtin material + if (Terrain.activeTerrains.Length > 0) + { + Material terrainMat = Terrain.activeTerrain.materialTemplate; + Upgrade(terrainMat, upgraders, flags); + } + UnityEditor.EditorUtility.ClearProgressBar(); } diff --git a/Editor/ShaderGenerator/ShaderTypeGeneration.cs b/Editor/ShaderGenerator/ShaderTypeGeneration.cs index caa3822..e1844a1 100644 --- a/Editor/ShaderGenerator/ShaderTypeGeneration.cs +++ b/Editor/ShaderGenerator/ShaderTypeGeneration.cs @@ -156,7 +156,7 @@ public object Clone() class DebugFieldInfo { - public DebugFieldInfo(string defineName, string fieldName, Type fieldType, bool isDirection, bool isSRGB, string displayName = "") + public DebugFieldInfo(string defineName, string fieldName, Type fieldType, bool isDirection, bool isSRGB, bool checkIsNormalized, string displayName = "") { this.defineName = defineName; this.fieldName = fieldName; @@ -164,6 +164,7 @@ public DebugFieldInfo(string defineName, string fieldName, Type fieldType, bool this.isDirection = isDirection; this.isSRGB = isSRGB; this.displayName = displayName; + this.checkIsNormalized = checkIsNormalized; } public string defineName; @@ -172,6 +173,7 @@ public DebugFieldInfo(string defineName, string fieldName, Type fieldType, bool public Type fieldType; public bool isDirection; public bool isSRGB; + public bool checkIsNormalized; } class PackedFieldInfo @@ -407,7 +409,15 @@ public string EmitTypeDecl() shaderText += "// Generated from " + type.FullName + "\n"; shaderText += "// PackingRules = " + attr.packingRules.ToString() + "\n"; - if (!attr.omitStructDeclaration) + + if (attr.generateCBuffer) + { + if (attr.constantRegister != -1) + shaderText += "GLOBAL_CBUFFER_START(" + type.Name + ", b" + attr.constantRegister + ")\n"; + else + shaderText += "CBUFFER_START(" + type.Name + ")\n"; + } + else if (!attr.omitStructDeclaration) { shaderText += "struct " + type.Name + "\n"; shaderText += "{\n"; @@ -418,7 +428,11 @@ public string EmitTypeDecl() shaderText += " " + shaderFieldInfo.ToString() + "\n"; } - if (!attr.omitStructDeclaration) + if (attr.generateCBuffer) + { + shaderText += "CBUFFER_END\n"; + } + else if (!attr.omitStructDeclaration) { shaderText += "};\n"; } @@ -573,7 +587,15 @@ public string EmitFunctions() { if (debugField.isDirection) { - shaderText += " result = " + lowerStructName + "." + debugField.fieldName + " * 0.5 + 0.5;\n"; + if (debugField.checkIsNormalized) + { + shaderText += " result = IsNormalized(" + lowerStructName + "." + debugField.fieldName +")? " + lowerStructName + "." + debugField.fieldName + " * 0.5 + 0.5 : float3(1.0, 0.0, 0.0);\n"; + + } + else + { + shaderText += " result = " + lowerStructName + "." + debugField.fieldName + " * 0.5 + 0.5;\n"; + } } else { @@ -764,6 +786,10 @@ private string EmitPackedGetters() { funcSignature = "float4 " + funcSignature; } + else if (packedInfo.fieldType == typeof(Vector2Int)) + { + funcSignature = "int2 " + funcSignature; + } funcBody += "return (" + sourceName + "." + packedInfo.fieldName + ");"; break; default: @@ -850,6 +876,10 @@ private string EmitPackedSetters() { funcSignature += "float4 " + newParamName + ", inout " + type.Name + " " + sourceName + ")"; } + else if (packedInfo.fieldType == typeof(Vector2Int)) + { + funcSignature += "int2 " + newParamName + ", inout " + type.Name + " " + sourceName + ")"; + } funcBody += sourceName + "." + packedInfo.fieldName + " = " + newParamName + ";"; break; default: @@ -939,6 +969,10 @@ private string EmitPackedInit() { funcSignature += "float4 " + newParamName + ", inout " + type.Name + " " + sourceName + ")"; } + else if (packedInfo.fieldType == typeof(Vector2Int)) + { + funcSignature += "int2 " + newParamName + ", inout " + type.Name + " " + sourceName + ")"; + } funcBody += sourceName + "." + packedInfo.fieldName + " = " + newParamName + ";"; break; default: @@ -1015,6 +1049,16 @@ public bool Generate() var arrayInfos = (field.GetCustomAttributes(typeof(HLSLArray), false) as HLSLArray[]); if (arrayInfos.Length != 0) { + // For constant buffers, every element of the array needs to be aligned to a Vector4 + if (attr.generateCBuffer && + arrayInfos[0].elementType != typeof(Vector4) && + arrayInfos[0].elementType != typeof(ShaderGenUInt4) && + arrayInfos[0].elementType != typeof(Matrix4x4)) + { + Error("Invalid HLSLArray target: '" + field.FieldType + "'" + ", only Vector4, Matrix4x4 and ShaderGenUInt4 are supported for arrays in constant buffers."); + return false; + } + arraySize = arrayInfos[0].arraySize; fieldType = arrayInfos[0].elementType; } @@ -1053,6 +1097,7 @@ public bool Generate() bool isDirection = false; bool sRGBDisplay = false; + bool checkIsNormalized = false; // Check if the display name have been override by the users if (Attribute.IsDefined(field, typeof(SurfaceDataAttributes))) @@ -1068,6 +1113,7 @@ public bool Generate() } isDirection = propertyAttr[0].isDirection; sRGBDisplay = propertyAttr[0].sRGBDisplay; + checkIsNormalized = propertyAttr[0].checkIsNormalized; } @@ -1083,7 +1129,7 @@ public bool Generate() string defineName = ("DEBUGVIEW_" + className + "_" + name).ToUpper(); m_Statics[defineName] = Convert.ToString(attr.paramDefinesStart + debugCounter++); - m_DebugFields.Add(new DebugFieldInfo(defineName, field.Name, fieldType, isDirection, sRGBDisplay)); + m_DebugFields.Add(new DebugFieldInfo(defineName, field.Name, fieldType, isDirection, sRGBDisplay, checkIsNormalized)); } } } @@ -1093,12 +1139,15 @@ public bool Generate() // Define only once, it is safe to assume that colors and directions are not packed with something else bool isDirection = false; bool sRGBDisplay = false; + bool checkIsNormalized = false; if (Attribute.IsDefined(field, typeof(PackingAttribute))) { var packingAttributes = (PackingAttribute[])field.GetCustomAttributes(typeof(PackingAttribute), false); isDirection = packingAttributes[0].isDirection; sRGBDisplay = packingAttributes[0].sRGBDisplay; + checkIsNormalized = packingAttributes[0].checkIsNormalized; + // Generate debug names string className = type.FullName.Substring(type.FullName.LastIndexOf((".")) + 1); // ClassName include nested class className = className.Replace('+', '_'); // FullName is Class+NestedClass replace by Class_NestedClass @@ -1128,7 +1177,7 @@ public bool Generate() typeForDebug = fieldType; } - m_DebugFields.Add(new DebugFieldInfo(defineName, field.Name, typeForDebug, isDirection, sRGBDisplay, packAttr.displayNames[0])); + m_DebugFields.Add(new DebugFieldInfo(defineName, field.Name, typeForDebug, isDirection, sRGBDisplay, checkIsNormalized, packAttr.displayNames[0])); } m_PackedFieldsInfos.Add(new PackedFieldInfo(packAttr, fieldType, field.Name)); @@ -1171,6 +1220,10 @@ public bool Generate() EmitPrimitiveType(floatPrecision, 3, arraySize, field.Name, "", m_ShaderFields); else if (fieldType == typeof(Vector4)) EmitPrimitiveType(floatPrecision, 4, arraySize, field.Name, "", m_ShaderFields); + else if (fieldType == typeof(Vector2Int)) + EmitPrimitiveType(PrimitiveType.Int, 2, arraySize, field.Name, "", m_ShaderFields); + else if (fieldType == typeof(ShaderGenUInt4)) + EmitPrimitiveType(PrimitiveType.UInt, 4, arraySize, field.Name, "", m_ShaderFields); else if (fieldType == typeof(Matrix4x4)) EmitMatrixType(floatPrecision, 4, 4, arraySize, field.Name, "", m_ShaderFields); else if (!ExtractComplex(field, m_ShaderFields)) diff --git a/Editor/Utilities/EditorMaterialQuality.cs b/Editor/Utilities/EditorMaterialQuality.cs index ab950b9..c8ff373 100644 --- a/Editor/Utilities/EditorMaterialQuality.cs +++ b/Editor/Utilities/EditorMaterialQuality.cs @@ -1,5 +1,4 @@ using UnityEngine.Rendering; -using Utilities; namespace UnityEditor.Rendering.Utilities { diff --git a/Editor/Volume/VolumeComponentEditor.cs b/Editor/Volume/VolumeComponentEditor.cs index 580fdae..fd35bfe 100644 --- a/Editor/Volume/VolumeComponentEditor.cs +++ b/Editor/Volume/VolumeComponentEditor.cs @@ -34,7 +34,7 @@ public VolumeComponentEditorAttribute(Type componentType) } /// - /// A custom editor class that draws a in the Inspector. If you do not + /// A custom editor class that draws a in the Inspector. If you do not /// provide a custom editor for a , Unity uses the default one. /// You must use a to let the editor know which /// component this drawer is for. @@ -43,7 +43,7 @@ public VolumeComponentEditorAttribute(Type componentType) /// Below is an example of a custom : /// /// using UnityEngine.Rendering; - /// + /// /// [Serializable, VolumeComponentMenu("Custom/Example Component")] /// public class ExampleComponent : VolumeComponent /// { @@ -53,18 +53,18 @@ public VolumeComponentEditorAttribute(Type componentType) /// And its associated editor: /// /// using UnityEditor.Rendering; - /// + /// /// [VolumeComponentEditor(typeof(ExampleComponent))] /// class ExampleComponentEditor : VolumeComponentEditor /// { /// SerializedDataParameter m_Intensity; - /// + /// /// public override void OnEnable() /// { /// var o = new PropertyFetcher<ExampleComponent>(serializedObject); /// m_Intensity = Unpack(o.Find(x => x.intensity)); /// } - /// + /// /// public override void OnInspectorGUI() /// { /// PropertyField(m_Intensity); @@ -125,7 +125,7 @@ internal set /// protected Editor m_Inspector; - List m_Parameters; + List<(GUIContent displayName, int displayOrder, SerializedDataParameter param)> m_Parameters; static Dictionary s_ParameterDrawers; @@ -179,6 +179,20 @@ internal void Init(VolumeComponent target, Editor inspector) OnEnable(); } + + class ParameterSorter : Comparer<(GUIContent displayName, int displayOrder, SerializedDataParameter param)> + { + public override int Compare((GUIContent displayName, int displayOrder, SerializedDataParameter param) x, (GUIContent displayName, int displayOrder, SerializedDataParameter param) y) + { + if (x.displayOrder < y.displayOrder) + return -1; + else if (x.displayOrder == y.displayOrder) + return 0; + else + return 1; + } + } + /// /// Unity calls this method when the object loads. /// @@ -188,7 +202,7 @@ internal void Init(VolumeComponent target, Editor inspector) /// public virtual void OnEnable() { - m_Parameters = new List(); + m_Parameters = new List<(GUIContent, int, SerializedDataParameter)>(); // Grab all valid serializable field on the VolumeComponent // TODO: Should only be done when needed / on demand as this can potentially be wasted CPU when a custom editor is in use @@ -206,9 +220,19 @@ public virtual void OnEnable() foreach (var field in fields) { var property = serializedObject.FindProperty(field.Name); + var name = ""; + var order = 0; + var attr = (DisplayInfoAttribute[])field.GetCustomAttributes(typeof(DisplayInfoAttribute), true); + if (attr.Length != 0) + { + name = attr[0].name; + order = attr[0].order; + } + var parameter = new SerializedDataParameter(property); - m_Parameters.Add(parameter); + m_Parameters.Add((new GUIContent(name), order, parameter)); } + m_Parameters.Sort(new ParameterSorter()); } /// @@ -239,7 +263,12 @@ public virtual void OnInspectorGUI() { // Display every field as-is foreach (var parameter in m_Parameters) - PropertyField(parameter); + { + if (parameter.displayName.text != "") + PropertyField(parameter.param, parameter.displayName); + else + PropertyField(parameter.param); + } } /// diff --git a/Editor/Volume/VolumeComponentListEditor.cs b/Editor/Volume/VolumeComponentListEditor.cs index 266271e..82e507a 100644 --- a/Editor/Volume/VolumeComponentListEditor.cs +++ b/Editor/Volume/VolumeComponentListEditor.cs @@ -119,8 +119,14 @@ void OnUndoRedoPerformed() // Dumb hack to make sure the serialized object is up to date on undo (else there'll be // a state mismatch when this class is used in a GameObject inspector). - m_SerializedObject.Update(); - m_SerializedObject.ApplyModifiedProperties(); + if (m_SerializedObject != null + && !m_SerializedObject.Equals(null) + && m_SerializedObject.targetObject != null + && !m_SerializedObject.targetObject.Equals(null)) + { + m_SerializedObject.Update(); + m_SerializedObject.ApplyModifiedProperties(); + } // Seems like there's an issue with the inspector not repainting after some undo events // This will take care of that @@ -259,15 +265,30 @@ void OnContextClick(Vector2 position, VolumeComponent targetComponent, int id) var menu = new GenericMenu(); if (id == 0) + { menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Move Up")); + menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Move to Top")); + } else + { + menu.AddItem(EditorGUIUtility.TrTextContent("Move to Top"), false, () => MoveComponent(id, -id)); menu.AddItem(EditorGUIUtility.TrTextContent("Move Up"), false, () => MoveComponent(id, -1)); + } if (id == m_Editors.Count - 1) + { + menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Move to Bottom")); menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Move Down")); + } else + { + menu.AddItem(EditorGUIUtility.TrTextContent("Move to Bottom"), false, () => MoveComponent(id, (m_Editors.Count -1) - id)); menu.AddItem(EditorGUIUtility.TrTextContent("Move Down"), false, () => MoveComponent(id, 1)); + } + menu.AddSeparator(string.Empty); + menu.AddItem(EditorGUIUtility.TrTextContent("Collapse All"), false, () => CollapseComponents()); + menu.AddItem(EditorGUIUtility.TrTextContent("Expand All"), false, () => ExpandComponents()); menu.AddSeparator(string.Empty); menu.AddItem(EditorGUIUtility.TrTextContent("Reset"), false, () => ResetComponent(targetComponent.GetType(), id)); menu.AddItem(EditorGUIUtility.TrTextContent("Remove"), false, () => RemoveComponent(id)); @@ -413,11 +434,44 @@ internal void MoveComponent(int id, int offset) m_ComponentsProperty.MoveArrayElement(id, id + offset); m_SerializedObject.ApplyModifiedProperties(); + // We need to keep track of what was expanded before to set it afterwards. + bool targetExpanded = m_Editors[id + offset].baseProperty.isExpanded; + bool sourceExpanded = m_Editors[id].baseProperty.isExpanded; + // Move editors var prev = m_Editors[id + offset]; m_Editors[id + offset] = m_Editors[id]; m_Editors[id] = prev; + + // Set the expansion values + m_Editors[id + offset].baseProperty.isExpanded = targetExpanded; + m_Editors[id].baseProperty.isExpanded = sourceExpanded; + } + + internal void CollapseComponents() + { + // Move components + m_SerializedObject.Update(); + int numEditors = m_Editors.Count; + for (int i = 0; i < numEditors; ++i) + { + m_Editors[i].baseProperty.isExpanded = false; + } + m_SerializedObject.ApplyModifiedProperties(); + } + + internal void ExpandComponents() + { + // Move components + m_SerializedObject.Update(); + int numEditors = m_Editors.Count; + for (int i = 0; i < numEditors; ++i) + { + m_Editors[i].baseProperty.isExpanded = true; + } + m_SerializedObject.ApplyModifiedProperties(); } + static bool CanPaste(VolumeComponent targetComponent) { diff --git a/Runtime/AssemblyInfo.cs b/Runtime/AssemblyInfo.cs index bf04d15..947d53a 100644 --- a/Runtime/AssemblyInfo.cs +++ b/Runtime/AssemblyInfo.cs @@ -1,3 +1,7 @@ using System.Runtime.CompilerServices; +#if ENABLE_HYBRID_RENDERER_V2 && !HYBRID_0_6_0_OR_NEWER +#error Core SRP 10.0.0 or newer with Hybrid Renderer V2 requires at least version 0.6.0 of com.unity.rendering.hybrid +#endif + [assembly: InternalsVisibleTo("Unity.RenderPipelines.Core.Editor")] diff --git a/Runtime/Camera/FreeCamera.cs b/Runtime/Camera/FreeCamera.cs index efb18ae..7c8abc7 100644 --- a/Runtime/Camera/FreeCamera.cs +++ b/Runtime/Camera/FreeCamera.cs @@ -1,3 +1,8 @@ +#if ENABLE_INPUT_SYSTEM && ENABLE_INPUT_SYSTEM_PACKAGE + #define USE_INPUT_SYSTEM + using UnityEngine.InputSystem; +#endif + using System.Collections.Generic; using UnityEngine; @@ -7,7 +12,6 @@ namespace UnityEngine.Rendering /// Utility Free Camera component. /// [HelpURL(Documentation.baseURL + Documentation.version + Documentation.subURL + "Free-Camera" + Documentation.endURL)] - [ExecuteAlways] public class FreeCamera : MonoBehaviour { /// @@ -41,6 +45,14 @@ public class FreeCamera : MonoBehaviour private static string kYAxis = "YAxis"; private static string kSpeedAxis = "Speed Axis"; +#if USE_INPUT_SYSTEM + InputAction lookAction; + InputAction moveAction; + InputAction speedAction; + InputAction fireAction; + InputAction yMoveAction; +#endif + void OnEnable() { RegisterInputs(); @@ -48,7 +60,43 @@ void OnEnable() void RegisterInputs() { -#if UNITY_EDITOR +#if USE_INPUT_SYSTEM + var map = new InputActionMap("Free Camera"); + + lookAction = map.AddAction("look", binding: "/delta"); + moveAction = map.AddAction("move", binding: "/leftStick"); + speedAction = map.AddAction("speed", binding: "/dpad"); + yMoveAction = map.AddAction("yMove"); + + lookAction.AddBinding("/rightStick").WithProcessor("scaleVector2(x=15, y=15)"); + moveAction.AddCompositeBinding("Dpad") + .With("Up", "/w") + .With("Up", "/upArrow") + .With("Down", "/s") + .With("Down", "/downArrow") + .With("Left", "/a") + .With("Left", "/leftArrow") + .With("Right", "/d") + .With("Right", "/rightArrow"); + speedAction.AddCompositeBinding("Dpad") + .With("Up", "/home") + .With("Down", "/end"); + yMoveAction.AddCompositeBinding("Dpad") + .With("Up", "/pageUp") + .With("Down", "/pageDown") + .With("Up", "/e") + .With("Down", "/q") + .With("Up", "/rightshoulder") + .With("Down", "/leftshoulder"); + + moveAction.Enable(); + lookAction.Enable(); + speedAction.Enable(); + fireAction.Enable(); + yMoveAction.Enable(); +#endif + +#if UNITY_EDITOR && !USE_INPUT_SYSTEM List inputEntries = new List(); // Add new bindings @@ -56,6 +104,7 @@ void RegisterInputs() inputEntries.Add(new InputManagerEntry { name = kRightStickY, kind = InputManagerEntry.Kind.Axis, axis = InputManagerEntry.Axis.Fifth, sensitivity = 1.0f, gravity = 1.0f, deadZone = 0.2f, invert = true }); inputEntries.Add(new InputManagerEntry { name = kYAxis, kind = InputManagerEntry.Kind.KeyOrButton, btnPositive = "page up", altBtnPositive = "joystick button 5", btnNegative = "page down", altBtnNegative = "joystick button 4", gravity = 1000.0f, deadZone = 0.001f, sensitivity = 1000.0f }); + inputEntries.Add(new InputManagerEntry { name = kYAxis, kind = InputManagerEntry.Kind.KeyOrButton, btnPositive = "q", btnNegative = "e", gravity = 1000.0f, deadZone = 0.001f, sensitivity = 1000.0f }); inputEntries.Add(new InputManagerEntry { name = kSpeedAxis, kind = InputManagerEntry.Kind.KeyOrButton, btnPositive = "home", btnNegative = "end", gravity = 1000.0f, deadZone = 0.001f, sensitivity = 1000.0f }); inputEntries.Add(new InputManagerEntry { name = kSpeedAxis, kind = InputManagerEntry.Kind.Axis, axis = InputManagerEntry.Axis.Seventh, gravity = 1000.0f, deadZone = 0.001f, sensitivity = 1000.0f }); @@ -64,33 +113,67 @@ void RegisterInputs() #endif } - void Update() - { - // If the debug menu is running, we don't want to conflict with its inputs. - if (DebugManager.instance.displayRuntimeUI) - return; + float inputRotateAxisX, inputRotateAxisY; + float inputChangeSpeed; + float inputVertical, inputHorizontal, inputYAxis; + bool leftShiftBoost, leftShift, fire1; - float inputRotateAxisX = 0.0f; - float inputRotateAxisY = 0.0f; + void UpdateInputs() + { + inputRotateAxisX = 0.0f; + inputRotateAxisY = 0.0f; + leftShiftBoost = false; + fire1 = false; + +#if USE_INPUT_SYSTEM + var lookDelta = lookAction.ReadValue(); + inputRotateAxisX = lookDelta.x * m_LookSpeedMouse * Time.deltaTime; + inputRotateAxisY = lookDelta.y * m_LookSpeedMouse * Time.deltaTime; + + leftShift = Keyboard.current.leftShiftKey.isPressed; + fire1 = Mouse.current?.leftButton?.isPressed == true || Gamepad.current?.xButton?.isPressed == true; + + inputChangeSpeed = speedAction.ReadValue().y; + + var moveDelta = moveAction.ReadValue(); + inputVertical = moveDelta.y; + inputHorizontal = moveDelta.x; + inputYAxis = yMoveAction.ReadValue().y; +#else if (Input.GetMouseButton(1)) { + leftShiftBoost = true; inputRotateAxisX = Input.GetAxis(kMouseX) * m_LookSpeedMouse; inputRotateAxisY = Input.GetAxis(kMouseY) * m_LookSpeedMouse; } inputRotateAxisX += (Input.GetAxis(kRightStickX) * m_LookSpeedController * Time.deltaTime); inputRotateAxisY += (Input.GetAxis(kRightStickY) * m_LookSpeedController * Time.deltaTime); - float inputChangeSpeed = Input.GetAxis(kSpeedAxis); + leftShift = Input.GetKeyDown(KeyCode.LeftShift); + fire1 = Input.GetAxis("Fire1") > 0.0f; + + inputChangeSpeed = Input.GetAxis(kSpeedAxis); + + inputVertical = Input.GetAxis(kVertical); + inputHorizontal = Input.GetAxis(kHorizontal); + inputYAxis = Input.GetAxis(kYAxis); +#endif + } + + void Update() + { + // If the debug menu is running, we don't want to conflict with its inputs. + if (DebugManager.instance.displayRuntimeUI) + return; + + UpdateInputs(); + if (inputChangeSpeed != 0.0f) { m_MoveSpeed += inputChangeSpeed * m_MoveSpeedIncrement; if (m_MoveSpeed < m_MoveSpeedIncrement) m_MoveSpeed = m_MoveSpeedIncrement; } - float inputVertical = Input.GetAxis(kVertical); - float inputHorizontal = Input.GetAxis(kHorizontal); - float inputYAxis = Input.GetAxis(kYAxis); - bool moved = inputRotateAxisX != 0.0f || inputRotateAxisY != 0.0f || inputVertical != 0.0f || inputHorizontal != 0.0f || inputYAxis != 0.0f; if (moved) { @@ -107,10 +190,10 @@ void Update() transform.localRotation = Quaternion.Euler(newRotationX, newRotationY, transform.localEulerAngles.z); float moveSpeed = Time.deltaTime * m_MoveSpeed; - if (Input.GetMouseButton(1)) - moveSpeed *= Input.GetKey(KeyCode.LeftShift) ? m_Turbo : 1.0f; + if (leftShiftBoost) + moveSpeed *= leftShift ? m_Turbo : 1.0f; else - moveSpeed *= Input.GetAxis("Fire1") > 0.0f ? m_Turbo : 1.0f; + moveSpeed *= fire1 ? m_Turbo : 1.0f; transform.position += transform.forward * moveSpeed * inputVertical; transform.position += transform.right * moveSpeed * inputHorizontal; transform.position += Vector3.up * moveSpeed * inputYAxis; diff --git a/Runtime/Common/CommandBufferPool.cs b/Runtime/Common/CommandBufferPool.cs index 868f8ef..65db8cd 100644 --- a/Runtime/Common/CommandBufferPool.cs +++ b/Runtime/Common/CommandBufferPool.cs @@ -17,12 +17,14 @@ public static class CommandBufferPool public static CommandBuffer Get() { var cmd = s_BufferPool.Get(); - cmd.name = "Unnamed Command Buffer"; + // Set to empty on purpose, does not create profiling markers. + cmd.name = ""; return cmd; } /// /// Get a new Command Buffer and assign a name to it. + /// Named Command Buffers will add profiling makers implicitly for the buffer execution. /// /// /// diff --git a/Runtime/Common/ConstantBuffer.cs b/Runtime/Common/ConstantBuffer.cs new file mode 100644 index 0000000..b016e72 --- /dev/null +++ b/Runtime/Common/ConstantBuffer.cs @@ -0,0 +1,195 @@ +using System.Collections.Generic; +using Unity.Collections.LowLevel.Unsafe; + +namespace UnityEngine.Rendering +{ + /// + /// Constant Buffer management class. + /// + public class ConstantBuffer + { + static List m_RegisteredConstantBuffers = new List(); + + /// + /// Update the GPU data of the constant buffer and bind it globally. + /// + /// The type of structure representing the constant buffer data. + /// Command Buffer used to execute the graphic commands. + /// Input data of the constant buffer. + /// Shader porperty id to bind the constant buffer to. + public static void PushGlobal(CommandBuffer cmd, in CBType data, int shaderId) where CBType : struct + { + var cb = TypedConstantBuffer.instance; + + cb.UpdateData(cmd, data); + cb.SetGlobal(cmd, shaderId); + } + + /// + /// Update the GPU data of the constant buffer and bind it to a compute shader. + /// + /// The type of structure representing the constant buffer data. + /// Command Buffer used to execute the graphic commands. + /// Input data of the constant buffer. + /// Compute shader to which the constant buffer should be bound. + /// Shader porperty id to bind the constant buffer to. + public static void Push(CommandBuffer cmd, in CBType data, ComputeShader cs, int shaderId) where CBType : struct + { + var cb = TypedConstantBuffer.instance; + + cb.UpdateData(cmd, data); + cb.Set(cmd, cs, shaderId); + } + + /// + /// Update the GPU data of the constant buffer and bind it to a material. + /// + /// The type of structure representing the constant buffer data. + /// Command Buffer used to execute the graphic commands. + /// Input data of the constant buffer. + /// Material to which the constant buffer should be bound. + /// Shader porperty id to bind the constant buffer to. + public static void Push(CommandBuffer cmd, in CBType data, Material mat, int shaderId) where CBType : struct + { + var cb = TypedConstantBuffer.instance; + + cb.UpdateData(cmd, data); + cb.Set(mat, shaderId); + } + + /// + /// Update the GPU data of the constant buffer. + /// + /// The type of structure representing the constant buffer data. + /// Command Buffer used to execute the graphic commands. + /// Input data of the constant buffer. + public static void UpdateData(CommandBuffer cmd, in CBType data) where CBType : struct + { + var cb = TypedConstantBuffer.instance; + + cb.UpdateData(cmd, data); + } + + /// + /// Bind the constant buffer globally. + /// + /// The type of structure representing the constant buffer data. + /// Command Buffer used to execute the graphic commands. + /// Shader porperty id to bind the constant buffer to. + public static void SetGlobal(CommandBuffer cmd, int shaderId) where CBType : struct + { + var cb = TypedConstantBuffer.instance; + + cb.SetGlobal(cmd, shaderId); + } + + /// + /// Bind the constant buffer to a compute shader. + /// + /// The type of structure representing the constant buffer data. + /// Command Buffer used to execute the graphic commands. + /// Compute shader to which the constant buffer should be bound. + /// Shader porperty id to bind the constant buffer to. + public static void Set(CommandBuffer cmd, ComputeShader cs, int shaderId) where CBType : struct + { + var cb = TypedConstantBuffer.instance; + + cb.Set(cmd, cs, shaderId); + } + + /// + /// Bind the constant buffer to a material. + /// + /// The type of structure representing the constant buffer data. + /// Material to which the constant buffer should be bound. + /// Shader porperty id to bind the constant buffer to. + public static void Set(Material mat, int shaderId) where CBType : struct + { + var cb = TypedConstantBuffer.instance; + + cb.Set(mat, shaderId); + } + + /// + /// Release all currently allocated constant buffers. + /// This needs to be called before shutting down the application. + /// + public static void ReleaseAll() + { + foreach (var cb in m_RegisteredConstantBuffers) + cb.Release(); + + m_RegisteredConstantBuffers.Clear(); + } + + internal abstract class ConstantBufferBase + { + public abstract void Release(); + } + + internal static void Register(ConstantBufferBase cb) + { + m_RegisteredConstantBuffers.Add(cb); + } + + class TypedConstantBuffer : ConstantBufferBase where CBType : struct + { + CBType[] m_Data = new CBType[1]; // Array is required by the ComputeBuffer SetData API + static TypedConstantBuffer s_Instance = null; + internal static TypedConstantBuffer instance + { + get + { + if (s_Instance == null) + s_Instance = new TypedConstantBuffer(); + return s_Instance; + } + set + { + s_Instance = value; + } + } + ComputeBuffer m_GPUConstantBuffer = null; + + TypedConstantBuffer() + { + m_GPUConstantBuffer = new ComputeBuffer(1, UnsafeUtility.SizeOf(), ComputeBufferType.Constant); + ConstantBuffer.Register(this); + } + + public void UpdateData(CommandBuffer cmd, in CBType data) + { + m_Data[0] = data; +#if UNITY_2021_1_OR_NEWER + cmd.SetBufferData(m_GPUConstantBuffer, m_Data); +#else + cmd.SetComputeBufferData(m_GPUConstantBuffer, m_Data); +#endif + } + + public void SetGlobal(CommandBuffer cmd, int shaderId) + { + cmd.SetGlobalConstantBuffer(m_GPUConstantBuffer, shaderId, 0, m_GPUConstantBuffer.stride); + } + + public void Set(CommandBuffer cmd, ComputeShader cs, int shaderId) + { + cmd.SetComputeConstantBufferParam(cs, shaderId, m_GPUConstantBuffer, 0, m_GPUConstantBuffer.stride); + } + + public void Set(Material mat, int shaderId) + { + // This isn't done via command buffer because as long as the buffer itself is not destroyed, + // the binding stays valid. Only the commit of data needs to go through the command buffer. + // We do it here anyway for now to simplify user API. + mat.SetConstantBuffer(shaderId, m_GPUConstantBuffer, 0, m_GPUConstantBuffer.stride); + } + + public override void Release() + { + CoreUtils.SafeRelease(m_GPUConstantBuffer); + s_Instance = null; + } + } + } +} diff --git a/Runtime/Common/ConstantBuffer.cs.meta b/Runtime/Common/ConstantBuffer.cs.meta new file mode 100644 index 0000000..af8b64f --- /dev/null +++ b/Runtime/Common/ConstantBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 61649e120aa78c04e8b44d1c5af17d35 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Common/CoreAttributes.cs b/Runtime/Common/CoreAttributes.cs new file mode 100644 index 0000000..18fc09f --- /dev/null +++ b/Runtime/Common/CoreAttributes.cs @@ -0,0 +1,16 @@ +using System; + +namespace UnityEngine.Rendering +{ + /// + /// Attribute used to customize UI display. + /// + [AttributeUsage(AttributeTargets.Field)] + public class DisplayInfoAttribute : Attribute + { + /// Display name used in UI. + public string name; + /// Display order used in UI. + public int order; + } +} diff --git a/Runtime/Common/CoreAttributes.cs.meta b/Runtime/Common/CoreAttributes.cs.meta new file mode 100644 index 0000000..5b12d7e --- /dev/null +++ b/Runtime/Common/CoreAttributes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07346c0b2cba0214f8f27c46ec2dd613 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Common/DynamicArray.cs b/Runtime/Common/DynamicArray.cs index daa1070..616efd2 100644 --- a/Runtime/Common/DynamicArray.cs +++ b/Runtime/Common/DynamicArray.cs @@ -15,6 +15,11 @@ public class DynamicArray where T: new() /// public int size { get; private set; } + /// + /// Allocated size of the array. + /// + public int capacity { get { return m_Array.Length; } } + /// /// Constructor. /// Defaults to a size of 32 elements. @@ -22,7 +27,7 @@ public class DynamicArray where T: new() public DynamicArray() { m_Array = new T[32]; - size = 32; + size = 0; } /// diff --git a/Runtime/Common/DynamicResolutionHandler.cs b/Runtime/Common/DynamicResolutionHandler.cs index 1caaad3..9e77bbf 100644 --- a/Runtime/Common/DynamicResolutionHandler.cs +++ b/Runtime/Common/DynamicResolutionHandler.cs @@ -53,6 +53,12 @@ public class DynamicResolutionHandler /// public DynamicResUpscaleFilter filter { get; set; } + /// + /// The viewport of the final buffer. This is likely the resolution the dynamic resolution starts from before any scaling. Note this is NOT the target resolution the rendering will happen in + /// but the resolution the scaled rendered result will be upscaled to. + /// + public Vector2Int finalViewport { get; set; } + private DynamicResolutionType type; @@ -80,7 +86,7 @@ static private float DefaultDynamicResMethod() private void ProcessSettings(GlobalDynamicResolutionSettings settings) { - m_Enabled = settings.enabled; + m_Enabled = settings.enabled && (Application.isPlaying || settings.forceResolution); if (!m_Enabled) { m_CurrentFraction = 1.0f; @@ -159,7 +165,8 @@ public void Update(GlobalDynamicResolutionSettings settings, Action OnResolution ScalableBufferManager.ResizeBuffers(m_CurrentFraction, m_CurrentFraction); } - OnResolutionChange(); + if(OnResolutionChange != null) + OnResolutionChange(); } else { @@ -169,7 +176,8 @@ public void Update(GlobalDynamicResolutionSettings settings, Action OnResolution if(ScalableBufferManager.widthScaleFactor != m_PrevHWScaleWidth || ScalableBufferManager.heightScaleFactor != m_PrevHWScaleHeight) { - OnResolutionChange(); + if (OnResolutionChange != null) + OnResolutionChange(); } } } diff --git a/Runtime/Common/IVirtualTexturingEnabledRenderPipeline.cs b/Runtime/Common/IVirtualTexturingEnabledRenderPipeline.cs new file mode 100644 index 0000000..344e911 --- /dev/null +++ b/Runtime/Common/IVirtualTexturingEnabledRenderPipeline.cs @@ -0,0 +1,13 @@ +namespace UnityEngine.Rendering +{ + /// + /// By implementing this interface, a render pipeline can indicate to external code it supports virtual texturing. + /// + public interface IVirtualTexturingEnabledRenderPipeline + { + /// + /// Indicates if virtual texturing is currently enabled for this render pipeline instance. + /// + bool virtualTexturingEnabled { get; } + } +} diff --git a/Runtime/Common/IVirtualTexturingEnabledRenderPipeline.cs.meta b/Runtime/Common/IVirtualTexturingEnabledRenderPipeline.cs.meta new file mode 100644 index 0000000..6745d61 --- /dev/null +++ b/Runtime/Common/IVirtualTexturingEnabledRenderPipeline.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: abbe08f5688bd7342b997cf075044945 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Debugging/DebugManager.Actions.cs b/Runtime/Debugging/DebugManager.Actions.cs index 8735df7..4ec565e 100644 --- a/Runtime/Debugging/DebugManager.Actions.cs +++ b/Runtime/Debugging/DebugManager.Actions.cs @@ -1,5 +1,5 @@ #if ENABLE_INPUT_SYSTEM && ENABLE_INPUT_SYSTEM_PACKAGE -#define USE_INPUT_SYSTEM + #define USE_INPUT_SYSTEM using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; #endif @@ -40,55 +40,111 @@ public sealed partial class DebugManager const string kDPadHorizontal = "Debug Horizontal"; const string kMultiplierBtn = "Debug Multiplier"; const string kResetBtn = "Debug Reset"; + const string kEnableDebug = "Enable Debug"; DebugActionDesc[] m_DebugActions; DebugActionState[] m_DebugActionStates; +#if USE_INPUT_SYSTEM + InputActionMap debugActionMap = new InputActionMap("Debug Menu"); +#endif + void RegisterActions() { m_DebugActions = new DebugActionDesc[(int)DebugAction.DebugActionCount]; m_DebugActionStates = new DebugActionState[(int)DebugAction.DebugActionCount]; var enableDebugMenu = new DebugActionDesc(); +#if USE_INPUT_SYSTEM + enableDebugMenu.buttonAction = debugActionMap.FindAction(kEnableDebug); +#else enableDebugMenu.buttonTriggerList.Add(new[] { kEnableDebugBtn1, kEnableDebugBtn2 }); enableDebugMenu.keyTriggerList.Add(new[] { KeyCode.LeftControl, KeyCode.Backspace }); +#endif enableDebugMenu.repeatMode = DebugActionRepeatMode.Never; AddAction(DebugAction.EnableDebugMenu, enableDebugMenu); var resetDebugMenu = new DebugActionDesc(); - resetDebugMenu.buttonTriggerList.Add(new[] { kResetBtn, kEnableDebugBtn2 }); +#if USE_INPUT_SYSTEM + resetDebugMenu.buttonAction = debugActionMap.FindAction(kResetBtn); +#else resetDebugMenu.keyTriggerList.Add(new[] { KeyCode.LeftAlt, KeyCode.Backspace }); + resetDebugMenu.buttonTriggerList.Add(new[] { kResetBtn, kEnableDebugBtn2 }); +#endif resetDebugMenu.repeatMode = DebugActionRepeatMode.Never; AddAction(DebugAction.ResetAll, resetDebugMenu); var nextDebugPanel = new DebugActionDesc(); +#if USE_INPUT_SYSTEM + nextDebugPanel.buttonAction = debugActionMap.FindAction(kDebugNextBtn); +#else nextDebugPanel.buttonTriggerList.Add(new[] { kDebugNextBtn }); +#endif nextDebugPanel.repeatMode = DebugActionRepeatMode.Never; AddAction(DebugAction.NextDebugPanel, nextDebugPanel); var previousDebugPanel = new DebugActionDesc(); +#if USE_INPUT_SYSTEM + previousDebugPanel.buttonAction = debugActionMap.FindAction(kDebugPreviousBtn); +#else previousDebugPanel.buttonTriggerList.Add(new[] { kDebugPreviousBtn }); +#endif previousDebugPanel.repeatMode = DebugActionRepeatMode.Never; AddAction(DebugAction.PreviousDebugPanel, previousDebugPanel); var validate = new DebugActionDesc(); +#if USE_INPUT_SYSTEM + validate.buttonAction = debugActionMap.FindAction(kValidateBtn); +#else validate.buttonTriggerList.Add(new[] { kValidateBtn }); +#endif validate.repeatMode = DebugActionRepeatMode.Never; AddAction(DebugAction.Action, validate); var persistent = new DebugActionDesc(); +#if USE_INPUT_SYSTEM + persistent.buttonAction = debugActionMap.FindAction(kPersistentBtn); +#else persistent.buttonTriggerList.Add(new[] { kPersistentBtn }); +#endif persistent.repeatMode = DebugActionRepeatMode.Never; AddAction(DebugAction.MakePersistent, persistent); var multiplier = new DebugActionDesc(); +#if USE_INPUT_SYSTEM + multiplier.buttonAction = debugActionMap.FindAction(kMultiplierBtn); +#else multiplier.buttonTriggerList.Add(new[] { kMultiplierBtn }); +#endif multiplier.repeatMode = DebugActionRepeatMode.Delay; validate.repeatDelay = 0f; + AddAction(DebugAction.Multiplier, multiplier); - AddAction(DebugAction.MoveVertical, new DebugActionDesc { axisTrigger = kDPadVertical, repeatMode = DebugActionRepeatMode.Delay, repeatDelay = 0.16f }); - AddAction(DebugAction.MoveHorizontal, new DebugActionDesc { axisTrigger = kDPadHorizontal, repeatMode = DebugActionRepeatMode.Delay, repeatDelay = 0.16f }); + var moveVertical = new DebugActionDesc(); +#if USE_INPUT_SYSTEM + moveVertical.buttonAction = debugActionMap.FindAction(kDPadVertical); +#else + moveVertical.axisTrigger = kDPadVertical; +#endif + moveVertical.repeatMode = DebugActionRepeatMode.Delay; + moveVertical.repeatDelay = 0.16f; + AddAction(DebugAction.MoveVertical, moveVertical); + + var moveHorizontal = new DebugActionDesc(); +#if USE_INPUT_SYSTEM + moveHorizontal.buttonAction = debugActionMap.FindAction(kDPadHorizontal); +#else + moveHorizontal.axisTrigger = kDPadHorizontal; +#endif + moveHorizontal.repeatMode = DebugActionRepeatMode.Delay; + moveHorizontal.repeatDelay = 0.16f; + AddAction(DebugAction.MoveHorizontal, moveHorizontal); + +#if USE_INPUT_SYSTEM + foreach (var action in debugActionMap) + action.Enable(); +#endif } void AddAction(DebugAction action, DebugActionDesc desc) @@ -104,7 +160,17 @@ void SampleAction(int actionIndex) var state = m_DebugActionStates[actionIndex]; // Disable all input events if we're using the new input system -#if ENABLE_LEGACY_INPUT_MANAGER +#if USE_INPUT_SYSTEM + if (state.runningAction == false) + { + if (desc.buttonAction != null) + { + var value = desc.buttonAction.ReadValue(); + if (!Mathf.Approximately(value, 0)) + state.TriggerWithButton(desc.buttonAction, value); + } + } +#elif ENABLE_LEGACY_INPUT_MANAGER //bool canSampleAction = (state.actionTriggered == false) || (desc.repeatMode == DebugActionRepeatMode.Delay && state.timer > desc.repeatDelay); if (state.runningAction == false) { @@ -157,8 +223,7 @@ void SampleAction(int actionIndex) } } } -#elif USE_INPUT_SYSTEM - // TODO: make the new input system work + #endif } @@ -206,14 +271,69 @@ void RegisterInputs() InputRegistering.RegisterInputs(inputEntries); #endif + +#if USE_INPUT_SYSTEM + // Register input system actions + var enableAction = debugActionMap.AddAction(kEnableDebug, type: InputActionType.Button); + enableAction.AddCompositeBinding("ButtonWithOneModifier") + .With("Modifier", "/rightStickPress") + .With("Button", "/leftStickPress") + .With("Modifier", "/leftCtrl") + .With("Button", "/backspace"); + + var resetAction = debugActionMap.AddAction(kResetBtn, type: InputActionType.Button); + resetAction.AddCompositeBinding("ButtonWithOneModifier") + .With("Modifier", "/rightStickPress") + .With("Button", "/b") + .With("Modifier", "/leftAlt") + .With("Button", "/backspace"); + + var next = debugActionMap.AddAction(kDebugNextBtn, type: InputActionType.Button); + next.AddBinding("/pageDown"); + next.AddBinding("/rightShoulder"); + + var previous = debugActionMap.AddAction(kDebugPreviousBtn, type: InputActionType.Button); + previous.AddBinding("/pageUp"); + previous.AddBinding("/leftShoulder"); + + var validateAction = debugActionMap.AddAction(kValidateBtn, type: InputActionType.Button); + validateAction.AddBinding("/enter"); + validateAction.AddBinding("/a"); + + var persistentAction = debugActionMap.AddAction(kPersistentBtn, type: InputActionType.Button); + persistentAction.AddBinding("/rightShift"); + persistentAction.AddBinding("/x"); + + var multiplierAction = debugActionMap.AddAction(kMultiplierBtn, type: InputActionType.Value); + multiplierAction.AddBinding("/leftShift"); + multiplierAction.AddBinding("/y"); + + var moveVerticalAction = debugActionMap.AddAction(kDPadVertical); + moveVerticalAction.AddCompositeBinding("1DAxis") + .With("Positive", "/dpad/up") + .With("Negative", "/dpad/down") + .With("Positive", "/upArrow") + .With("Negative", "/downArrow"); + + var moveHorizontalAction = debugActionMap.AddAction(kDPadHorizontal); + moveHorizontalAction.AddCompositeBinding("1DAxis") + .With("Positive", "/dpad/right") + .With("Negative", "/dpad/left") + .With("Positive", "/rightArrow") + .With("Negative", "/leftArrow"); +#endif } } class DebugActionDesc { - public List buttonTriggerList = new List(); +#if USE_INPUT_SYSTEM + public InputAction buttonAction = null; +#else public string axisTrigger = ""; + public List buttonTriggerList = new List(); public List keyTriggerList = new List(); +#endif public DebugActionRepeatMode repeatMode = DebugActionRepeatMode.Never; public float repeatDelay; } @@ -228,9 +348,13 @@ enum DebugActionKeyType } DebugActionKeyType m_Type; +#if USE_INPUT_SYSTEM + InputAction inputAction; +#else string[] m_PressedButtons; string m_PressedAxis = ""; KeyCode[] m_PressedKeys; +#endif bool[] m_TriggerPressedUp; float m_Timer; @@ -248,6 +372,13 @@ void Trigger(int triggerCount, float state) m_TriggerPressedUp[i] = false; } +#if USE_INPUT_SYSTEM + public void TriggerWithButton(InputAction action, float state) + { + inputAction = action; + Trigger(action.bindings.Count, state); + } +#else public void TriggerWithButton(string[] buttons, float state) { m_Type = DebugActionKeyType.Button; @@ -270,6 +401,7 @@ public void TriggerWithKey(KeyCode[] keys, float state) m_PressedAxis = ""; Trigger(keys.Length, state); } +#endif void Reset() { @@ -289,12 +421,17 @@ public void Update(DebugActionDesc desc) for (int i = 0; i < m_TriggerPressedUp.Length; ++i) { +#if USE_INPUT_SYSTEM + if (inputAction != null) + m_TriggerPressedUp[i] |= Mathf.Approximately(inputAction.ReadValue(), 0f); +#else if (m_Type == DebugActionKeyType.Button) m_TriggerPressedUp[i] |= Input.GetButtonUp(m_PressedButtons[i]); else if (m_Type == DebugActionKeyType.Axis) m_TriggerPressedUp[i] |= Mathf.Approximately(Input.GetAxis(m_PressedAxis), 0f); else m_TriggerPressedUp[i] |= Input.GetKeyUp(m_PressedKeys[i]); +#endif } bool allTriggerUp = true; diff --git a/Runtime/Debugging/DebugUI.Containers.cs b/Runtime/Debugging/DebugUI.Containers.cs index 3441cc0..9d6746e 100644 --- a/Runtime/Debugging/DebugUI.Containers.cs +++ b/Runtime/Debugging/DebugUI.Containers.cs @@ -205,5 +205,206 @@ public VBox() displayName = "VBox"; } } + + /// + /// Array Container. + /// + public class Table : Container + { + /// Row Container. + public class Row : Foldout + { + /// Constructor. + public Row() { displayName = "Row"; } + } + + /// + /// True if the table is read only. + /// + public bool isReadOnly = false; + + /// Constructor. + public Table() { displayName = "Array"; } + + /// + /// Set column visibility. + /// + /// Index of the column. + /// True if the column should be visible. + public void SetColumnVisibility(int index, bool visible) + { +#if UNITY_EDITOR + var header = Header; + if (index < 0 || index >= m_ColumnCount) + return; + + index++; + if (header.IsColumnVisible(index) != visible) + { + var newVisibleColumns = new System.Collections.Generic.List(header.state.visibleColumns); + if (newVisibleColumns.Contains(index)) + { + newVisibleColumns.Remove(index); + } + else + { + newVisibleColumns.Add(index); + newVisibleColumns.Sort(); + } + header.state.visibleColumns = newVisibleColumns.ToArray(); + + var cols = header.state.columns; + for (int i = 0; i < cols.Length; i++) + cols[i].width = 50f; + header.ResizeToFit(); + } +#else + var columns = VisibleColumns; + if (index < 0 || index > columns.Length) + return; + + columns[index] = visible; +#endif + } + + /// + /// Get column visibility. + /// + /// Index of the column. + /// True if the column is visible. + public bool GetColumnVisibility(int index) + { +#if UNITY_EDITOR + var header = Header; + if (index < 0 || index >= m_ColumnCount) + return false; + + return header.IsColumnVisible(index + 1); +#else + var columns = VisibleColumns; + if (index < 0 || index > columns.Length) + return false; + + return columns[index]; +#endif + } + +#if UNITY_EDITOR + /// + /// The scroll position of the table. + /// + public Vector2 scroll = Vector2.zero; + + int m_ColumnCount; + UnityEditor.IMGUI.Controls.MultiColumnHeader m_Header = null; + + /// + /// The table header for drawing + /// + public UnityEditor.IMGUI.Controls.MultiColumnHeader Header + { + get + { + if (m_Header != null) + return m_Header; + + if (children.Count != 0) + { + m_ColumnCount = ((Container)children[0]).children.Count; + for (int i = 1; i < children.Count; i++) + { + if (((Container)children[i]).children.Count != m_ColumnCount) + { + Debug.LogError("All rows must have the same number of children."); + return null; + } + } + } + + UnityEditor.IMGUI.Controls.MultiColumnHeaderState.Column CreateColumn(string name) + { + var col = new UnityEditor.IMGUI.Controls.MultiColumnHeaderState.Column() + { + canSort = false, + headerTextAlignment = TextAlignment.Center, + headerContent = new GUIContent(name), + }; + + GUIStyle style = UnityEditor.IMGUI.Controls.MultiColumnHeader.DefaultStyles.columnHeaderCenterAligned; + style.CalcMinMaxWidth(col.headerContent, out col.width, out float _); + col.width = Mathf.Min(col.width, 50f); + return col; + } + + var cols = new UnityEditor.IMGUI.Controls.MultiColumnHeaderState.Column[m_ColumnCount + 1]; + cols[0] = CreateColumn(displayName); + cols[0].allowToggleVisibility = false; + for (int i = 0; i < m_ColumnCount; i++) + cols[i + 1] = CreateColumn(((Container)children[0]).children[i].displayName); + + var state = new UnityEditor.IMGUI.Controls.MultiColumnHeaderState(cols); + m_Header = new UnityEditor.IMGUI.Controls.MultiColumnHeader(state) { height = 23 }; + m_Header.ResizeToFit(); + return m_Header; + } + } +#else + bool[] m_Header = null; + + /// + /// The visible columns + /// + public bool[] VisibleColumns + { + get + { + if (m_Header != null) + return m_Header; + + int columnCount = 0; + if (children.Count != 0) + { + columnCount = ((Container)children[0]).children.Count; + for (int i = 1; i < children.Count; i++) + { + if (((Container)children[i]).children.Count != columnCount) + { + Debug.LogError("All rows must have the same number of children."); + return null; + } + } + } + + m_Header = new bool[columnCount]; + for (int i = 0; i < columnCount; i++) + m_Header[i] = true; + + return m_Header; + } + } +#endif + + /// + /// Method called when a children is added. + /// + /// Sender widget. + /// List of added children. + protected override void OnItemAdded(ObservableList sender, ListChangedEventArgs e) + { + base.OnItemAdded(sender, e); + m_Header = null; + } + + /// + /// Method called when a children is removed. + /// + /// Sender widget. + /// List of removed children. + protected override void OnItemRemoved(ObservableList sender, ListChangedEventArgs e) + { + base.OnItemRemoved(sender, e); + m_Header = null; + } + } } } diff --git a/Runtime/Debugging/DebugUI.Fields.cs b/Runtime/Debugging/DebugUI.Fields.cs index 4f0c835..8023d7d 100644 --- a/Runtime/Debugging/DebugUI.Fields.cs +++ b/Runtime/Debugging/DebugUI.Fields.cs @@ -320,6 +320,9 @@ internal void InitQuickSeparators() internal void InitIndexes() { + if (enumNames == null) + enumNames = new GUIContent[0]; + indexes = new int[enumNames.Length]; for (int i = 0; i < enumNames.Length; i++) { diff --git a/Runtime/Debugging/DebugUI.cs b/Runtime/Debugging/DebugUI.cs index fa42922..da9b75f 100644 --- a/Runtime/Debugging/DebugUI.cs +++ b/Runtime/Debugging/DebugUI.cs @@ -178,6 +178,11 @@ public class Value : Widget /// public float refreshRate = 0.1f; + /// + /// Constructor. + /// + public Value() { displayName = ""; } + /// /// Returns the value of the widget. /// diff --git a/Runtime/Debugging/MousePositionDebug.cs b/Runtime/Debugging/MousePositionDebug.cs index 2c9fae2..26166f6 100644 --- a/Runtime/Debugging/MousePositionDebug.cs +++ b/Runtime/Debugging/MousePositionDebug.cs @@ -1,3 +1,9 @@ +#if ENABLE_INPUT_SYSTEM && ENABLE_INPUT_SYSTEM_PACKAGE + #define USE_INPUT_SYSTEM + using UnityEngine.InputSystem; + using UnityEngine.InputSystem.Controls; +#endif + using UnityEditor; namespace UnityEngine.Rendering @@ -52,17 +58,33 @@ public static void Build() void Update() { - if (Input.mousePosition.x < 0 - || Input.mousePosition.y < 0 - || Input.mousePosition.x > Screen.width - || Input.mousePosition.y > Screen.height) + Vector2 mousePosition; + bool rightClickPressed = false; + bool endKeyPressed = false; + +#if USE_INPUT_SYSTEM + mousePosition = Pointer.current != null ? Pointer.current.position.ReadValue() : new Vector2(-1, -1); + if (Mouse.current != null) + rightClickPressed = Mouse.current.rightButton.isPressed; + if (Keyboard.current != null) + endKeyPressed = Keyboard.current.endKey.isPressed; +#else + mousePosition = Input.mousePosition; + rightClickPressed = Input.GetMouseButton(1); + endKeyPressed = Input.GetKey(KeyCode.End); +#endif + + if (mousePosition.x < 0 + || mousePosition.y < 0 + || mousePosition.x > Screen.width + || mousePosition.y > Screen.height) return; - instance.m_mousePosition = Input.mousePosition; + instance.m_mousePosition = mousePosition; instance.m_mousePosition.y = Screen.height - instance.m_mousePosition.y; - if (Input.GetMouseButton(1)) + if (rightClickPressed) instance.m_MouseClickPosition = instance.m_mousePosition; - if (Input.GetKey(KeyCode.End)) + if (endKeyPressed) instance.m_MouseClickPosition = instance.m_mousePosition; } } @@ -139,7 +161,7 @@ public Vector2 GetMousePosition(float ScreenHeight, bool sceneView) // In play mode, Input.mousecoords matches the position in the game view if (EditorApplication.isPlayingOrWillChangePlaymode) { - return Input.mousePosition; + return GetInputMousePosition(); } else { @@ -151,6 +173,15 @@ public Vector2 GetMousePosition(float ScreenHeight, bool sceneView) } #else // In app mode, we only use the Input.mousecoords + return GetInputMousePosition(); +#endif + } + + Vector2 GetInputMousePosition() + { +#if USE_INPUT_SYSTEM + return Pointer.current != null ? Pointer.current.position.ReadValue() : new Vector2(-1, -1); +#else return Input.mousePosition; #endif } diff --git a/Runtime/Debugging/Prefabs/Resources/DebugUI Canvas.prefab b/Runtime/Debugging/Prefabs/Resources/DebugUI Canvas.prefab index cfb99ee..379448b 100644 --- a/Runtime/Debugging/Prefabs/Resources/DebugUI Canvas.prefab +++ b/Runtime/Debugging/Prefabs/Resources/DebugUI Canvas.prefab @@ -111,8 +111,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 76db615e524a19c4990482d75a475543, type: 3} m_Name: m_EditorClassIdentifier: - panelPrefab: {fileID: 224481716535368988, guid: daa46a58178a6ad41ae1ddc2dc7f856d, - type: 3} + panelPrefab: {fileID: 224481716535368988, guid: daa46a58178a6ad41ae1ddc2dc7f856d, type: 3} prefabs: - type: UnityEngine.Rendering.DebugUI+Value, Unity.RenderPipelines.Core.Runtime, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null @@ -161,12 +160,16 @@ MonoBehaviour: prefab: {fileID: 224284813447651300, guid: 38a07789c9e87004dad98c2909f58369, type: 3} - type: UnityEngine.Rendering.DebugUI+BitField, Unity.RenderPipelines.Core.Runtime, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null - prefab: {fileID: 5833802642077810669, guid: 7c78b588b2e1f7c4a86ca4a985cf6e4a, - type: 3} + prefab: {fileID: 5833802642077810669, guid: 7c78b588b2e1f7c4a86ca4a985cf6e4a, type: 3} - type: UnityEngine.Rendering.DebugUI+HistoryBoolField, Unity.RenderPipelines.Core.Runtime, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null prefab: {fileID: 108402283379224504, guid: 5088d0220f0c4df439cf06c5c270eacb, type: 3} - type: UnityEngine.Rendering.DebugUI+HistoryEnumField, Unity.RenderPipelines.Core.Runtime, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null - prefab: {fileID: 8535926254376877601, guid: b2da6b27df236b144b3516ed8e7d36ac, - type: 3} + prefab: {fileID: 8535926254376877601, guid: b2da6b27df236b144b3516ed8e7d36ac, type: 3} + - type: UnityEngine.Rendering.DebugUI+Table, Unity.RenderPipelines.Core.Runtime, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + prefab: {fileID: 224284813447651300, guid: 38a07789c9e87004dad98c2909f58369, type: 3} + - type: UnityEngine.Rendering.DebugUI+Table+Row, Unity.RenderPipelines.Core.Runtime, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + prefab: {fileID: 224053494956566916, guid: 2d019437ff89b8d44949727731cd9357, type: 3} diff --git a/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerColor.cs b/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerColor.cs index 836f25d..b8ffeea 100644 --- a/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerColor.cs +++ b/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerColor.cs @@ -141,7 +141,7 @@ public override void OnAction() valueToggle.isOn = !valueToggle.isOn; } - void UpdateColor() + internal void UpdateColor() { if (colorImage != null) colorImage.color = m_Field.GetValue(); diff --git a/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerRow.cs b/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerRow.cs new file mode 100644 index 0000000..9fab5ed --- /dev/null +++ b/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerRow.cs @@ -0,0 +1,81 @@ +using UnityEngine.UI; + +namespace UnityEngine.Rendering.UI +{ + /// + /// DebugUIHandler for row widget. + /// + public class DebugUIHandlerRow : DebugUIHandlerFoldout + { + float m_Timer; + + /// + /// OnEnable implementation. + /// + protected override void OnEnable() + { + m_Timer = 0f; + } + + /// + /// Update implementation. + /// + protected void Update() + { + var row = CastWidget(); + var table = row.parent as DebugUI.Table; + + float refreshRate = 0.1f; + bool refreshRow = m_Timer >= refreshRate; + if (refreshRow) + m_Timer -= refreshRate; + m_Timer += Time.deltaTime; + + for (int i = 0; i < row.children.Count; i++) + { + var child = gameObject.transform.GetChild(1).GetChild(i).gameObject; + var active = table.GetColumnVisibility(i); + child.SetActive(active); + if (active && refreshRow) + { + if (child.TryGetComponent(out var color)) + color.UpdateColor(); + if (child.TryGetComponent(out var toggle)) + toggle.UpdateValueLabel(); + } + } + + // Update previous and next ui handlers to pass over hidden volumes + var item = gameObject.transform.GetChild(1).GetChild(0).gameObject; + var itemWidget = item.GetComponent(); + DebugUIHandlerWidget previous = null; + for (int i = 0; i < row.children.Count; i++) + { + itemWidget.previousUIHandler = previous; + if (table.GetColumnVisibility(i)) + previous = itemWidget; + + bool found = false; + for (int j = i + 1; j < row.children.Count; j++) + { + if (table.GetColumnVisibility(j)) + { + var child = gameObject.transform.GetChild(1).GetChild(j).gameObject; + var childWidget = child.GetComponent(); + itemWidget.nextUIHandler = childWidget; + item = child; + itemWidget = childWidget; + i = j - 1; + found = true; + break; + } + } + if (!found) + { + itemWidget.nextUIHandler = null; + break; + } + } + } + } +} diff --git a/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerRow.cs.meta b/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerRow.cs.meta new file mode 100644 index 0000000..fd5a51a --- /dev/null +++ b/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerRow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4822e5675c12bf14d93b254d27ec8bd7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerToggle.cs b/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerToggle.cs index b040a65..073166c 100644 --- a/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerToggle.cs +++ b/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerToggle.cs @@ -59,7 +59,7 @@ public override void OnAction() /// /// Update the label. /// - protected virtual void UpdateValueLabel() + internal protected virtual void UpdateValueLabel() { if (valueToggle != null) valueToggle.isOn = m_Field.GetValue(); diff --git a/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerToggleHistory.cs b/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerToggleHistory.cs index b24f4bf..44d60b3 100644 --- a/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerToggleHistory.cs +++ b/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerToggleHistory.cs @@ -36,7 +36,7 @@ internal override void SetWidget(DebugUI.Widget widget) /// /// Update the label. /// - protected override void UpdateValueLabel() + internal protected override void UpdateValueLabel() { base.UpdateValueLabel(); DebugUI.HistoryBoolField field = m_Field as DebugUI.HistoryBoolField; diff --git a/Runtime/Debugging/Prefabs/Widgets/DebugUI Row.prefab b/Runtime/Debugging/Prefabs/Widgets/DebugUI Row.prefab new file mode 100644 index 0000000..3399ff4 --- /dev/null +++ b/Runtime/Debugging/Prefabs/Widgets/DebugUI Row.prefab @@ -0,0 +1,545 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1117777935091328 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 224404899962668226} + - component: {fileID: 222292134107675628} + - component: {fileID: 114934813895219466} + m_Layer: 5 + m_Name: Arrow Closed + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &224404899962668226 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1117777935091328} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 224398617048880834} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 0, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 11, y: 11} + m_Pivot: {x: 0, y: 0.5} +--- !u!222 &222292134107675628 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1117777935091328} + m_CullTransparentMesh: 0 +--- !u!114 &114934813895219466 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1117777935091328} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 7a0568d5e3330b84687e307992be3030, type: 3} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &1157130882260826 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 224091637017424492} + - component: {fileID: 114246251359002098} + - component: {fileID: 114721609938004740} + - component: {fileID: 222580990534994246} + - component: {fileID: 114267363758275858} + m_Layer: 5 + m_Name: Content + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!224 &224091637017424492 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1157130882260826} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 224053494956566916} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 590, y: -50} + m_SizeDelta: {x: 590, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &114246251359002098 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1157130882260826} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 25 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_ChildAlignment: 0 + m_Spacing: 1 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 0 + m_ChildControlWidth: 1 + m_ChildControlHeight: 0 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 +--- !u!114 &114721609938004740 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1157130882260826} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalFit: 0 + m_VerticalFit: 1 +--- !u!222 &222580990534994246 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1157130882260826} + m_CullTransparentMesh: 0 +--- !u!114 &114267363758275858 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1157130882260826} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0, g: 0, b: 0, a: 0.2509804} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 127279d577f25ac4ea17dae3782e5074, type: 3} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &1675372956212332 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 224861680556189892} + - component: {fileID: 222407094714348334} + - component: {fileID: 114534572167021932} + m_Layer: 5 + m_Name: Arrow Opened + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!224 &224861680556189892 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1675372956212332} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 224398617048880834} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 0, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 11, y: 11} + m_Pivot: {x: 0, y: 0.5} +--- !u!222 &222407094714348334 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1675372956212332} + m_CullTransparentMesh: 0 +--- !u!114 &114534572167021932 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1675372956212332} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: a674720496c1ed248a5b7ea3e22a11fd, type: 3} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &1741108581676328 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 224398617048880834} + - component: {fileID: 114589844970474540} + m_Layer: 5 + m_Name: Header + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &224398617048880834 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1741108581676328} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 224861680556189892} + - {fileID: 224404899962668226} + - {fileID: 224133929923872250} + m_Father: {fileID: 224053494956566916} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 590, y: 26} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &114589844970474540 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1741108581676328} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e4786b5477cac0a42855b21fdaa2242f, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 0 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Highlighted + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 0} + toggleTransition: 0 + graphic: {fileID: 0} + m_Group: {fileID: 0} + onValueChanged: + m_PersistentCalls: + m_Calls: [] + m_IsOn: 0 + content: {fileID: 1157130882260826} + arrowOpened: {fileID: 1675372956212332} + arrowClosed: {fileID: 1117777935091328} +--- !u!1 &1880654171993120 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 224053494956566916} + - component: {fileID: 114903677526224182} + - component: {fileID: 114617406789257194} + - component: {fileID: 114488953024160460} + - component: {fileID: 114624457690215086} + m_Layer: 0 + m_Name: DebugUI Row + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &224053494956566916 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1880654171993120} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 224398617048880834} + - {fileID: 224091637017424492} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 295, y: 0} + m_SizeDelta: {x: 590, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &114903677526224182 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1880654171993120} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_ChildAlignment: 0 + m_Spacing: 0 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 1 + m_ChildControlWidth: 1 + m_ChildControlHeight: 0 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 +--- !u!114 &114617406789257194 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1880654171993120} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalFit: 0 + m_VerticalFit: 1 +--- !u!114 &114488953024160460 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1880654171993120} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2bd470ffc0c0fe54faddbf8d466bf519, type: 3} + m_Name: + m_EditorClassIdentifier: + contentHolder: {fileID: 224091637017424492} +--- !u!114 &114624457690215086 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1880654171993120} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4822e5675c12bf14d93b254d27ec8bd7, type: 3} + m_Name: + m_EditorClassIdentifier: + colorDefault: {r: 0.8, g: 0.8, b: 0.8, a: 1} + colorSelected: {r: 0.25, g: 0.65, b: 0.8, a: 1} + nameLabel: {fileID: 114398791307483412} + valueToggle: {fileID: 114589844970474540} +--- !u!1 &1887383709356810 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 224133929923872250} + - component: {fileID: 222546040197109316} + - component: {fileID: 114398791307483412} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &224133929923872250 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1887383709356810} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 224398617048880834} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 10, y: 0} + m_SizeDelta: {x: -20, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &222546040197109316 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1887383709356810} + m_CullTransparentMesh: 0 +--- !u!114 &114398791307483412 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1887383709356810} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.8, g: 0.8, b: 0.8, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: 74a5091d8707f334b9a5c31ef71a64ba, type: 3} + m_FontSize: 16 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 1 + m_MaxSize: 40 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 0 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 'Foldout + +' diff --git a/Runtime/Debugging/Prefabs/Widgets/DebugUI Row.prefab.meta b/Runtime/Debugging/Prefabs/Widgets/DebugUI Row.prefab.meta new file mode 100644 index 0000000..cd3a08f --- /dev/null +++ b/Runtime/Debugging/Prefabs/Widgets/DebugUI Row.prefab.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2d019437ff89b8d44949727731cd9357 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 100100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Debugging/ProfilingScope.cs b/Runtime/Debugging/ProfilingScope.cs index 4b65031..ef63968 100644 --- a/Runtime/Debugging/ProfilingScope.cs +++ b/Runtime/Debugging/ProfilingScope.cs @@ -4,7 +4,7 @@ //#define USE_UNSAFE #if UNITY_2020_1_OR_NEWER -//#define UNITY_USE_RECORDER // Temporarily commented out until a crash is fixed in GPU profiling samplers. +#define UNITY_USE_RECORDER #endif using System; @@ -98,6 +98,42 @@ public ProfilingSampler(string name) #endif } + /// + /// Begin the profiling block. + /// + /// Command buffer used by the profiling block. + public void Begin(CommandBuffer cmd) + { + if (cmd != null) +#if UNITY_USE_RECORDER + if (sampler != null && sampler.isValid) + cmd.BeginSample(sampler); + else + cmd.BeginSample(name); +#else + cmd.BeginSample(name); +#endif + inlineSampler?.Begin(); + } + + /// + /// End the profiling block. + /// + /// Command buffer used by the profiling block. + public void End(CommandBuffer cmd) + { + if (cmd != null) +#if UNITY_USE_RECORDER + if (sampler != null && sampler.isValid) + cmd.EndSample(sampler); + else + cmd.EndSample(name); +#else + m_Cmd.EndSample(name); +#endif + inlineSampler?.End(); + } + internal bool IsValid() { return (sampler != null && inlineSampler != null); } internal CustomSampler sampler { get; private set; } @@ -187,11 +223,9 @@ public bool enableRecording /// public struct ProfilingScope : IDisposable { - string m_Name; - CommandBuffer m_Cmd; - bool m_Disposed; - CustomSampler m_Sampler; - CustomSampler m_InlineSampler; + CommandBuffer m_Cmd; + bool m_Disposed; + ProfilingSampler m_Sampler; /// /// Profiling Scope constructor @@ -200,28 +234,17 @@ public struct ProfilingScope : IDisposable /// Profiling Sampler to be used for this scope. public ProfilingScope(CommandBuffer cmd, ProfilingSampler sampler) { + // NOTE: Do not mix with named CommandBuffers. + // Currently there's an issue which results in mismatched markers. + // The named CommandBuffer will close its "profiling scope" on execution. + // That will orphan ProfilingScope markers as the named CommandBuffer marker + // is their "parent". + // Resulting in following pattern: + // exec(cmd.start, scope.start, cmd.end) and exec(cmd.start, scope.end, cmd.end) m_Cmd = cmd; m_Disposed = false; - if (sampler != null) - { - m_Name = sampler.name; // Don't use CustomSampler.name because it causes garbage - m_Sampler = sampler.sampler; - m_InlineSampler = sampler.inlineSampler; - } - else - { - m_Name = "NullProfilingSampler"; // Don't use CustomSampler.name because it causes garbage - m_Sampler = null; - m_InlineSampler = null; - } - - if (cmd != null) -#if UNITY_USE_RECORDER - cmd.BeginSample(m_Sampler); -#else - cmd.BeginSample(m_Name); -#endif - m_InlineSampler?.Begin(); + m_Sampler = sampler; + m_Sampler?.Begin(m_Cmd); } /// @@ -243,13 +266,7 @@ void Dispose(bool disposing) // this but will generate garbage on every frame (and this struct is used quite a lot). if (disposing) { - if (m_Cmd != null) -#if UNITY_USE_RECORDER - m_Cmd.EndSample(m_Sampler); -#else - m_Cmd.EndSample(m_Name); -#endif - m_InlineSampler?.End(); + m_Sampler?.End(m_Cmd); } m_Disposed = true; diff --git a/Runtime/Documentation.cs b/Runtime/Documentation.cs index 6ed0cd2..3823a2c 100644 --- a/Runtime/Documentation.cs +++ b/Runtime/Documentation.cs @@ -4,17 +4,30 @@ namespace UnityEngine.Rendering { + //We need to have only one version number amongst packages (so public) + /// + /// Documentation Info class. + /// + public class DocumentationInfo + { + //Update this field when upgrading the target Documentation for the package + //Should be linked to the package version somehow. + /// + /// Current version of the documentation. + /// + public const string version = "10.1"; + } + //Need to live in Runtime as Attribute of documentation is on Runtime classes \o/ /// /// Documentation class. /// - internal class Documentation + class Documentation : DocumentationInfo { //This must be used like //[HelpURL(Documentation.baseURL + Documentation.version + Documentation.subURL + "some-page" + Documentation.endURL)] //It cannot support String.Format nor string interpolation internal const string baseURL = "https://docs.unity3d.com/Packages/com.unity.render-pipelines.core@"; - internal const string version = "8.2"; internal const string subURL = "/manual/"; internal const string endURL = ".html"; diff --git a/Runtime/LookDev/IDataProvider.cs b/Runtime/LookDev/IDataProvider.cs index f48f7c5..37edab0 100644 --- a/Runtime/LookDev/IDataProvider.cs +++ b/Runtime/LookDev/IDataProvider.cs @@ -46,6 +46,12 @@ public interface IDataProvider /// /// Access element of the LookDev's scene void OnEndRendering(StageRuntimeInterface stage); + + /// + /// Callback called to do any necessary cleanup. + /// + /// Access element of the LookDev's scene + void Cleanup(StageRuntimeInterface SRI); } /// diff --git a/Runtime/RenderGraph/RenderGraph.cs b/Runtime/RenderGraph/RenderGraph.cs index 813ec9f..40ae04e 100644 --- a/Runtime/RenderGraph/RenderGraph.cs +++ b/Runtime/RenderGraph/RenderGraph.cs @@ -1,7 +1,7 @@ using System; +using System.Diagnostics; using System.Collections.Generic; using UnityEngine.Rendering; -using UnityEngine.Profiling; namespace UnityEngine.Experimental.Rendering.RenderGraphModule { @@ -20,9 +20,9 @@ public enum DepthAccess } /// - /// This struct specifies the context given to every render pass. + /// This class specifies the context given to every render pass. /// - public ref struct RenderGraphContext + public class RenderGraphContext { ///Scriptable Render Context used for rendering. public ScriptableRenderContext renderContext; @@ -30,51 +30,66 @@ public enum DepthAccess public CommandBuffer cmd; ///Render Graph pooll used for temporary data. public RenderGraphObjectPool renderGraphPool; - ///Render Graph Resource Registry used for accessing resources. - public RenderGraphResourceRegistry resources; + ///Render Graph default resources. + public RenderGraphDefaultResources defaultResources; } /// /// This struct contains properties which control the execution of the Render Graph. /// - public struct RenderGraphExecuteParams + public struct RenderGraphParameters { - ///Rendering width. - public int renderingWidth; - ///Rendering height. - public int renderingHeight; - ///Number of MSAA samples. - public MSAASamples msaaSamples; + ///Index of the current frame being rendered. + public int currentFrameIndex; + ///Scriptable Render Context used by the render pipeline. + public ScriptableRenderContext scriptableRenderContext; + ///Command Buffer used to execute graphic commands. + public CommandBuffer commandBuffer; } class RenderGraphDebugParams { - public bool enableRenderGraph = false; // TODO: TEMP TO REMOVE - public bool tagResourceNamesWithRG; public bool clearRenderTargetsAtCreation; public bool clearRenderTargetsAtRelease; - public bool unbindGlobalTextures; + public bool disablePassCulling; + public bool immediateMode; public bool logFrameInformation; public bool logResources; - public void RegisterDebug() + public void RegisterDebug(string name) { var list = new List(); - list.Add(new DebugUI.BoolField { displayName = "Enable Render Graph", getter = () => enableRenderGraph, setter = value => enableRenderGraph = value }); - list.Add(new DebugUI.BoolField { displayName = "Tag Resources with RG", getter = () => tagResourceNamesWithRG, setter = value => tagResourceNamesWithRG = value }); list.Add(new DebugUI.BoolField { displayName = "Clear Render Targets at creation", getter = () => clearRenderTargetsAtCreation, setter = value => clearRenderTargetsAtCreation = value }); - list.Add(new DebugUI.BoolField { displayName = "Clear Render Targets at release", getter = () => clearRenderTargetsAtRelease, setter = value => clearRenderTargetsAtRelease = value }); - list.Add(new DebugUI.BoolField { displayName = "Unbind Global Textures", getter = () => unbindGlobalTextures, setter = value => unbindGlobalTextures = value }); - list.Add(new DebugUI.Button { displayName = "Log Frame Information", action = () => logFrameInformation = true }); - list.Add(new DebugUI.Button { displayName = "Log Resources", action = () => logResources = true }); + // We cannot expose this option as it will change the active render target and the debug menu won't know where to render itself anymore. + // list.Add(new DebugUI.BoolField { displayName = "Clear Render Targets at release", getter = () => clearRenderTargetsAtRelease, setter = value => clearRenderTargetsAtRelease = value }); + list.Add(new DebugUI.BoolField { displayName = "Disable Pass Culling", getter = () => disablePassCulling, setter = value => disablePassCulling = value }); + list.Add(new DebugUI.BoolField { displayName = "Immediate Mode", getter = () => immediateMode, setter = value => immediateMode = value }); + list.Add(new DebugUI.Button { displayName = "Log Frame Information", + action = () => + { + logFrameInformation = true; + #if UNITY_EDITOR + UnityEditor.SceneView.RepaintAll(); + #endif + } + }); + list.Add(new DebugUI.Button { displayName = "Log Resources", + action = () => + { + logResources = true; + #if UNITY_EDITOR + UnityEditor.SceneView.RepaintAll(); + #endif + } + }); - var testPanel = DebugManager.instance.GetPanel("Render Graph", true); - testPanel.children.Add(list.ToArray()); + var panel = DebugManager.instance.GetPanel(name.Length == 0 ? "Render Graph" : name, true); + panel.children.Add(list.ToArray()); } - public void UnRegisterDebug() + public void UnRegisterDebug(string name) { - DebugManager.instance.RemovePanel("Render Graph"); + DebugManager.instance.RemovePanel(name.Length == 0 ? "Render Graph" : name); } } @@ -94,122 +109,150 @@ public class RenderGraph ///Maximum number of MRTs supported by Render Graph. public static readonly int kMaxMRTCount = 8; - internal abstract class RenderPass - { - internal RenderFunc GetExecuteDelegate() - where PassData : class, new() => ((RenderPass)this).renderFunc; - - internal abstract void Execute(RenderGraphContext renderGraphContext); - internal abstract void Release(RenderGraphContext renderGraphContext); - internal abstract bool HasRenderFunc(); - - internal string name; - internal int index; - internal ProfilingSampler customSampler; - internal List resourceReadList = new List(); - internal List resourceWriteList = new List(); - internal List usedRendererListList = new List(); - internal bool enableAsyncCompute; - internal RenderGraphMutableResource depthBuffer { get { return m_DepthBuffer; } } - internal RenderGraphMutableResource[] colorBuffers { get { return m_ColorBuffers; } } - internal int colorBufferMaxIndex { get { return m_MaxColorBufferIndex; } } - - protected RenderGraphMutableResource[] m_ColorBuffers = new RenderGraphMutableResource[RenderGraph.kMaxMRTCount]; - protected RenderGraphMutableResource m_DepthBuffer; - protected int m_MaxColorBufferIndex = -1; - - internal void Clear() - { - name = ""; - index = -1; - customSampler = null; - resourceReadList.Clear(); - resourceWriteList.Clear(); - usedRendererListList.Clear(); - enableAsyncCompute = false; - - // Invalidate everything - m_MaxColorBufferIndex = -1; - m_DepthBuffer = new RenderGraphMutableResource(); - for (int i = 0; i < RenderGraph.kMaxMRTCount; ++i) - { - m_ColorBuffers[i] = new RenderGraphMutableResource(); - } - } + internal struct CompiledResourceInfo + { + public List producers; + public List consumers; + public bool resourceCreated; + public int refCount; - internal void SetColorBuffer(in RenderGraphMutableResource resource, int index) + public void Reset() { - Debug.Assert(index < RenderGraph.kMaxMRTCount && index >= 0); - m_MaxColorBufferIndex = Math.Max(m_MaxColorBufferIndex, index); - m_ColorBuffers[index] = resource; - resourceWriteList.Add(resource); + if (producers == null) + producers = new List(); + if (consumers == null) + consumers = new List(); + + producers.Clear(); + consumers.Clear(); + resourceCreated = false; + refCount = 0; } + } - internal void SetDepthBuffer(in RenderGraphMutableResource resource, DepthAccess flags) + [DebuggerDisplay("RenderPass: {pass.name} (Index:{pass.index} Async:{enableAsyncCompute})")] + internal struct CompiledPassInfo + { + public RenderGraphPass pass; + public List[] resourceCreateList; + public List[] resourceReleaseList; + public int refCount; + public bool culled; + public bool hasSideEffect; + public int syncToPassIndex; // Index of the pass that needs to be waited for. + public int syncFromPassIndex; // Smaller pass index that waits for this pass. + public bool needGraphicsFence; + public GraphicsFence fence; + + public bool enableAsyncCompute; + public bool allowPassCulling { get { return pass.allowPassCulling; } } + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + // This members are only here to ease debugging. + public List[] debugResourceReads; + public List[] debugResourceWrites; +#endif + + public void Reset(RenderGraphPass pass) { - m_DepthBuffer = resource; - if ((flags | DepthAccess.Read) != 0) - resourceReadList.Add(resource); - if ((flags | DepthAccess.Write) != 0) - resourceWriteList.Add(resource); + this.pass = pass; + enableAsyncCompute = pass.enableAsyncCompute; - } - } + if (resourceCreateList == null) + { + resourceCreateList = new List[(int)RenderGraphResourceType.Count]; + resourceReleaseList = new List[(int)RenderGraphResourceType.Count]; + for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i) + { + resourceCreateList[i] = new List(); + resourceReleaseList[i] = new List(); + } - internal sealed class RenderPass : RenderPass - where PassData : class, new() - { - internal PassData data; - internal RenderFunc renderFunc; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + debugResourceReads = new List[(int)RenderGraphResourceType.Count]; + debugResourceWrites = new List[(int)RenderGraphResourceType.Count]; + for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i) + { + debugResourceReads[i] = new List(); + debugResourceWrites[i] = new List(); + } +#endif + } - internal override void Execute(RenderGraphContext renderGraphContext) - { - GetExecuteDelegate()(data, renderGraphContext); - } + for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i) + { + resourceCreateList[i].Clear(); + resourceReleaseList[i].Clear(); + } - internal override void Release(RenderGraphContext renderGraphContext) - { - Clear(); - renderGraphContext.renderGraphPool.Release(data); - data = null; - renderFunc = null; - renderGraphContext.renderGraphPool.Release(this); - } + refCount = 0; + culled = false; + hasSideEffect = false; + syncToPassIndex = -1; + syncFromPassIndex = -1; + needGraphicsFence = false; - internal override bool HasRenderFunc() - { - return renderFunc != null; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i) + { + debugResourceReads[i].Clear(); + debugResourceWrites[i].Clear(); + } +#endif } } - RenderGraphResourceRegistry m_Resources; - RenderGraphObjectPool m_RenderGraphPool = new RenderGraphObjectPool(); - List m_RenderPasses = new List(); - List m_RendererLists = new List(); - RenderGraphDebugParams m_DebugParameters = new RenderGraphDebugParams(); - RenderGraphLogger m_Logger = new RenderGraphLogger(); + string m_Name; + RenderGraphResourceRegistry m_Resources; + RenderGraphObjectPool m_RenderGraphPool = new RenderGraphObjectPool(); + List m_RenderPasses = new List(64); + List m_RendererLists = new List(32); + RenderGraphDebugParams m_DebugParameters = new RenderGraphDebugParams(); + RenderGraphLogger m_Logger = new RenderGraphLogger(); + RenderGraphDefaultResources m_DefaultResources = new RenderGraphDefaultResources(); + Dictionary m_DefaultProfilingSamplers = new Dictionary(); + bool m_ExecutionExceptionWasRaised; + RenderGraphContext m_RenderGraphContext = new RenderGraphContext(); + CommandBuffer m_PreviousCommandBuffer; + int m_CurrentImmediatePassIndex; + List[] m_ImmediateModeResourceList = new List[(int)RenderGraphResourceType.Count]; + + // Compiled Render Graph info. + DynamicArray[] m_CompiledResourcesInfos = new DynamicArray[(int)RenderGraphResourceType.Count]; + DynamicArray m_CompiledPassInfos = new DynamicArray(); + Stack m_CullingStack = new Stack(); + + int m_ExecutionCount; #region Public Interface /// - /// Returns true if rendering with Render Graph is enabled. + /// Set of default resources usable in a pass rendering code. /// - public bool enabled { get { return m_DebugParameters.enableRenderGraph; } } - - // TODO: Currently only needed by SSAO to sample correctly depth texture mips. Need to figure out a way to hide this behind a proper formalization. - /// - /// Gets the RTHandleProperties structure associated with the Render Graph's RTHandle System. - /// - public RTHandleProperties rtHandleProperties { get { return m_Resources.GetRTHandleProperties(); } } + public RenderGraphDefaultResources defaultResources + { + get + { + m_DefaultResources.InitializeForRendering(this); + return m_DefaultResources; + } + } /// /// Render Graph constructor. /// - /// Specify if this Render Graph should support MSAA. - /// Specify the initial sample count of MSAA render textures. - public RenderGraph(bool supportMSAA, MSAASamples initialSampleCount) + /// Optional name used to identify the render graph instnace. + public RenderGraph(string name = "") { - m_Resources = new RenderGraphResourceRegistry(supportMSAA, initialSampleCount, m_DebugParameters, m_Logger); + m_Name = name; + m_Resources = new RenderGraphResourceRegistry(m_DebugParameters, m_Logger); + + for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i) + { + m_CompiledResourcesInfos[i] = new DynamicArray(); + } + + m_DebugParameters.RegisterDebug(m_Name); } /// @@ -217,211 +260,797 @@ public RenderGraph(bool supportMSAA, MSAASamples initialSampleCount) /// public void Cleanup() { + m_DebugParameters.UnRegisterDebug(m_Name); m_Resources.Cleanup(); + m_DefaultResources.Cleanup(); } /// - /// Register this Render Graph to the debug window. + /// End frame processing. Purge resources that have been used since last frame and resets internal states. + /// This need to be called once per frame. /// - public void RegisterDebug() + public void EndFrame() { - //m_DebugParameters.RegisterDebug(); + //m_Resources.PurgeUnusedResources(); + m_DebugParameters.logFrameInformation = false; + m_DebugParameters.logResources = false; } /// - /// Unregister this Render Graph from the debug window. + /// Import an external texture to the Render Graph. + /// Any pass writing to an imported texture will be considered having side effects and can't be automatically culled. /// - public void UnRegisterDebug() + /// External RTHandle that needs to be imported. + /// A new TextureHandle. + public TextureHandle ImportTexture(RTHandle rt) { - //m_DebugParameters.UnRegisterDebug(); + return m_Resources.ImportTexture(rt); } /// - /// Resets the reference size of the internal RTHandle System. - /// This allows users to reduce the memory footprint of render textures after doing a super sampled rendering pass for example. + /// Import the final backbuffer to render graph. /// - /// New width of the internal RTHandle System. - /// New height of the internal RTHandle System. - public void ResetRTHandleReferenceSize(int width, int height) + /// Backbuffer render target identifier. + /// A new TextureHandle for the backbuffer. + public TextureHandle ImportBackbuffer(RenderTargetIdentifier rt) { - m_Resources.ResetRTHandleReferenceSize(width, height); + return m_Resources.ImportBackbuffer(rt); } /// - /// Import an external texture to the Render Graph. + /// Create a new Render Graph Texture resource. /// - /// External RTHandle that needs to be imported. - /// Optional property that allows you to specify a Shader property name to use for automatic resource binding. - /// A new RenderGraphMutableResource. - public RenderGraphMutableResource ImportTexture(RTHandle rt, int shaderProperty = 0) + /// Texture descriptor. + /// A new TextureHandle. + public TextureHandle CreateTexture(in TextureDesc desc) { - return m_Resources.ImportTexture(rt, shaderProperty); + return m_Resources.CreateTexture(desc); } /// - /// Create a new Render Graph Texture resource. + /// Create a new Render Graph Texture resource using the descriptor from another texture. /// - /// Texture descriptor. - /// Optional property that allows you to specify a Shader property name to use for automatic resource binding. - /// A new RenderGraphMutableResource. - public RenderGraphMutableResource CreateTexture(TextureDesc desc, int shaderProperty = 0) + /// Texture from which the descriptor should be used. + /// A new TextureHandle. + public TextureHandle CreateTexture(TextureHandle texture) { - if (m_DebugParameters.tagResourceNamesWithRG) - desc.name = string.Format("{0}_RenderGraph", desc.name); - return m_Resources.CreateTexture(desc, shaderProperty); + return m_Resources.CreateTexture(m_Resources.GetTextureResourceDesc(texture.handle)); } /// - /// Create a new Render Graph Texture resource using the descriptor from another texture. + /// Create a new Render Graph Texture if the passed handle is invalid and use said handle as output. + /// If the passed handle is valid, no texture is created. /// /// Texture from which the descriptor should be used. - /// Optional property that allows you to specify a Shader property name to use for automatic resource binding. - /// A new RenderGraphMutableResource. - public RenderGraphMutableResource CreateTexture(in RenderGraphResource texture, int shaderProperty = 0) + /// A new TextureHandle. + public void CreateTextureIfInvalid(in TextureDesc desc, ref TextureHandle texture) { - var desc = m_Resources.GetTextureResourceDesc(texture); - if (m_DebugParameters.tagResourceNamesWithRG) - desc.name = string.Format("{0}_RenderGraph", desc.name); - return m_Resources.CreateTexture(desc, shaderProperty); + if (!texture.IsValid()) + texture = m_Resources.CreateTexture(desc); } + /// /// Gets the descriptor of the specified Texture resource. /// - /// + /// Texture resource from which the descriptor is requested. /// The input texture descriptor. - public TextureDesc GetTextureDesc(in RenderGraphResource texture) + public TextureDesc GetTextureDesc(TextureHandle texture) { - if (texture.type != RenderGraphResourceType.Texture) - { - throw new ArgumentException("Trying to retrieve a TextureDesc from a resource that is not a texture."); - } - - return m_Resources.GetTextureResourceDesc(texture); + return m_Resources.GetTextureResourceDesc(texture.handle); } /// /// Creates a new Renderer List Render Graph resource. /// /// Renderer List descriptor. - /// A new RenderGraphResource. - public RenderGraphResource CreateRendererList(in RendererListDesc desc) + /// A new TextureHandle. + public RendererListHandle CreateRendererList(in RendererListDesc desc) { return m_Resources.CreateRendererList(desc); } /// - /// Add a new Render Pass to the current Render Graph. + /// Import an external Compute Buffer to the Render Graph + /// Any pass writing to an imported compute buffer will be considered having side effects and can't be automatically culled. + /// + /// External Compute Buffer that needs to be imported. + /// A new ComputeBufferHandle. + public ComputeBufferHandle ImportComputeBuffer(ComputeBuffer computeBuffer) + { + return m_Resources.ImportComputeBuffer(computeBuffer); + } + + /// + /// Create a new Render Graph Compute Buffer resource. + /// + /// Compute Buffer descriptor. + /// A new ComputeBufferHandle. + public ComputeBufferHandle CreateComputeBuffer(in ComputeBufferDesc desc) + { + return m_Resources.CreateComputeBuffer(desc); + } + + /// + /// Create a new Render Graph Compute Buffer resource using the descriptor from another compute buffer. + /// + /// Compute Buffer from which the descriptor should be used. + /// A new ComputeBufferHandle. + public ComputeBufferHandle CreateComputeBuffer(in ComputeBufferHandle computeBuffer) + { + return m_Resources.CreateComputeBuffer(m_Resources.GetComputeBufferResourceDesc(computeBuffer.handle)); + } + + /// + /// Gets the descriptor of the specified Compute Buffer resource. + /// + /// Compute Buffer resource from which the descriptor is requested. + /// The input compute buffer descriptor. + public ComputeBufferDesc GetComputeBufferDesc(in ComputeBufferHandle computeBuffer) + { + return m_Resources.GetComputeBufferResourceDesc(computeBuffer.handle); + } + + /// + /// Add a new Render Pass to the Render Graph. /// /// Type of the class to use to provide data to the Render Pass. /// Name of the new Render Pass (this is also be used to generate a GPU profiling marker). /// Instance of PassData that is passed to the render function and you must fill. - /// Optional profiling sampler. + /// Profiling sampler used around the pass. /// A new instance of a RenderGraphBuilder used to setup the new Render Pass. - public RenderGraphBuilder AddRenderPass(string passName, out PassData passData, ProfilingSampler sampler = null) where PassData : class, new() + public RenderGraphBuilder AddRenderPass(string passName, out PassData passData, ProfilingSampler sampler) where PassData : class, new() { - var renderPass = m_RenderGraphPool.Get>(); - renderPass.Clear(); - renderPass.index = m_RenderPasses.Count; - renderPass.data = m_RenderGraphPool.Get(); - renderPass.name = passName; - renderPass.customSampler = sampler; + var renderPass = m_RenderGraphPool.Get>(); + renderPass.Initialize(m_RenderPasses.Count, m_RenderGraphPool.Get(), passName, sampler); passData = renderPass.data; m_RenderPasses.Add(renderPass); - return new RenderGraphBuilder(renderPass, m_Resources); + return new RenderGraphBuilder(renderPass, m_Resources, this); } /// - /// Execute the Render Graph in its current state. + /// Add a new Render Pass to the Render Graph. /// - /// ScriptableRenderContext used to execute Scriptable Render Pipeline. - /// Command Buffer used for Render Passes rendering. - /// Render Graph execution parameters. - public void Execute(ScriptableRenderContext renderContext, CommandBuffer cmd, in RenderGraphExecuteParams parameters) + /// Type of the class to use to provide data to the Render Pass. + /// Name of the new Render Pass (this is also be used to generate a GPU profiling marker). + /// Instance of PassData that is passed to the render function and you must fill. + /// A new instance of a RenderGraphBuilder used to setup the new Render Pass. + public RenderGraphBuilder AddRenderPass(string passName, out PassData passData) where PassData : class, new() + { + return AddRenderPass(passName, out passData, GetDefaultProfilingSampler(passName)); + } + + /// + /// Begin using the render graph. + /// This must be called before adding any pass to the render graph. + /// + /// Parameters necessary for the render graph execution. + public void Begin(in RenderGraphParameters parameters) { + m_ExecutionCount++; + m_Logger.Initialize(); - // Update RTHandleSystem with size for this rendering pass. - m_Resources.SetRTHandleReferenceSize(parameters.renderingWidth, parameters.renderingHeight, parameters.msaaSamples); + m_Resources.BeginRender(parameters.currentFrameIndex, m_ExecutionCount); - LogFrameInformation(parameters.renderingWidth, parameters.renderingHeight); + m_RenderGraphContext.cmd = parameters.commandBuffer; + m_RenderGraphContext.renderContext = parameters.scriptableRenderContext; + m_RenderGraphContext.renderGraphPool = m_RenderGraphPool; + m_RenderGraphContext.defaultResources = m_DefaultResources; - // First pass, traversal and pruning - for (int passIndex = 0; passIndex < m_RenderPasses.Count; ++passIndex) + if (m_DebugParameters.immediateMode) { - var pass = m_RenderPasses[passIndex]; + LogFrameInformation(); - // TODO: Pruning + // Prepare the list of compiled pass info for immediate mode. + // Conservative resize because we don't know how many passes there will be. + // We might still need to grow the array later on anyway if it's not enough. + m_CompiledPassInfos.Resize(m_CompiledPassInfos.capacity); + m_CurrentImmediatePassIndex = 0; - // Gather all renderer lists - m_RendererLists.AddRange(pass.usedRendererListList); - } + for(int i = 0; i < (int)RenderGraphResourceType.Count; ++i) + { + if (m_ImmediateModeResourceList[i] == null) + m_ImmediateModeResourceList[i] = new List(); - // Creates all renderer lists - m_Resources.CreateRendererLists(m_RendererLists); - LogRendererListsCreation(); + m_ImmediateModeResourceList[i].Clear(); + } + } + } - // Second pass, execution - RenderGraphContext rgContext = new RenderGraphContext(); - rgContext.cmd = cmd; - rgContext.renderContext = renderContext; - rgContext.renderGraphPool = m_RenderGraphPool; - rgContext.resources = m_Resources; + /// + /// Execute the Render Graph in its current state. + /// + public void Execute() + { + m_ExecutionExceptionWasRaised = false; try { - for (int passIndex = 0; passIndex < m_RenderPasses.Count; ++passIndex) + if (m_RenderGraphContext.cmd == null) + throw new InvalidOperationException("RenderGraph.Begin was not called before executing the render graph."); + + if (!m_DebugParameters.immediateMode) + { + LogFrameInformation(); + + CompileRenderGraph(); + ExecuteRenderGraph(); + } + } + catch (Exception e) + { + Debug.LogError("Render Graph Execution error"); + if (!m_ExecutionExceptionWasRaised) // Already logged. TODO: There is probably a better way in C# to handle that. + Debug.LogException(e); + m_ExecutionExceptionWasRaised = true; + } + finally + { + if (m_DebugParameters.immediateMode) + ReleaseImmediateModeResources(); + + ClearCompiledGraph(); + + if (m_DebugParameters.logFrameInformation || m_DebugParameters.logResources) + Debug.Log(m_Logger.GetLog()); + + m_Resources.EndRender(); + + InvalidateContext(); + } + } + + + class ProfilingScopePassData + { + public ProfilingSampler sampler; + } + + /// + /// Begin a profiling scope. + /// + /// Sampler used for profiling. + public void BeginProfilingSampler(ProfilingSampler sampler) + { + using (var builder = AddRenderPass("BeginProfile", out var passData, null)) + { + passData.sampler = sampler; + builder.AllowPassCulling(false); + builder.SetRenderFunc((ProfilingScopePassData data, RenderGraphContext ctx) => { - var pass = m_RenderPasses[passIndex]; + data.sampler.Begin(ctx.cmd); + }); + } + } + + /// + /// End a profiling scope. + /// + /// Sampler used for profiling. + public void EndProfilingSampler(ProfilingSampler sampler) + { + using (var builder = AddRenderPass("EndProfile", out var passData, null)) + { + passData.sampler = sampler; + builder.AllowPassCulling(false); + builder.SetRenderFunc((ProfilingScopePassData data, RenderGraphContext ctx) => + { + data.sampler.End(ctx.cmd); + }); + } + } + #endregion + + #region Private Interface + + // Internal for testing purpose only + internal DynamicArray GetCompiledPassInfos() { return m_CompiledPassInfos; } + + // Internal for testing purpose only + internal void ClearCompiledGraph() + { + ClearRenderPasses(); + m_Resources.Clear(m_ExecutionExceptionWasRaised); + m_DefaultResources.Clear(); + m_RendererLists.Clear(); + for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i) + m_CompiledResourcesInfos[i].Clear(); + m_CompiledPassInfos.Clear(); + } + + void InvalidateContext() + { + m_RenderGraphContext.cmd = null; + m_RenderGraphContext.renderGraphPool = null; + m_RenderGraphContext.defaultResources = null; + } + + internal void OnPassAdded(RenderGraphPass pass) + { + if (m_DebugParameters.immediateMode) + { + ExecutePassImmediatly(pass); + } + } - if (!pass.HasRenderFunc()) + void InitResourceInfosData(DynamicArray resourceInfos, int count) + { + resourceInfos.Resize(count); + for (int i = 0; i < resourceInfos.size; ++i) + resourceInfos[i].Reset(); + } + + void InitializeCompilationData() + { + InitResourceInfosData(m_CompiledResourcesInfos[(int)RenderGraphResourceType.Texture], m_Resources.GetTextureResourceCount()); + InitResourceInfosData(m_CompiledResourcesInfos[(int)RenderGraphResourceType.ComputeBuffer], m_Resources.GetComputeBufferResourceCount()); + + m_CompiledPassInfos.Resize(m_RenderPasses.Count); + for (int i = 0; i < m_CompiledPassInfos.size; ++i) + m_CompiledPassInfos[i].Reset(m_RenderPasses[i]); + } + + void CountReferences() + { + for (int passIndex = 0; passIndex < m_CompiledPassInfos.size; ++passIndex) + { + ref CompiledPassInfo passInfo = ref m_CompiledPassInfos[passIndex]; + + for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type) + { + var resourceRead = passInfo.pass.resourceReadLists[type]; + foreach (var resource in resourceRead) { - throw new InvalidOperationException(string.Format("RenderPass {0} was not provided with an execute function.", pass.name)); + ref CompiledResourceInfo info = ref m_CompiledResourcesInfos[type][resource]; + info.consumers.Add(passIndex); + info.refCount++; + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + passInfo.debugResourceReads[type].Add(m_Resources.GetResourceName(resource)); +#endif } - using (new ProfilingScope(cmd, pass.customSampler)) + var resourceWrite = passInfo.pass.resourceWriteLists[type]; + foreach (var resource in resourceWrite) { - LogRenderPassBegin(pass); - using (new RenderGraphLogIndent(m_Logger)) + ref CompiledResourceInfo info = ref m_CompiledResourcesInfos[type][resource]; + info.producers.Add(passIndex); + passInfo.refCount++; + + // Writing to an imported texture is considered as a side effect because we don't know what users will do with it outside of render graph. + if (m_Resources.IsResourceImported(resource)) + passInfo.hasSideEffect = true; + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + passInfo.debugResourceWrites[type].Add(m_Resources.GetResourceName(resource)); +#endif + } + + foreach (int resourceIndex in passInfo.pass.transientResourceList[type]) + { + ref CompiledResourceInfo info = ref m_CompiledResourcesInfos[type][resourceIndex]; + info.refCount++; + info.consumers.Add(passIndex); + info.producers.Add(passIndex); + } + } + } + } + + void CullOutputlessPasses() + { + // Gather passes that don't produce anything and cull them. + m_CullingStack.Clear(); + for (int pass = 0; pass < m_CompiledPassInfos.size; ++pass) + { + ref CompiledPassInfo passInfo = ref m_CompiledPassInfos[pass]; + + if (passInfo.refCount == 0 && !passInfo.hasSideEffect && passInfo.allowPassCulling) + { + // Producer is not necessary as it produces zero resources + // Cull it and decrement refCount of all the resources it reads. + // We don't need to go recursively here because we decrement ref count of read resources + // so the subsequent passes of culling will detect those and remove the related passes. + passInfo.culled = true; + for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type) + { + foreach (var index in passInfo.pass.resourceReadLists[type]) { - PreRenderPassExecute(passIndex, pass, rgContext); - pass.Execute(rgContext); - PostRenderPassExecute(passIndex, pass, rgContext); + m_CompiledResourcesInfos[type][index].refCount--; + } } } } - catch(Exception e) + } + + void CullUnusedPasses() + { + if (m_DebugParameters.disablePassCulling) { - Debug.LogError("Render Graph Execution error"); - Debug.LogException(e); + if (m_DebugParameters.logFrameInformation) + { + m_Logger.LogLine("- Pass Culling Disabled -\n"); + } + return; } - finally + + // TODO RENDERGRAPH: temporarily remove culling of passes without product. + // Many passes are used just to set global variables so we don't want to force users to disallow culling on those explicitly every time. + // This will cull passes with no outputs. + //CullOutputlessPasses(); + + // This will cull all passes that produce resource that are never read. + for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type) { - ClearRenderPasses(); - m_Resources.Clear(); - m_RendererLists.Clear(); + DynamicArray resourceUsageList = m_CompiledResourcesInfos[type]; - if (m_DebugParameters.logFrameInformation || m_DebugParameters.logResources) - Debug.Log(m_Logger.GetLog()); + // Gather resources that are never read. + m_CullingStack.Clear(); + for (int i = 0; i < resourceUsageList.size; ++i) + { + if (resourceUsageList[i].refCount == 0) + { + m_CullingStack.Push(i); + } + } - m_DebugParameters.logFrameInformation = false; - m_DebugParameters.logResources = false; + while (m_CullingStack.Count != 0) + { + var unusedResource = resourceUsageList[m_CullingStack.Pop()]; + foreach (var producerIndex in unusedResource.producers) + { + ref var producerInfo = ref m_CompiledPassInfos[producerIndex]; + producerInfo.refCount--; + if (producerInfo.refCount == 0 && !producerInfo.hasSideEffect && producerInfo.allowPassCulling) + { + // Producer is not necessary anymore as it produces zero resources + // Cull it and decrement refCount of all the textures it reads. + producerInfo.culled = true; + + foreach (var resourceIndex in producerInfo.pass.resourceReadLists[type]) + { + ref CompiledResourceInfo resourceInfo = ref resourceUsageList[resourceIndex]; + resourceInfo.refCount--; + // If a resource is not used anymore, add it to the stack to be processed in subsequent iteration. + if (resourceInfo.refCount == 0) + m_CullingStack.Push(resourceIndex); + } + } + } + } } + + LogCulledPasses(); } - #endregion - #region Internal Interface - private RenderGraph() + void UpdatePassSynchronization(ref CompiledPassInfo currentPassInfo, ref CompiledPassInfo producerPassInfo, int currentPassIndex, int lastProducer, ref int intLastSyncIndex) + { + // Current pass needs to wait for pass index lastProducer + currentPassInfo.syncToPassIndex = lastProducer; + // Update latest pass waiting for the other pipe. + intLastSyncIndex = lastProducer; + + // Producer will need a graphics fence that this pass will wait on. + producerPassInfo.needGraphicsFence = true; + // We update the producer pass with the index of the smallest pass waiting for it. + // This will be used to "lock" resource from being reused until the pipe has been synchronized. + if (producerPassInfo.syncFromPassIndex == -1) + producerPassInfo.syncFromPassIndex = currentPassIndex; + } + + void UpdateResourceSynchronization(ref int lastGraphicsPipeSync, ref int lastComputePipeSync, int currentPassIndex, in CompiledResourceInfo resource) + { + int lastProducer = GetLatestProducerIndex(currentPassIndex, resource); + if (lastProducer != -1) + { + ref CompiledPassInfo currentPassInfo = ref m_CompiledPassInfos[currentPassIndex]; + + //If the passes are on different pipes, we need synchronization. + if (m_CompiledPassInfos[lastProducer].enableAsyncCompute != currentPassInfo.enableAsyncCompute) + { + // Pass is on compute pipe, need sync with graphics pipe. + if (currentPassInfo.enableAsyncCompute) + { + if (lastProducer > lastGraphicsPipeSync) + { + UpdatePassSynchronization(ref currentPassInfo, ref m_CompiledPassInfos[lastProducer], currentPassIndex, lastProducer, ref lastGraphicsPipeSync); + } + } + else + { + if (lastProducer > lastComputePipeSync) + { + UpdatePassSynchronization(ref currentPassInfo, ref m_CompiledPassInfos[lastProducer], currentPassIndex, lastProducer, ref lastComputePipeSync); + } + } + } + } + } + + int GetLatestProducerIndex(int passIndex, in CompiledResourceInfo info) + { + // We want to know the highest pass index below the current pass that writes to the resource. + int result = -1; + foreach (var producer in info.producers) + { + // producers are by construction in increasing order. + if (producer < passIndex) + result = producer; + else + return result; + } + + return result; + } + + int GetLatestValidReadIndex(in CompiledResourceInfo info) { + if (info.consumers.Count == 0) + return -1; + var consumers = info.consumers; + for (int i = consumers.Count - 1; i >= 0; --i) + { + if (!m_CompiledPassInfos[consumers[i]].culled) + return consumers[i]; + } + + return -1; } - void PreRenderPassSetRenderTargets(in RenderPass pass, RenderGraphContext rgContext) + int GetFirstValidWriteIndex(in CompiledResourceInfo info) { + if (info.producers.Count == 0) + return -1; + + var producers = info.producers; + for (int i = 0; i < producers.Count; i++) + { + if (!m_CompiledPassInfos[producers[i]].culled) + return producers[i]; + } + + return -1; + } + + int GetLatestValidWriteIndex(in CompiledResourceInfo info) + { + if (info.producers.Count == 0) + return -1; + + var producers = info.producers; + for (int i = producers.Count - 1; i >= 0; --i) + { + if (!m_CompiledPassInfos[producers[i]].culled) + return producers[i]; + } + + return -1; + } + + + void UpdateResourceAllocationAndSynchronization() + { + int lastGraphicsPipeSync = -1; + int lastComputePipeSync = -1; + + // First go through all passes. + // - Update the last pass read index for each resource. + // - Add texture to creation list for passes that first write to a texture. + // - Update synchronization points for all resources between compute and graphics pipes. + for (int passIndex = 0; passIndex < m_CompiledPassInfos.size; ++passIndex) + { + ref CompiledPassInfo passInfo = ref m_CompiledPassInfos[passIndex]; + + if (passInfo.culled) + continue; + + for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type) + { + var resourcesInfo = m_CompiledResourcesInfos[type]; + foreach (int resource in passInfo.pass.resourceReadLists[type]) + { + UpdateResourceSynchronization(ref lastGraphicsPipeSync, ref lastComputePipeSync, passIndex, resourcesInfo[resource]); + } + + foreach (int resource in passInfo.pass.resourceWriteLists[type]) + { + UpdateResourceSynchronization(ref lastGraphicsPipeSync, ref lastComputePipeSync, passIndex, resourcesInfo[resource]); + } + + } + + // Gather all renderer lists + m_RendererLists.AddRange(passInfo.pass.usedRendererListList); + } + + for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type) + { + var resourceInfos = m_CompiledResourcesInfos[type]; + // Now push resources to the release list of the pass that reads it last. + for (int i = 0; i < resourceInfos.size; ++i) + { + CompiledResourceInfo resourceInfo = resourceInfos[i]; + + // Resource creation + int firstWriteIndex = GetFirstValidWriteIndex(resourceInfo); + // Index -1 can happen for imported resources (for example an imported dummy black texture will never be written to but does not need creation anyway) + if (firstWriteIndex != -1) + m_CompiledPassInfos[firstWriteIndex].resourceCreateList[type].Add(i); + + // Texture release + // Sometimes, a texture can be written by a pass after the last pass that reads it. + // In this case, we need to extend its lifetime to this pass otherwise the pass would get an invalid texture. + int lastReadPassIndex = Math.Max(GetLatestValidReadIndex(resourceInfo), GetLatestValidWriteIndex(resourceInfo)); + + if (lastReadPassIndex != -1) + { + // In case of async passes, we need to extend lifetime of resource to the first pass on the graphics pipeline that wait for async passes to be over. + // Otherwise, if we freed the resource right away during an async pass, another non async pass could reuse the resource even though the async pipe is not done. + if (m_CompiledPassInfos[lastReadPassIndex].enableAsyncCompute) + { + int currentPassIndex = lastReadPassIndex; + int firstWaitingPassIndex = m_CompiledPassInfos[currentPassIndex].syncFromPassIndex; + // Find the first async pass that is synchronized by the graphics pipeline (ie: passInfo.syncFromPassIndex != -1) + while (firstWaitingPassIndex == -1 && currentPassIndex < m_CompiledPassInfos.size) + { + currentPassIndex++; + if (m_CompiledPassInfos[currentPassIndex].enableAsyncCompute) + firstWaitingPassIndex = m_CompiledPassInfos[currentPassIndex].syncFromPassIndex; + } + + // Finally add the release command to the pass before the first pass that waits for the compute pipe. + ref CompiledPassInfo passInfo = ref m_CompiledPassInfos[Math.Max(0, firstWaitingPassIndex - 1)]; + passInfo.resourceReleaseList[type].Add(i); + + // Fail safe in case render graph is badly formed. + if (currentPassIndex == m_CompiledPassInfos.size) + { + RenderGraphPass invalidPass = m_RenderPasses[lastReadPassIndex]; + throw new InvalidOperationException($"Asynchronous pass {invalidPass.name} was never synchronized on the graphics pipeline."); + } + } + else + { + ref CompiledPassInfo passInfo = ref m_CompiledPassInfos[lastReadPassIndex]; + passInfo.resourceReleaseList[type].Add(i); + } + } + } + } + + // Creates all renderer lists + m_Resources.CreateRendererLists(m_RendererLists); + } + + // Internal visibility for testing purpose only + // Traverse the render graph: + // - Determines when resources are created/released + // - Determines async compute pass synchronization + // - Cull unused render passes. + internal void CompileRenderGraph() + { + InitializeCompilationData(); + CountReferences(); + CullUnusedPasses(); + UpdateResourceAllocationAndSynchronization(); + LogRendererListsCreation(); + } + + ref CompiledPassInfo CompilePassImmediatly(RenderGraphPass pass) + { + // If we don't have enough pre allocated elements we double the size. + // It's pretty aggressive but the immediate mode is only for debug purpose so it should be fine. + if (m_CurrentImmediatePassIndex >= m_CompiledPassInfos.size) + m_CompiledPassInfos.Resize(m_CompiledPassInfos.size * 2); + + ref CompiledPassInfo passInfo = ref m_CompiledPassInfos[m_CurrentImmediatePassIndex++]; + passInfo.Reset(pass); + // In immediate mode we don't have proper information to generate synchronization so we disable async compute. + passInfo.enableAsyncCompute = false; + + // In immediate mode, we don't have any resource usage information so we'll just create resources whenever they are written to if not already alive. + // We will release all resources at the end of the render graph execution. + for (int iType = 0; iType < (int)RenderGraphResourceType.Count; ++iType) + { + foreach (var res in pass.resourceWriteLists[iType]) + { + if (!m_Resources.IsResourceCreated(res)) + { + passInfo.resourceCreateList[iType].Add(res); + m_ImmediateModeResourceList[iType].Add(res); + } + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + passInfo.debugResourceWrites[iType].Add(m_Resources.GetResourceName(res)); +#endif + } + + foreach (var res in pass.transientResourceList[iType]) + { + passInfo.resourceCreateList[iType].Add(res); + passInfo.resourceReleaseList[iType].Add(res); + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + passInfo.debugResourceWrites[iType].Add(m_Resources.GetResourceName(res)); + passInfo.debugResourceReads[iType].Add(m_Resources.GetResourceName(res)); +#endif + } + + foreach (var res in pass.resourceReadLists[iType]) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + passInfo.debugResourceReads[iType].Add(m_Resources.GetResourceName(res)); +#endif + } + } + + // Create the necessary renderer lists + foreach(var rl in pass.usedRendererListList) + { + if (!m_Resources.IsRendererListCreated(rl)) + m_RendererLists.Add(rl); + } + m_Resources.CreateRendererLists(m_RendererLists); + m_RendererLists.Clear(); + + return ref passInfo; + } + + void ExecutePassImmediatly(RenderGraphPass pass) + { + ExecuteCompiledPass(ref CompilePassImmediatly(pass), m_CurrentImmediatePassIndex - 1); + } + + void ExecuteCompiledPass(ref CompiledPassInfo passInfo, int passIndex) + { + if (passInfo.culled) + return; + + if (!passInfo.pass.HasRenderFunc()) + { + throw new InvalidOperationException(string.Format("RenderPass {0} was not provided with an execute function.", passInfo.pass.name)); + } + + try + { + using (new ProfilingScope(m_RenderGraphContext.cmd, passInfo.pass.customSampler)) + { + LogRenderPassBegin(passInfo); + using (new RenderGraphLogIndent(m_Logger)) + { + PreRenderPassExecute(passInfo, m_RenderGraphContext); + passInfo.pass.Execute(m_RenderGraphContext); + PostRenderPassExecute(ref passInfo, m_RenderGraphContext); + } + } + } + catch (Exception e) + { + m_ExecutionExceptionWasRaised = true; + Debug.LogError($"Render Graph Execution error at pass {passInfo.pass.name} ({passIndex})"); + Debug.LogException(e); + throw; + } + } + + // Execute the compiled render graph + void ExecuteRenderGraph() + { + for (int passIndex = 0; passIndex < m_CompiledPassInfos.size; ++passIndex) + { + ExecuteCompiledPass(ref m_CompiledPassInfos[passIndex], passIndex); + } + } + + void PreRenderPassSetRenderTargets(in CompiledPassInfo passInfo, RenderGraphContext rgContext) + { + var pass = passInfo.pass; if (pass.depthBuffer.IsValid() || pass.colorBufferMaxIndex != -1) { var mrtArray = rgContext.renderGraphPool.GetTempArray(pass.colorBufferMaxIndex + 1); @@ -463,35 +1092,86 @@ void PreRenderPassSetRenderTargets(in RenderPass pass, RenderGraphContext rgCont } } - void PreRenderPassExecute(int passIndex, in RenderPass pass, RenderGraphContext rgContext) + void PreRenderPassExecute(in CompiledPassInfo passInfo, RenderGraphContext rgContext) { - // TODO merge clear and setup here if possible - m_Resources.CreateAndClearTexturesForPass(rgContext, pass.index, pass.resourceWriteList); - PreRenderPassSetRenderTargets(pass, rgContext); - m_Resources.PreRenderPassSetGlobalTextures(rgContext, pass.resourceReadList); + // TODO RENDERGRAPH merge clear and setup here if possible + RenderGraphPass pass = passInfo.pass; + + // Need to save the command buffer to restore it later as the one in the context can changed if running a pass async. + m_PreviousCommandBuffer = rgContext.cmd; + + foreach (var texture in passInfo.resourceCreateList[(int)RenderGraphResourceType.Texture]) + m_Resources.CreateAndClearTexture(rgContext, texture); + + foreach (var buffer in passInfo.resourceCreateList[(int)RenderGraphResourceType.ComputeBuffer]) + m_Resources.CreateComputeBuffer(rgContext, buffer); + + PreRenderPassSetRenderTargets(passInfo, rgContext); + + // Flush first the current command buffer on the render context. + rgContext.renderContext.ExecuteCommandBuffer(rgContext.cmd); + rgContext.cmd.Clear(); + + if (passInfo.enableAsyncCompute) + { + CommandBuffer asyncCmd = CommandBufferPool.Get(pass.name); + asyncCmd.SetExecutionFlags(CommandBufferExecutionFlags.AsyncCompute); + rgContext.cmd = asyncCmd; + } + + // Synchronize with graphics or compute pipe if needed. + if (passInfo.syncToPassIndex != -1) + { + rgContext.cmd.WaitOnAsyncGraphicsFence(m_CompiledPassInfos[passInfo.syncToPassIndex].fence); + } } - void PostRenderPassExecute(int passIndex, in RenderPass pass, RenderGraphContext rgContext) + void PostRenderPassExecute(ref CompiledPassInfo passInfo, RenderGraphContext rgContext) { - if (m_DebugParameters.unbindGlobalTextures) - m_Resources.PostRenderPassUnbindGlobalTextures(rgContext, pass.resourceReadList); + RenderGraphPass pass = passInfo.pass; + + if (passInfo.needGraphicsFence) + passInfo.fence = rgContext.cmd.CreateAsyncGraphicsFence(); + + if (passInfo.enableAsyncCompute) + { + // The command buffer has been filled. We can kick the async task. + rgContext.renderContext.ExecuteCommandBufferAsync(rgContext.cmd, ComputeQueueType.Background); + CommandBufferPool.Release(rgContext.cmd); + rgContext.cmd = m_PreviousCommandBuffer; // Restore the main command buffer. + } m_RenderGraphPool.ReleaseAllTempAlloc(); - m_Resources.ReleaseTexturesForPass(rgContext, pass.index, pass.resourceReadList, pass.resourceWriteList); - pass.Release(rgContext); + + foreach (var texture in passInfo.resourceReleaseList[(int)RenderGraphResourceType.Texture]) + m_Resources.ReleaseTexture(rgContext, texture); + foreach (var buffer in passInfo.resourceReleaseList[(int)RenderGraphResourceType.ComputeBuffer]) + m_Resources.ReleaseComputeBuffer(rgContext, buffer); } void ClearRenderPasses() { + foreach (var pass in m_RenderPasses) + pass.Release(m_RenderGraphPool); m_RenderPasses.Clear(); } - void LogFrameInformation(int renderingWidth, int renderingHeight) + void ReleaseImmediateModeResources() + { + foreach (var texture in m_ImmediateModeResourceList[(int)RenderGraphResourceType.Texture]) + m_Resources.ReleaseTexture(m_RenderGraphContext, texture); + foreach (var buffer in m_ImmediateModeResourceList[(int)RenderGraphResourceType.ComputeBuffer]) + m_Resources.ReleaseComputeBuffer(m_RenderGraphContext, buffer); + } + + void LogFrameInformation() { if (m_DebugParameters.logFrameInformation) { - m_Logger.LogLine("==== Staring frame at resolution ({0}x{1}) ====", renderingWidth, renderingHeight); - m_Logger.LogLine("Number of passes declared: {0}", m_RenderPasses.Count); + m_Logger.LogLine("==== Staring render graph frame ===="); + + if (!m_DebugParameters.immediateMode) + m_Logger.LogLine("Number of passes declared: {0}\n", m_RenderPasses.Count); } } @@ -499,19 +1179,106 @@ void LogRendererListsCreation() { if (m_DebugParameters.logFrameInformation) { - m_Logger.LogLine("Number of renderer lists created: {0}", m_RendererLists.Count); + m_Logger.LogLine("Number of renderer lists created: {0}\n", m_RendererLists.Count); } } - void LogRenderPassBegin(in RenderPass pass) + void LogRenderPassBegin(in CompiledPassInfo passInfo) { if (m_DebugParameters.logFrameInformation) { - m_Logger.LogLine("Executing pass \"{0}\" (index: {1})", pass.name, pass.index); + RenderGraphPass pass = passInfo.pass; + + m_Logger.LogLine("[{0}][{1}] \"{2}\"", pass.index, pass.enableAsyncCompute ? "Compute" : "Graphics", pass.name); + using (new RenderGraphLogIndent(m_Logger)) + { + if (passInfo.syncToPassIndex != -1) + m_Logger.LogLine("Synchronize with [{0}]", passInfo.syncToPassIndex); + } } } + void LogCulledPasses() + { + if (m_DebugParameters.logFrameInformation) + { + m_Logger.LogLine("Pass Culling Report:"); + using (new RenderGraphLogIndent(m_Logger)) + { + for (int i = 0; i < m_CompiledPassInfos.size; ++i) + { + if (m_CompiledPassInfos[i].culled) + { + var pass = m_RenderPasses[i]; + m_Logger.LogLine("[{0}] {1}", pass.index, pass.name); + } + } + m_Logger.LogLine("\n"); + } + } + } + + ProfilingSampler GetDefaultProfilingSampler(string name) + { + int hash = name.GetHashCode(); + if (!m_DefaultProfilingSamplers.TryGetValue(hash, out var sampler)) + { + sampler = new ProfilingSampler(name); + m_DefaultProfilingSamplers.Add(hash, sampler); + } + + return sampler; + } + #endregion } + + /// + /// Render Graph Scoped Profiling markers + /// + public struct RenderGraphProfilingScope : IDisposable + { + bool m_Disposed; + ProfilingSampler m_Sampler; + RenderGraph m_RenderGraph; + + /// + /// Profiling Scope constructor + /// + /// Render Graph used for this scope. + /// Profiling Sampler to be used for this scope. + public RenderGraphProfilingScope(RenderGraph renderGraph, ProfilingSampler sampler) + { + m_RenderGraph = renderGraph; + m_Sampler = sampler; + m_Disposed = false; + renderGraph.BeginProfilingSampler(sampler); + } + + /// + /// Dispose pattern implementation + /// + public void Dispose() + { + Dispose(true); + } + + // Protected implementation of Dispose pattern. + void Dispose(bool disposing) + { + if (m_Disposed) + return; + + // As this is a struct, it could have been initialized using an empty constructor so we + // need to make sure `cmd` isn't null to avoid a crash. Switching to a class would fix + // this but will generate garbage on every frame (and this struct is used quite a lot). + if (disposing) + { + m_RenderGraph.EndProfilingSampler(m_Sampler); + } + + m_Disposed = true; + } + } } diff --git a/Runtime/RenderGraph/RenderGraphBuilder.cs b/Runtime/RenderGraph/RenderGraphBuilder.cs index 79001b9..28bac21 100644 --- a/Runtime/RenderGraph/RenderGraphBuilder.cs +++ b/Runtime/RenderGraph/RenderGraphBuilder.cs @@ -8,8 +8,9 @@ namespace UnityEngine.Experimental.Rendering.RenderGraphModule /// public struct RenderGraphBuilder : IDisposable { - RenderGraph.RenderPass m_RenderPass; + RenderGraphPass m_RenderPass; RenderGraphResourceRegistry m_Resources; + RenderGraph m_RenderGraph; bool m_Disposed; #region Public Interface @@ -20,13 +21,10 @@ public struct RenderGraphBuilder : IDisposable /// The Texture resource to use as a color render target. /// Index for multiple render target usage. /// An updated resource handle to the input resource. - public RenderGraphMutableResource UseColorBuffer(in RenderGraphMutableResource input, int index) + public TextureHandle UseColorBuffer(in TextureHandle input, int index) { - if (input.type != RenderGraphResourceType.Texture) - throw new ArgumentException("Trying to write to a resource that is not a texture or is invalid."); - + CheckTransientResource(input.handle); m_RenderPass.SetColorBuffer(input, index); - m_Resources.UpdateTextureFirstWrite(input, m_RenderPass.index); return input; } @@ -36,16 +34,10 @@ public RenderGraphMutableResource UseColorBuffer(in RenderGraphMutableResource i /// The Texture resource to use as a depth buffer during the pass. /// Specify the access level for the depth buffer. This allows you to say whether you will read from or write to the depth buffer, or do both. /// An updated resource handle to the input resource. - public RenderGraphMutableResource UseDepthBuffer(in RenderGraphMutableResource input, DepthAccess flags) + public TextureHandle UseDepthBuffer(in TextureHandle input, DepthAccess flags) { - if (input.type != RenderGraphResourceType.Texture) - throw new ArgumentException("Trying to write to a resource that is not a texture or is invalid."); - + CheckTransientResource(input.handle); m_RenderPass.SetDepthBuffer(input, flags); - if ((flags | DepthAccess.Read) != 0) - m_Resources.UpdateTextureLastRead(input, m_RenderPass.index); - if ((flags | DepthAccess.Write) != 0) - m_Resources.UpdateTextureFirstWrite(input, m_RenderPass.index); return input; } @@ -54,12 +46,10 @@ public RenderGraphMutableResource UseDepthBuffer(in RenderGraphMutableResource i /// /// The Texture resource to read from during the pass. /// An updated resource handle to the input resource. - public RenderGraphResource ReadTexture(in RenderGraphResource input) + public TextureHandle ReadTexture(in TextureHandle input) { - if (input.type != RenderGraphResourceType.Texture) - throw new ArgumentException("Trying to read a resource that is not a texture or is invalid."); - m_RenderPass.resourceReadList.Add(input); - m_Resources.UpdateTextureLastRead(input, m_RenderPass.index); + CheckTransientResource(input.handle); + m_RenderPass.AddResourceRead(input.handle); return input; } @@ -68,29 +58,103 @@ public RenderGraphResource ReadTexture(in RenderGraphResource input) /// /// The Texture resource to write to during the pass. /// An updated resource handle to the input resource. - public RenderGraphMutableResource WriteTexture(in RenderGraphMutableResource input) + public TextureHandle WriteTexture(in TextureHandle input) { - if (input.type != RenderGraphResourceType.Texture) - throw new ArgumentException("Trying to write to a resource that is not a texture or is invalid."); - // TODO: Manage resource "version" for debugging purpose - m_RenderPass.resourceWriteList.Add(input); - m_Resources.UpdateTextureFirstWrite(input, m_RenderPass.index); + CheckTransientResource(input.handle); + // TODO RENDERGRAPH: Manage resource "version" for debugging purpose + m_RenderPass.AddResourceWrite(input.handle); return input; } + /// + /// Create a new Render Graph Texture resource. + /// This texture will only be available for the current pass and will be assumed to be both written and read so users don't need to add explicit read/write declarations. + /// + /// Texture descriptor. + /// A new transient TextureHandle. + public TextureHandle CreateTransientTexture(in TextureDesc desc) + { + var result = m_Resources.CreateTexture(desc, m_RenderPass.index); + m_RenderPass.AddTransientResource(result.handle); + return result; + } + + /// + /// Create a new Render Graph Texture resource using the descriptor from another texture. + /// This texture will only be available for the current pass and will be assumed to be both written and read so users don't need to add explicit read/write declarations. + /// + /// Texture from which the descriptor should be used. + /// A new transient TextureHandle. + public TextureHandle CreateTransientTexture(in TextureHandle texture) + { + var desc = m_Resources.GetTextureResourceDesc(texture.handle); + var result = m_Resources.CreateTexture(desc, m_RenderPass.index); + m_RenderPass.AddTransientResource(result.handle); + return result; + } + /// /// Specify a Renderer List resource to use during the pass. /// /// The Renderer List resource to use during the pass. /// An updated resource handle to the input resource. - public RenderGraphResource UseRendererList(in RenderGraphResource input) + public RendererListHandle UseRendererList(in RendererListHandle input) { - if (input.type != RenderGraphResourceType.RendererList) - throw new ArgumentException("Trying use a resource that is not a renderer list."); - m_RenderPass.usedRendererListList.Add(input); + m_RenderPass.UseRendererList(input); return input; } + /// + /// Specify a Compute Buffer resource to read from during the pass. + /// + /// The Compute Buffer resource to read from during the pass. + /// An updated resource handle to the input resource. + public ComputeBufferHandle ReadComputeBuffer(in ComputeBufferHandle input) + { + CheckTransientResource(input.handle); + m_RenderPass.AddResourceRead(input.handle); + return input; + } + + /// + /// Specify a Compute Buffer resource to write to during the pass. + /// + /// The Compute Buffer resource to write to during the pass. + /// An updated resource handle to the input resource. + public ComputeBufferHandle WriteComputeBuffer(in ComputeBufferHandle input) + { + CheckTransientResource(input.handle); + m_RenderPass.AddResourceWrite(input.handle); + return input; + } + + /// + /// Create a new Render Graph Compute Buffer resource. + /// This Compute Buffer will only be available for the current pass and will be assumed to be both written and read so users don't need to add explicit read/write declarations. + /// + /// Compute Buffer descriptor. + /// A new transient ComputeBufferHandle. + public ComputeBufferHandle CreateTransientComputeBuffer(in ComputeBufferDesc desc) + { + var result = m_Resources.CreateComputeBuffer(desc, m_RenderPass.index); + m_RenderPass.AddTransientResource(result.handle); + return result; + } + + /// + /// Create a new Render Graph Compute Buffer resource using the descriptor from another Compute Buffer. + /// This Compute Buffer will only be available for the current pass and will be assumed to be both written and read so users don't need to add explicit read/write declarations. + /// + /// Compute Buffer from which the descriptor should be used. + /// A new transient ComputeBufferHandle. + public ComputeBufferHandle CreateTransientComputeBuffer(in ComputeBufferHandle computebuffer) + { + var desc = m_Resources.GetComputeBufferResourceDesc(computebuffer.handle); + var result = m_Resources.CreateComputeBuffer(desc, m_RenderPass.index); + m_RenderPass.AddTransientResource(result.handle); + return result; + } + /// /// Specify the render function to use for this pass. /// A call to this is mandatory for the pass to be valid. @@ -99,7 +163,7 @@ public RenderGraphResource UseRendererList(in RenderGraphResource input) /// Render function for the pass. public void SetRenderFunc(RenderFunc renderFunc) where PassData : class, new() { - ((RenderGraph.RenderPass)m_RenderPass).renderFunc = renderFunc; + ((RenderGraphPass)m_RenderPass).renderFunc = renderFunc; } /// @@ -108,7 +172,19 @@ public void SetRenderFunc(RenderFunc renderFunc) where PassD /// Set to true to enable asynchronous compute. public void EnableAsyncCompute(bool value) { - m_RenderPass.enableAsyncCompute = value; + m_RenderPass.EnableAsyncCompute(value); + } + + /// + /// Allow or not pass culling + /// By default all passes can be culled out if the render graph detects it's not actually used. + /// In some cases, a pass may not write or read any texture but rather do something with side effects (like setting a global texture parameter for example). + /// This function can be used to tell the system that it should not cull this pass. + /// + /// True to allow pass culling. + public void AllowPassCulling(bool value) + { + m_RenderPass.AllowPassCulling(value); } /// @@ -121,10 +197,11 @@ public void Dispose() #endregion #region Internal Interface - internal RenderGraphBuilder(RenderGraph.RenderPass renderPass, RenderGraphResourceRegistry resources) + internal RenderGraphBuilder(RenderGraphPass renderPass, RenderGraphResourceRegistry resources, RenderGraph renderGraph) { m_RenderPass = renderPass; m_Resources = resources; + m_RenderGraph = renderGraph; m_Disposed = false; } @@ -133,8 +210,28 @@ void Dispose(bool disposing) if (m_Disposed) return; + m_RenderGraph.OnPassAdded(m_RenderPass); m_Disposed = true; } + + void CheckTransientResource(in ResourceHandle res) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (res.IsValid()) + { + int transientIndex = m_Resources.GetResourceTransientIndex(res); + if (transientIndex == m_RenderPass.index) + { + Debug.LogError($"Trying to read or write a transient resource at pass {m_RenderPass.name}.Transient resource are always assumed to be both read and written."); + } + + if (transientIndex != -1 && transientIndex != m_RenderPass.index) + { + throw new ArgumentException($"Trying to use a transient texture (pass index {transientIndex}) in a different pass (pass index {m_RenderPass.index}."); + } + } +#endif + } #endregion } } diff --git a/Runtime/RenderGraph/RenderGraphDefaultResources.cs b/Runtime/RenderGraph/RenderGraphDefaultResources.cs new file mode 100644 index 0000000..1a33b22 --- /dev/null +++ b/Runtime/RenderGraph/RenderGraphDefaultResources.cs @@ -0,0 +1,74 @@ +using UnityEngine.Rendering; + +namespace UnityEngine.Experimental.Rendering.RenderGraphModule +{ + /// + /// Helper class allowing access to default resources (black or white texture, etc.) during render passes. + /// + public class RenderGraphDefaultResources + { + bool m_IsValid; + + // We need to keep around a RTHandle version of default regular 2D textures since RenderGraph API is all RTHandle. + RTHandle m_BlackTexture2D; + RTHandle m_WhiteTexture2D; + + /// Default black 2D texture. + public TextureHandle blackTexture { get; private set; } + /// Default white 2D texture. + public TextureHandle whiteTexture { get; private set; } + /// Default clear color XR 2D texture. + public TextureHandle clearTextureXR { get; private set; } + /// Default magenta XR 2D texture. + public TextureHandle magentaTextureXR { get; private set; } + /// Default black XR 2D texture. + public TextureHandle blackTextureXR { get; private set; } + /// Default black XR 2D Array texture. + public TextureHandle blackTextureArrayXR { get; private set; } + /// Default black (UInt) XR 2D texture. + public TextureHandle blackUIntTextureXR { get; private set; } + /// Default black XR 3D texture. + public TextureHandle blackTexture3DXR { get; private set; } + /// Default white XR 2D texture. + public TextureHandle whiteTextureXR { get; private set; } + + internal RenderGraphDefaultResources() + { + m_BlackTexture2D = RTHandles.Alloc(Texture2D.blackTexture); + m_WhiteTexture2D = RTHandles.Alloc(Texture2D.whiteTexture); + } + + internal void Cleanup() + { + m_BlackTexture2D.Release(); + m_WhiteTexture2D.Release(); + } + + internal void InitializeForRendering(RenderGraph renderGraph) + { + if (!m_IsValid) + { + blackTexture = renderGraph.ImportTexture(m_BlackTexture2D); + whiteTexture = renderGraph.ImportTexture(m_WhiteTexture2D); + + clearTextureXR = renderGraph.ImportTexture(TextureXR.GetClearTexture()); + magentaTextureXR = renderGraph.ImportTexture(TextureXR.GetMagentaTexture()); + blackTextureXR = renderGraph.ImportTexture(TextureXR.GetBlackTexture()); + blackTextureArrayXR = renderGraph.ImportTexture(TextureXR.GetBlackTextureArray()); + blackUIntTextureXR = renderGraph.ImportTexture(TextureXR.GetBlackUIntTexture()); + blackTexture3DXR = renderGraph.ImportTexture(TextureXR.GetBlackTexture3D()); + whiteTextureXR = renderGraph.ImportTexture(TextureXR.GetWhiteTexture()); + + m_IsValid = true; + } + } + + // Imported resources are cleared everytime the Render Graph is executed, so we need to know if that happens + // so that we can re-import all default resources if needed. + internal void Clear() + { + m_IsValid = false; + } + } +} + diff --git a/Runtime/RenderGraph/RenderGraphDefaultResources.cs.meta b/Runtime/RenderGraph/RenderGraphDefaultResources.cs.meta new file mode 100644 index 0000000..e969657 --- /dev/null +++ b/Runtime/RenderGraph/RenderGraphDefaultResources.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d9929b63696b16c4ca41927306959897 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/RenderGraph/RenderGraphLogger.cs b/Runtime/RenderGraph/RenderGraphLogger.cs index 5f434d2..47f7a83 100644 --- a/Runtime/RenderGraph/RenderGraphLogger.cs +++ b/Runtime/RenderGraph/RenderGraphLogger.cs @@ -25,10 +25,12 @@ public void Dispose() void Dispose(bool disposing) { + Debug.Assert(m_Logger != null, "RenderGraphLogIndent: logger parameter should not be null."); + if (m_Disposed) return; - if (disposing) + if (disposing && m_Logger != null) { m_Logger.DecrementIndentation(m_Indentation); } diff --git a/Runtime/RenderGraph/RenderGraphObjectPool.cs b/Runtime/RenderGraph/RenderGraphObjectPool.cs index cfe0df2..edd95f6 100644 --- a/Runtime/RenderGraph/RenderGraphObjectPool.cs +++ b/Runtime/RenderGraph/RenderGraphObjectPool.cs @@ -88,14 +88,14 @@ internal void ReleaseAllTempAlloc() // Regular pooling API. Only internal use for now internal T Get() where T : new() { - var toto = SharedObjectPool.sharedPool; - return toto.Get(); + var pool = SharedObjectPool.sharedPool; + return pool.Get(); } internal void Release(T value) where T : new() { - var toto = SharedObjectPool.sharedPool; - toto.Release(value); + var pool = SharedObjectPool.sharedPool; + pool.Release(value); } } } diff --git a/Runtime/RenderGraph/RenderGraphPass.cs b/Runtime/RenderGraph/RenderGraphPass.cs new file mode 100644 index 0000000..cd9b696 --- /dev/null +++ b/Runtime/RenderGraph/RenderGraphPass.cs @@ -0,0 +1,156 @@ +using System; +using System.Diagnostics; +using System.Collections.Generic; +using UnityEngine.Rendering; + +namespace UnityEngine.Experimental.Rendering.RenderGraphModule +{ + [DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")] + abstract class RenderGraphPass + { + public RenderFunc GetExecuteDelegate() + where PassData : class, new() => ((RenderGraphPass)this).renderFunc; + + public abstract void Execute(RenderGraphContext renderGraphContext); + public abstract void Release(RenderGraphObjectPool pool); + public abstract bool HasRenderFunc(); + + public string name { get; protected set; } + public int index { get; protected set; } + public ProfilingSampler customSampler { get; protected set; } + public bool enableAsyncCompute { get; protected set; } + public bool allowPassCulling { get; protected set; } + + public TextureHandle depthBuffer { get; protected set; } + public TextureHandle[] colorBuffers { get; protected set; } = new TextureHandle[RenderGraph.kMaxMRTCount]; + public int colorBufferMaxIndex { get; protected set; } = -1; + public int refCount { get; protected set; } + + public List[] resourceReadLists = new List[(int)RenderGraphResourceType.Count]; + public List[] resourceWriteLists = new List[(int)RenderGraphResourceType.Count]; + public List[] transientResourceList = new List[(int)RenderGraphResourceType.Count]; + + public List usedRendererListList = new List(); + + public RenderGraphPass() + { + for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i) + { + resourceReadLists[i] = new List(); + resourceWriteLists[i] = new List(); + transientResourceList[i] = new List(); + } + } + + public void Clear() + { + name = ""; + index = -1; + customSampler = null; + for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i) + { + resourceReadLists[i].Clear(); + resourceWriteLists[i].Clear(); + transientResourceList[i].Clear(); + } + + usedRendererListList.Clear(); + enableAsyncCompute = false; + allowPassCulling = true; + refCount = 0; + + // Invalidate everything + colorBufferMaxIndex = -1; + depthBuffer = TextureHandle.nullHandle; + for (int i = 0; i < RenderGraph.kMaxMRTCount; ++i) + { + colorBuffers[i] = TextureHandle.nullHandle; + } + } + + public void AddResourceWrite(in ResourceHandle res) + { + resourceWriteLists[res.iType].Add(res); + } + + public void AddResourceRead(in ResourceHandle res) + { + resourceReadLists[res.iType].Add(res); + } + + public void AddTransientResource(in ResourceHandle res) + { + transientResourceList[res.iType].Add(res); + } + + public void UseRendererList(RendererListHandle rendererList) + { + usedRendererListList.Add(rendererList); + } + + public void EnableAsyncCompute(bool value) + { + enableAsyncCompute = value; + } + + public void AllowPassCulling(bool value) + { + allowPassCulling = value; + } + + public void SetColorBuffer(TextureHandle resource, int index) + { + Debug.Assert(index < RenderGraph.kMaxMRTCount && index >= 0); + colorBufferMaxIndex = Math.Max(colorBufferMaxIndex, index); + colorBuffers[index] = resource; + AddResourceWrite(resource.handle); + } + + public void SetDepthBuffer(TextureHandle resource, DepthAccess flags) + { + depthBuffer = resource; + if ((flags & DepthAccess.Read) != 0) + AddResourceRead(resource.handle); + if ((flags & DepthAccess.Write) != 0) + AddResourceWrite(resource.handle); + } + } + + [DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")] + internal sealed class RenderGraphPass : RenderGraphPass + where PassData : class, new() + { + internal PassData data; + internal RenderFunc renderFunc; + + public override void Execute(RenderGraphContext renderGraphContext) + { + GetExecuteDelegate()(data, renderGraphContext); + } + + public void Initialize(int passIndex, PassData passData, string passName, ProfilingSampler sampler) + { + Clear(); + index = passIndex; + data = passData; + name = passName; + customSampler = sampler; + } + + public override void Release(RenderGraphObjectPool pool) + { + Clear(); + pool.Release(data); + data = null; + renderFunc = null; + + // We need to do the release from here because we need the final type. + pool.Release(this); + } + + public override bool HasRenderFunc() + { + return renderFunc != null; + } + } +} diff --git a/Runtime/RenderGraph/RenderGraphPass.cs.meta b/Runtime/RenderGraph/RenderGraphPass.cs.meta new file mode 100644 index 0000000..133cd05 --- /dev/null +++ b/Runtime/RenderGraph/RenderGraphPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 377a2b96156b1344eaf58df67e35de17 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/RenderGraph/RenderGraphResource.cs b/Runtime/RenderGraph/RenderGraphResource.cs deleted file mode 100644 index b59c38d..0000000 --- a/Runtime/RenderGraph/RenderGraphResource.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Diagnostics; - -namespace UnityEngine.Experimental.Rendering.RenderGraphModule -{ - internal enum RenderGraphResourceType - { - Invalid = 0, // Don't change this. We need this to be Zero otherwise default zero initialized RenderGraphResource would have a valid Type - Texture, - RendererList - } - - /// - /// Handle to a read-only Render Graph resource. - /// - [DebuggerDisplay("{type} ({handle})")] - public struct RenderGraphResource - { - internal int handle { get; private set; } - internal RenderGraphResourceType type { get; private set; } - - internal RenderGraphResource(RenderGraphMutableResource mutableResource) - { - handle = mutableResource.handle; - type = mutableResource.type; - } - - internal RenderGraphResource(int handle, RenderGraphResourceType type) - { - this.handle = handle; - this.type = type; - } - - /// - /// Is the resource valid? - /// - /// True if the resource is valid. - public bool IsValid() { return type != RenderGraphResourceType.Invalid; } - } - - /// - /// Handle to a writable Render Graph resource. - /// - [DebuggerDisplay("{type} ({handle})")] - public struct RenderGraphMutableResource - { - internal int handle { get; private set; } - internal RenderGraphResourceType type { get; private set; } - internal int version { get; private set; } - - internal RenderGraphMutableResource(int handle, RenderGraphResourceType type) - { - this.handle = handle; - this.type = type; - this.version = 0; - } - - internal RenderGraphMutableResource(RenderGraphMutableResource other) - { - handle = other.handle; - type = other.type; - version = other.version + 1; - } - - /// - /// Build a RenderGraphResource from a RenderGraphMutableResource. - /// - /// Other render graph resource. - /// New RenderGraphResource handle. - public static implicit operator RenderGraphResource(RenderGraphMutableResource handle) - { - return new RenderGraphResource(handle); - } - - internal bool IsValid() { return type != RenderGraphResourceType.Invalid; } - } -} diff --git a/Runtime/RenderGraph/RenderGraphResourcePool.cs b/Runtime/RenderGraph/RenderGraphResourcePool.cs new file mode 100644 index 0000000..f03dae9 --- /dev/null +++ b/Runtime/RenderGraph/RenderGraphResourcePool.cs @@ -0,0 +1,202 @@ +using System.Collections.Generic; +using UnityEngine.Rendering; + +namespace UnityEngine.Experimental.Rendering.RenderGraphModule +{ + + abstract class RenderGraphResourcePool where Type : class + { + // Dictionary tracks resources by hash and stores resources with same hash in a List (list instead of a stack because we need to be able to remove stale allocations, potentially in the middle of the stack). + protected Dictionary> m_ResourcePool = new Dictionary>(); + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + // Diagnostic only + // This list allows us to determine if all resources were correctly released in the frame. + List<(int, Type)> m_FrameAllocatedResources = new List<(int, Type)>(); +#endif + + protected static int s_CurrentFrameIndex; + + // Release the GPU resource itself + abstract protected void ReleaseInternalResource(Type res); + abstract protected string GetResourceName(Type res); + abstract protected string GetResourceTypeName(); + + public void ReleaseResource(int hash, Type resource, int currentFrameIndex) + { + if (!m_ResourcePool.TryGetValue(hash, out var list)) + { + list = new List<(Type resource, int frameIndex)>(); + m_ResourcePool.Add(hash, list); + } + + list.Add((resource, currentFrameIndex)); + } + + public bool TryGetResource(int hashCode, out Type resource) + { + if (m_ResourcePool.TryGetValue(hashCode, out var list) && list.Count > 0) + { + resource = list[list.Count - 1].resource; + list.RemoveAt(list.Count - 1); // O(1) since it's the last element. + return true; + } + + resource = null; + return false; + } + + abstract public void PurgeUnusedResources(int currentFrameIndex); + + public void Cleanup() + { + foreach (var kvp in m_ResourcePool) + { + foreach (var res in kvp.Value) + { + ReleaseInternalResource(res.resource); + } + } + } + + public void RegisterFrameAllocation(int hash, Type value) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (hash != -1) + m_FrameAllocatedResources.Add((hash, value)); +#endif + } + + public void UnregisterFrameAllocation(int hash, Type value) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (hash != -1) + m_FrameAllocatedResources.Remove((hash, value)); +#endif + } + + public void CheckFrameAllocation(bool onException, int frameIndex) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (m_FrameAllocatedResources.Count != 0 && !onException) + { + string logMessage = $"RenderGraph: Not all resources of type {GetResourceTypeName()} were released. This can be caused by a resources being allocated but never read by any pass."; + + foreach (var value in m_FrameAllocatedResources) + { + logMessage = $"{logMessage}\n\t{GetResourceName(value.Item2)}"; + ReleaseResource(value.Item1, value.Item2, frameIndex); + } + + Debug.LogWarning(logMessage); + } + + // If an error occurred during execution, it's expected that textures are not all released so we clear the tracking list. + m_FrameAllocatedResources.Clear(); +#endif + } + + + public void LogResources(RenderGraphLogger logger) + { + List allocationList = new List(); + foreach (var kvp in m_ResourcePool) + { + foreach (var res in kvp.Value) + { + allocationList.Add(GetResourceName(res.resource)); + } + } + + logger.LogLine($"== {GetResourceTypeName()} Resources =="); + allocationList.Sort(); + int index = 0; + foreach (var element in allocationList) + logger.LogLine("[{0}] {1}", index++, element); + } + } + + class TexturePool : RenderGraphResourcePool + { + protected override void ReleaseInternalResource(RTHandle res) + { + res.Release(); + } + + protected override string GetResourceName(RTHandle res) + { + return res.rt.name; + } + + override protected string GetResourceTypeName() + { + return "Texture"; + } + + // Another C# nicety. + // We need to re-implement the whole thing every time because: + // - obj.resource.Release is Type specific so it cannot be called on a generic (and there's no shared interface for resources like RTHandle, ComputeBuffers etc) + // - We can't use a virtual release function because it will capture this in the lambda for RemoveAll generating GCAlloc in the process. + override public void PurgeUnusedResources(int currentFrameIndex) + { + // Update the frame index for the lambda. Static because we don't want to capture. + s_CurrentFrameIndex = currentFrameIndex; + + foreach (var kvp in m_ResourcePool) + { + var list = kvp.Value; + list.RemoveAll(obj => + { + if (obj.frameIndex < s_CurrentFrameIndex) + { + obj.resource.Release(); + return true; + } + return false; + }); + } + } + } + + class ComputeBufferPool : RenderGraphResourcePool + { + protected override void ReleaseInternalResource(ComputeBuffer res) + { + res.Release(); + } + + protected override string GetResourceName(ComputeBuffer res) + { + return "ComputeBufferNameNotAvailable"; // res.name is a setter only :( + } + + override protected string GetResourceTypeName() + { + return "ComputeBuffer"; + } + + // Another C# nicety. + // We need to re-implement the whole thing every time because: + // - obj.resource.Release is Type specific so it cannot be called on a generic (and there's no shared interface for resources like RTHandle, ComputeBuffers etc) + // - We can't use a virtual release function because it will capture this in the lambda for RemoveAll generating GCAlloc in the process. + override public void PurgeUnusedResources(int currentFrameIndex) + { + // Update the frame index for the lambda. Static because we don't want to capture. + s_CurrentFrameIndex = currentFrameIndex; + + foreach (var kvp in m_ResourcePool) + { + var list = kvp.Value; + list.RemoveAll(obj => + { + if (obj.frameIndex < s_CurrentFrameIndex) + { + obj.resource.Release(); + return true; + } + return false; + }); + } + } + } +} diff --git a/Runtime/RenderGraph/RenderGraphResourcePool.cs.meta b/Runtime/RenderGraph/RenderGraphResourcePool.cs.meta new file mode 100644 index 0000000..0e7e35a --- /dev/null +++ b/Runtime/RenderGraph/RenderGraphResourcePool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4998ae5b70f43844eb7716decb537ba5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/RenderGraph/RenderGraphResourceRegistry.cs b/Runtime/RenderGraph/RenderGraphResourceRegistry.cs index e398012..6fd7516 100644 --- a/Runtime/RenderGraph/RenderGraphResourceRegistry.cs +++ b/Runtime/RenderGraph/RenderGraphResourceRegistry.cs @@ -1,258 +1,102 @@ using System; +using System.Diagnostics; using System.Collections.Generic; using UnityEngine.Rendering; namespace UnityEngine.Experimental.Rendering.RenderGraphModule { - /// - /// The mode that determines the size of a Texture. - /// - #region Resource Descriptors - public enum TextureSizeMode + class RenderGraphResourceRegistry { - ///Explicit size. - Explicit, - ///Size automatically scaled by a Vector. - Scale, - ///Size automatically scaled by a Functor. - Functor - } + static readonly ShaderTagId s_EmptyName = new ShaderTagId(""); - /// - /// Descriptor used to create texture resources - /// - public struct TextureDesc - { - ///Texture sizing mode. - public TextureSizeMode sizeMode; - ///Texture width. - public int width; - ///Texture height. - public int height; - ///Number of texture slices.. - public int slices; - ///Texture scale. - public Vector2 scale; - ///Texture scale function. - public ScaleFunc func; - ///Depth buffer bit depth. - public DepthBits depthBufferBits; - ///Color format. - public GraphicsFormat colorFormat; - ///Filtering mode. - public FilterMode filterMode; - ///Addressing mode. - public TextureWrapMode wrapMode; - ///Texture dimension. - public TextureDimension dimension; - ///Enable random UAV read/write on the texture. - public bool enableRandomWrite; - ///Texture needs mip maps. - public bool useMipMap; - ///Automatically generate mip maps. - public bool autoGenerateMips; - ///Texture is a shadow map. - public bool isShadowMap; - ///Anisotropic filtering level. - public int anisoLevel; - ///Mip map bias. - public float mipMapBias; - ///Textre is multisampled. Only supported for Scale and Functor size mode. - public bool enableMSAA; - ///Number of MSAA samples. Only supported for Explicit size mode. - public MSAASamples msaaSamples; - ///Bind texture multi sampled. - public bool bindTextureMS; - ///Texture uses dynamic scaling. - public bool useDynamicScale; - ///Memory less flag. - public RenderTextureMemoryless memoryless; - ///Texture name. - public string name; - - // Initial state. Those should not be used in the hash - ///Texture needs to be cleared on first use. - public bool clearBuffer; - ///Clear color. - public Color clearColor; - - void InitDefaultValues(bool dynamicResolution, bool xrReady) - { - useDynamicScale = dynamicResolution; - // XR Ready - if (xrReady) + static RenderGraphResourceRegistry m_CurrentRegistry; + internal static RenderGraphResourceRegistry current + { + get { - slices = TextureXR.slices; - dimension = TextureXR.dimension; + // We assume that it's enough to only check in editor because we don't want to pay the cost at runtime. +#if UNITY_EDITOR + if (m_CurrentRegistry == null) + { + throw new InvalidOperationException("Current Render Graph Resource Registry is not set. You are probably trying to cast a Render Graph handle to a resource outside of a Render Graph Pass."); + } +#endif + return m_CurrentRegistry; } - else + set { - slices = 1; - dimension = TextureDimension.Tex2D; + m_CurrentRegistry = value; } } - /// - /// TextureDesc constructor for a texture using explicit size - /// - /// Texture width - /// Texture height - /// Use dynamic resolution - /// Set this to true if the Texture is a render texture in an XR setting. - public TextureDesc(int width, int height, bool dynamicResolution = false, bool xrReady = false) - : this() - { - // Size related init - sizeMode = TextureSizeMode.Explicit; - this.width = width; - this.height = height; - // Important default values not handled by zero construction in this() - msaaSamples = MSAASamples.None; - InitDefaultValues(dynamicResolution, xrReady); - } - - /// - /// TextureDesc constructor for a texture using a fixed scaling - /// - /// RTHandle scale used for this texture - /// Use dynamic resolution - /// Set this to true if the Texture is a render texture in an XR setting. - public TextureDesc(Vector2 scale, bool dynamicResolution = false, bool xrReady = false) - : this() - { - // Size related init - sizeMode = TextureSizeMode.Scale; - this.scale = scale; - // Important default values not handled by zero construction in this() - msaaSamples = MSAASamples.None; - dimension = TextureDimension.Tex2D; - InitDefaultValues(dynamicResolution, xrReady); - } - - /// - /// TextureDesc constructor for a texture using a functor for scaling - /// - /// Function used to determnine the texture size - /// Use dynamic resolution - /// Set this to true if the Texture is a render texture in an XR setting. - public TextureDesc(ScaleFunc func, bool dynamicResolution = false, bool xrReady = false) - : this() - { - // Size related init - sizeMode = TextureSizeMode.Functor; - this.func = func; - // Important default values not handled by zero construction in this() - msaaSamples = MSAASamples.None; - dimension = TextureDimension.Tex2D; - InitDefaultValues(dynamicResolution, xrReady); - } - - /// - /// Copy constructor - /// - /// - public TextureDesc(TextureDesc input) - { - this = input; - } - - /// - /// Hash function - /// - /// The texture descriptor hash. - public override int GetHashCode() - { - int hashCode = 17; - - unchecked + class IRenderGraphResource + { + public bool imported; + public int cachedHash; + public int transientPassIndex; + public bool wasReleased; + + public virtual void Reset() { - switch (sizeMode) - { - case TextureSizeMode.Explicit: - hashCode = hashCode * 23 + width; - hashCode = hashCode * 23 + height; - hashCode = hashCode * 23 + (int)msaaSamples; - break; - case TextureSizeMode.Functor: - if (func != null) - hashCode = hashCode * 23 + func.GetHashCode(); - hashCode = hashCode * 23 + (enableMSAA ? 1 : 0); - break; - case TextureSizeMode.Scale: - hashCode = hashCode * 23 + scale.x.GetHashCode(); - hashCode = hashCode * 23 + scale.y.GetHashCode(); - hashCode = hashCode * 23 + (enableMSAA ? 1 : 0); - break; - } + imported = false; + cachedHash = -1; + transientPassIndex = -1; + wasReleased = false; + } - hashCode = hashCode * 23 + mipMapBias.GetHashCode(); - hashCode = hashCode * 23 + slices; - hashCode = hashCode * 23 + (int)depthBufferBits; - hashCode = hashCode * 23 + (int)colorFormat; - hashCode = hashCode * 23 + (int)filterMode; - hashCode = hashCode * 23 + (int)wrapMode; - hashCode = hashCode * 23 + (int)dimension; - hashCode = hashCode * 23 + (int)memoryless; - hashCode = hashCode * 23 + anisoLevel; - hashCode = hashCode * 23 + (enableRandomWrite ? 1 : 0); - hashCode = hashCode * 23 + (useMipMap ? 1 : 0); - hashCode = hashCode * 23 + (autoGenerateMips ? 1 : 0); - hashCode = hashCode * 23 + (isShadowMap ? 1 : 0); - hashCode = hashCode * 23 + (bindTextureMS ? 1 : 0); - hashCode = hashCode * 23 + (useDynamicScale ? 1 : 0); + public virtual string GetName() + { + return ""; } - return hashCode; + public virtual bool IsCreated() + { + return false; + } } - } - #endregion - - /// - /// The RenderGraphResourceRegistry holds all resource allocated during Render Graph execution. - /// - public class RenderGraphResourceRegistry - { - static readonly ShaderTagId s_EmptyName = new ShaderTagId(""); #region Resources - internal struct TextureResource - { - public TextureDesc desc; - public bool imported; - public RTHandle rt; - public int cachedHash; - public int firstWritePassIndex; - public int lastReadPassIndex; - public int shaderProperty; - public bool wasReleased; - - internal TextureResource(RTHandle rt, int shaderProperty) - : this() + [DebuggerDisplay("Resource ({GetType().Name}:{GetName()})")] + class RenderGraphResource + : IRenderGraphResource + where DescType : struct + where ResType : class + { + public DescType desc; + public ResType resource; + + protected RenderGraphResource() { - Reset(); - this.rt = rt; - imported = true; - this.shaderProperty = shaderProperty; } - internal TextureResource(in TextureDesc desc, int shaderProperty) - : this() + public override void Reset() { - Reset(); + base.Reset(); + resource = null; + } - this.desc = desc; - this.shaderProperty = shaderProperty; + public override bool IsCreated() + { + return resource != null; } + } - void Reset() + [DebuggerDisplay("TextureResource ({desc.name})")] + class TextureResource : RenderGraphResource + { + public override string GetName() { - imported = false; - rt = null; - cachedHash = -1; - firstWritePassIndex = int.MaxValue; - lastReadPassIndex = -1; - wasReleased = false; + return desc.name; + } + } + + [DebuggerDisplay("ComputeBufferResource ({desc.name})")] + class ComputeBufferResource : RenderGraphResource + { + public override string GetName() + { + return desc.name; } } @@ -267,54 +111,44 @@ internal RendererListResource(in RendererListDesc desc) this.rendererList = new RendererList(); // Invalid by default } } + #endregion - DynamicArray m_TextureResources = new DynamicArray(); - Dictionary> m_TexturePool = new Dictionary>(); + + DynamicArray[] m_Resources = new DynamicArray[(int)RenderGraphResourceType.Count]; + + TexturePool m_TexturePool = new TexturePool(); + int m_TextureCreationIndex; + ComputeBufferPool m_ComputeBufferPool = new ComputeBufferPool(); DynamicArray m_RendererListResources = new DynamicArray(); - RTHandleSystem m_RTHandleSystem = new RTHandleSystem(); RenderGraphDebugParams m_RenderGraphDebug; RenderGraphLogger m_Logger; + int m_CurrentFrameIndex; - // Diagnostic only - List<(int, RTHandle)> m_AllocatedTextures = new List<(int, RTHandle)>(); + RTHandle m_CurrentBackbuffer; - #region Public Interface - /// - /// Returns the RTHandle associated with the provided resource handle. - /// - /// Handle to a texture resource. - /// The RTHandle associated with the provided resource handle. - public RTHandle GetTexture(in RenderGraphResource handle) + internal RTHandle GetTexture(in TextureHandle handle) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (handle.type != RenderGraphResourceType.Texture) - throw new InvalidOperationException("Trying to access a RenderGraphResource that is not a texture."); + if (!handle.IsValid()) + return null; - var res = m_TextureResources[handle.handle]; + return GetTextureResource(handle.handle).resource; + } - if (res.rt == null && !res.wasReleased) - throw new InvalidOperationException(string.Format("Trying to access texture \"{0}\" that was never created. Check that it was written at least once before trying to get it.", res.desc.name)); + internal RendererList GetRendererList(in RendererListHandle handle) + { + if (!handle.IsValid() || handle >= m_RendererListResources.size) + return RendererList.nullRendererList; - if (res.rt == null && res.wasReleased) - throw new InvalidOperationException(string.Format("Trying to access texture \"{0}\" that was already released. Check that the last pass where it's read is after this one.", res.desc.name)); -#endif - return m_TextureResources[handle.handle].rt; + return m_RendererListResources[handle].rendererList; } - /// - /// Returns the RendererList associated with the provided resource handle. - /// - /// Handle to a Renderer List resource. - /// The Renderer List associated with the provided resource handle. - public RendererList GetRendererList(in RenderGraphResource handle) + internal ComputeBuffer GetComputeBuffer(in ComputeBufferHandle handle) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (handle.type != RenderGraphResourceType.RendererList) - throw new InvalidOperationException("Trying to access a RenderGraphResource that is not a RendererList."); -#endif - return m_RendererListResources[handle.handle].rendererList; + if (!handle.IsValid()) + return null; + + return GetComputeBufferResource(handle.handle).resource; } - #endregion #region Internal Interface private RenderGraphResourceRegistry() @@ -322,239 +156,316 @@ private RenderGraphResourceRegistry() } - internal RenderGraphResourceRegistry(bool supportMSAA, MSAASamples initialSampleCount, RenderGraphDebugParams renderGraphDebug, RenderGraphLogger logger) + internal RenderGraphResourceRegistry(RenderGraphDebugParams renderGraphDebug, RenderGraphLogger logger) { - // We initialize to screen width/height to avoid multiple realloc that can lead to inflated memory usage (as releasing of memory is delayed). - m_RTHandleSystem.Initialize(Screen.width, Screen.height, supportMSAA, initialSampleCount); m_RenderGraphDebug = renderGraphDebug; m_Logger = logger; + + for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i) + m_Resources[i] = new DynamicArray(); } - internal void SetRTHandleReferenceSize(int width, int height, MSAASamples msaaSamples) + ResType GetResource(DynamicArray resourceArray, int index) + where DescType : struct + where ResType : class { - m_RTHandleSystem.SetReferenceSize(width, height, msaaSamples); + var res = resourceArray[index] as RenderGraphResource; + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (res.resource == null && !res.wasReleased) + throw new InvalidOperationException(string.Format("Trying to access resource \"{0}\" that was never created. Check that it was written at least once before trying to get it.", res.GetName())); + + if (res.resource == null && res.wasReleased) + throw new InvalidOperationException(string.Format("Trying to access resource \"{0}\" that was already released. Check that the last pass where it's read is after this one.", res.GetName())); +#endif + return res.resource; } - internal RTHandleProperties GetRTHandleProperties() { return m_RTHandleSystem.rtHandleProperties; } + internal void BeginRender(int currentFrameIndex, int executionCount) + { + m_CurrentFrameIndex = currentFrameIndex; + ResourceHandle.NewFrame(executionCount); + current = this; + } - // Texture Creation/Import APIs are internal because creation should only go through RenderGraph - internal RenderGraphMutableResource ImportTexture(RTHandle rt, int shaderProperty = 0) + internal void EndRender() { - int newHandle = m_TextureResources.Add(new TextureResource(rt, shaderProperty)); - return new RenderGraphMutableResource(newHandle, RenderGraphResourceType.Texture); + current = null; } - internal RenderGraphMutableResource CreateTexture(in TextureDesc desc, int shaderProperty = 0) + void CheckHandleValidity(in ResourceHandle res) { - ValidateTextureDesc(desc); + var resources = m_Resources[res.iType]; + if (res.index >= resources.size) + throw new ArgumentException($"Trying to access resource of type {res.type} with an invalid resource index {res.index}"); + } - int newHandle = m_TextureResources.Add(new TextureResource(desc, shaderProperty)); - return new RenderGraphMutableResource(newHandle, RenderGraphResourceType.Texture); + internal string GetResourceName(in ResourceHandle res) + { + CheckHandleValidity(res); + return m_Resources[res.iType][res.index].GetName(); } - internal void UpdateTextureFirstWrite(RenderGraphResource tex, int passIndex) + internal bool IsResourceImported(in ResourceHandle res) { - ref var res = ref GetTextureResource(tex); - res.firstWritePassIndex = Math.Min(passIndex, res.firstWritePassIndex); + CheckHandleValidity(res); + return m_Resources[res.iType][res.index].imported; + } - //// We increment lastRead index here so that a resource used only for a single pass can be released at the end of said pass. - //// This will also keep the resource alive as long as it is written to. - //// Typical example is a depth buffer that may never be explicitly read from but is necessary all along - /// - // PROBLEM: Increasing last read on write operation will keep the target alive even if it's not used at all so it's not good. - // If we don't do it though, it means that client code cannot write "by default" into a target as it will try to write to an already released target. - // Example: - // DepthPrepass: Writes to Depth and Normal buffers (pass will create normal buffer) - // ObjectMotion: Writes to MotionVectors and Normal => Exception because NormalBuffer is already released as it not used. - // => Solution includes : Shader Combination (without MRT for example) / Dummy Targets - //res.lastReadPassIndex = Math.Max(passIndex, res.lastReadPassIndex); + internal bool IsResourceCreated(in ResourceHandle res) + { + CheckHandleValidity(res); + return m_Resources[res.iType][res.index].IsCreated(); } - internal void UpdateTextureLastRead(RenderGraphResource tex, int passIndex) + internal bool IsRendererListCreated(in RendererListHandle res) { - ref var res = ref GetTextureResource(tex); - res.lastReadPassIndex = Math.Max(passIndex, res.lastReadPassIndex); + return m_RendererListResources[res].rendererList.isValid; } - ref TextureResource GetTextureResource(RenderGraphResource res) + internal int GetResourceTransientIndex(in ResourceHandle res) { - return ref m_TextureResources[res.handle]; + CheckHandleValidity(res); + return m_Resources[res.iType][res.index].transientPassIndex; } - internal TextureDesc GetTextureResourceDesc(RenderGraphResource res) + // Texture Creation/Import APIs are internal because creation should only go through RenderGraph + internal TextureHandle ImportTexture(RTHandle rt) { - return m_TextureResources[res.handle].desc; + int newHandle = AddNewResource(m_Resources[(int)RenderGraphResourceType.Texture], out TextureResource texResource); + texResource.resource = rt; + texResource.imported = true; + + return new TextureHandle(newHandle); } - internal RenderGraphResource CreateRendererList(in RendererListDesc desc) + internal TextureHandle ImportBackbuffer(RenderTargetIdentifier rt) { - ValidateRendererListDesc(desc); + if (m_CurrentBackbuffer != null) + m_CurrentBackbuffer.SetTexture(rt); + else + m_CurrentBackbuffer = RTHandles.Alloc(rt, "Backbuffer"); - int newHandle = m_RendererListResources.Add(new RendererListResource(desc)); - return new RenderGraphResource(newHandle, RenderGraphResourceType.RendererList); + int newHandle = AddNewResource(m_Resources[(int)RenderGraphResourceType.Texture], out TextureResource texResource); + texResource.resource = m_CurrentBackbuffer; + texResource.imported = true; + + return new TextureHandle(newHandle); } - internal void CreateAndClearTexturesForPass(RenderGraphContext rgContext, int passIndex, List textures) + int AddNewResource(DynamicArray resourceArray, out ResType outRes) where ResType : IRenderGraphResource, new() { - foreach (var rgResource in textures) - { - ref var resource = ref GetTextureResource(rgResource); - if (!resource.imported && resource.firstWritePassIndex == passIndex) - { - CreateTextureForPass(ref resource); + // In order to not create garbage, instead of using Add, we keep the content of the array while resizing and we just reset the existing ref (or create it if it's null). + int result = resourceArray.size; + resourceArray.Resize(resourceArray.size + 1, true); + if (resourceArray[result] == null) + resourceArray[result] = new ResType(); - if (resource.desc.clearBuffer || m_RenderGraphDebug.clearRenderTargetsAtCreation) - { - bool debugClear = m_RenderGraphDebug.clearRenderTargetsAtCreation && !resource.desc.clearBuffer; - var name = debugClear ? "RenderGraph: Clear Buffer (Debug)" : "RenderGraph: Clear Buffer"; - using (new ProfilingScope(rgContext.cmd, ProfilingSampler.Get(RenderGraphProfileId.RenderGraphClear))) - { - var clearFlag = resource.desc.depthBufferBits != DepthBits.None ? ClearFlag.Depth : ClearFlag.Color; - var clearColor = debugClear ? Color.magenta : resource.desc.clearColor; - CoreUtils.SetRenderTarget(rgContext.cmd, resource.rt, clearFlag, clearColor); - } - } + outRes = resourceArray[result] as ResType; + outRes.Reset(); + return result; + } - LogTextureCreation(resource.rt, resource.desc.clearBuffer || m_RenderGraphDebug.clearRenderTargetsAtCreation); - } - } + internal TextureHandle CreateTexture(in TextureDesc desc, int transientPassIndex = -1) + { + ValidateTextureDesc(desc); + + int newHandle = AddNewResource(m_Resources[(int)RenderGraphResourceType.Texture], out TextureResource texResource); + texResource.desc = desc; + texResource.transientPassIndex = transientPassIndex; + return new TextureHandle(newHandle); } - void CreateTextureForPass(ref TextureResource resource) + internal int GetTextureResourceCount() { - var desc = resource.desc; - int hashCode = desc.GetHashCode(); + return m_Resources[(int)RenderGraphResourceType.Texture].size; + } - if(resource.rt != null) - throw new InvalidOperationException(string.Format("Trying to create an already created texture ({0}). Texture was probably declared for writing more than once.", resource.desc.name)); + TextureResource GetTextureResource(in ResourceHandle handle) + { + return m_Resources[(int)RenderGraphResourceType.Texture][handle] as TextureResource; + } - resource.rt = null; - if (!TryGetRenderTarget(hashCode, out resource.rt)) - { - // Note: Name used here will be the one visible in the memory profiler so it means that whatever is the first pass that actually allocate the texture will set the name. - // TODO: Find a way to display name by pass. - switch (desc.sizeMode) - { - case TextureSizeMode.Explicit: - resource.rt = m_RTHandleSystem.Alloc(desc.width, desc.height, desc.slices, desc.depthBufferBits, desc.colorFormat, desc.filterMode, desc.wrapMode, desc.dimension, desc.enableRandomWrite, - desc.useMipMap, desc.autoGenerateMips, desc.isShadowMap, desc.anisoLevel, desc.mipMapBias, desc.msaaSamples, desc.bindTextureMS, desc.useDynamicScale, desc.memoryless, desc.name); - break; - case TextureSizeMode.Scale: - resource.rt = m_RTHandleSystem.Alloc(desc.scale, desc.slices, desc.depthBufferBits, desc.colorFormat, desc.filterMode, desc.wrapMode, desc.dimension, desc.enableRandomWrite, - desc.useMipMap, desc.autoGenerateMips, desc.isShadowMap, desc.anisoLevel, desc.mipMapBias, desc.enableMSAA, desc.bindTextureMS, desc.useDynamicScale, desc.memoryless, desc.name); - break; - case TextureSizeMode.Functor: - resource.rt = m_RTHandleSystem.Alloc(desc.func, desc.slices, desc.depthBufferBits, desc.colorFormat, desc.filterMode, desc.wrapMode, desc.dimension, desc.enableRandomWrite, - desc.useMipMap, desc.autoGenerateMips, desc.isShadowMap, desc.anisoLevel, desc.mipMapBias, desc.enableMSAA, desc.bindTextureMS, desc.useDynamicScale, desc.memoryless, desc.name); - break; - } - } + internal TextureDesc GetTextureResourceDesc(in ResourceHandle handle) + { + return (m_Resources[(int)RenderGraphResourceType.Texture][handle] as TextureResource).desc; + } - //// Try to update name when re-using a texture. - //// TODO: Check if that actually works. - //resource.rt.name = desc.name; + internal RendererListHandle CreateRendererList(in RendererListDesc desc) + { + ValidateRendererListDesc(desc); -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (hashCode != -1) - { - m_AllocatedTextures.Add((hashCode, resource.rt)); - } -#endif + int newHandle = m_RendererListResources.Add(new RendererListResource(desc)); + return new RendererListHandle(newHandle); + } + + internal ComputeBufferHandle ImportComputeBuffer(ComputeBuffer computeBuffer) + { + int newHandle = AddNewResource(m_Resources[(int)RenderGraphResourceType.ComputeBuffer], out ComputeBufferResource bufferResource); + bufferResource.resource = computeBuffer; + bufferResource.imported = true; - resource.cachedHash = hashCode; + return new ComputeBufferHandle(newHandle); } - void SetGlobalTextures(RenderGraphContext rgContext, List textures, bool bindDummyTexture) + internal ComputeBufferHandle CreateComputeBuffer(in ComputeBufferDesc desc, int transientPassIndex = -1) { - foreach (var resource in textures) - { - var resourceDesc = GetTextureResource(resource); - if (resourceDesc.shaderProperty != 0) - { - if (resourceDesc.rt == null) - { - throw new InvalidOperationException(string.Format("Trying to set Global Texture parameter for \"{0}\" which was never created.\nCheck that at least one write operation happens before reading it.", resourceDesc.desc.name)); - } - rgContext.cmd.SetGlobalTexture(resourceDesc.shaderProperty, bindDummyTexture ? TextureXR.GetMagentaTexture() : resourceDesc.rt); - } - } + ValidateComputeBufferDesc(desc); + + int newHandle = AddNewResource(m_Resources[(int)RenderGraphResourceType.ComputeBuffer], out ComputeBufferResource bufferResource); + bufferResource.desc = desc; + bufferResource.transientPassIndex = transientPassIndex; + + return new ComputeBufferHandle(newHandle); } + internal ComputeBufferDesc GetComputeBufferResourceDesc(in ResourceHandle handle) + { + return (m_Resources[(int)RenderGraphResourceType.ComputeBuffer][handle] as ComputeBufferResource).desc; + } - internal void PreRenderPassSetGlobalTextures(RenderGraphContext rgContext, List textures) + internal int GetComputeBufferResourceCount() { - SetGlobalTextures(rgContext, textures, false); + return m_Resources[(int)RenderGraphResourceType.ComputeBuffer].size; } - internal void PostRenderPassUnbindGlobalTextures(RenderGraphContext rgContext, List textures) + ComputeBufferResource GetComputeBufferResource(in ResourceHandle handle) { - SetGlobalTextures(rgContext, textures, true); + return m_Resources[(int)RenderGraphResourceType.ComputeBuffer][handle] as ComputeBufferResource; } - internal void ReleaseTexturesForPass(RenderGraphContext rgContext, int passIndex, List readTextures, List writtenTextures) + internal void CreateAndClearTexture(RenderGraphContext rgContext, int index) { - foreach (var resource in readTextures) + var resource = m_Resources[(int)RenderGraphResourceType.Texture][index] as TextureResource; + + if (!resource.imported) { - ref var resourceDesc = ref GetTextureResource(resource); - if (!resourceDesc.imported && resourceDesc.lastReadPassIndex == passIndex) + var desc = resource.desc; + int hashCode = desc.GetHashCode(); + + if (resource.resource != null) + throw new InvalidOperationException(string.Format("Trying to create an already created texture ({0}). Texture was probably declared for writing more than once in the same pass.", resource.desc.name)); + + resource.resource = null; + if (!m_TexturePool.TryGetResource(hashCode, out resource.resource)) { - if (m_RenderGraphDebug.clearRenderTargetsAtRelease) + // Textures are going to be reused under different aliases along the frame so we can't provide a specific name upon creation. + // The name in the desc is going to be used for debugging purpose and render graph visualization. + string name = $"RenderGraphTexture_{m_TextureCreationIndex++}"; + + switch (desc.sizeMode) { - using (new ProfilingScope(rgContext.cmd, ProfilingSampler.Get(RenderGraphProfileId.RenderGraphClearDebug))) - { - var clearFlag = resourceDesc.desc.depthBufferBits != DepthBits.None ? ClearFlag.Depth : ClearFlag.Color; - CoreUtils.SetRenderTarget(rgContext.cmd, GetTexture(resource), clearFlag, Color.magenta); - } + case TextureSizeMode.Explicit: + resource.resource = RTHandles.Alloc(desc.width, desc.height, desc.slices, desc.depthBufferBits, desc.colorFormat, desc.filterMode, desc.wrapMode, desc.dimension, desc.enableRandomWrite, + desc.useMipMap, desc.autoGenerateMips, desc.isShadowMap, desc.anisoLevel, desc.mipMapBias, desc.msaaSamples, desc.bindTextureMS, desc.useDynamicScale, desc.memoryless, name); + break; + case TextureSizeMode.Scale: + resource.resource = RTHandles.Alloc(desc.scale, desc.slices, desc.depthBufferBits, desc.colorFormat, desc.filterMode, desc.wrapMode, desc.dimension, desc.enableRandomWrite, + desc.useMipMap, desc.autoGenerateMips, desc.isShadowMap, desc.anisoLevel, desc.mipMapBias, desc.enableMSAA, desc.bindTextureMS, desc.useDynamicScale, desc.memoryless, name); + break; + case TextureSizeMode.Functor: + resource.resource = RTHandles.Alloc(desc.func, desc.slices, desc.depthBufferBits, desc.colorFormat, desc.filterMode, desc.wrapMode, desc.dimension, desc.enableRandomWrite, + desc.useMipMap, desc.autoGenerateMips, desc.isShadowMap, desc.anisoLevel, desc.mipMapBias, desc.enableMSAA, desc.bindTextureMS, desc.useDynamicScale, desc.memoryless, name); + break; } + } + + resource.cachedHash = hashCode; - ReleaseTextureForPass(resource); +#if UNITY_2020_2_OR_NEWER + var fastMemDesc = resource.desc.fastMemoryDesc; + if(fastMemDesc.inFastMemory) + { + resource.resource.SwitchToFastMemory(rgContext.cmd, fastMemDesc.residencyFraction, fastMemDesc.flags); } +#endif + + if (resource.desc.clearBuffer || m_RenderGraphDebug.clearRenderTargetsAtCreation) + { + bool debugClear = m_RenderGraphDebug.clearRenderTargetsAtCreation && !resource.desc.clearBuffer; + var name = debugClear ? "RenderGraph: Clear Buffer (Debug)" : "RenderGraph: Clear Buffer"; + using (new ProfilingScope(rgContext.cmd, ProfilingSampler.Get(RenderGraphProfileId.RenderGraphClear))) + { + var clearFlag = resource.desc.depthBufferBits != DepthBits.None ? ClearFlag.Depth : ClearFlag.Color; + var clearColor = debugClear ? Color.magenta : resource.desc.clearColor; + CoreUtils.SetRenderTarget(rgContext.cmd, resource.resource, clearFlag, clearColor); + } + } + + m_TexturePool.RegisterFrameAllocation(hashCode, resource.resource); + LogTextureCreation(resource); } + } - // If a resource was created for only a single pass, we don't want users to have to declare explicitly the read operation. - // So to do that, we also update lastReadIndex on resource writes. - // This means that we need to check written resources for destruction too - foreach (var resource in writtenTextures) + internal void CreateComputeBuffer(RenderGraphContext rgContext, int index) + { + var resource = m_Resources[(int)RenderGraphResourceType.ComputeBuffer][index] as ComputeBufferResource; + if (!resource.imported) { - ref var resourceDesc = ref GetTextureResource(resource); - // <= because a texture that is only declared as written in a single pass (and read implicitly in the same pass) will have the default lastReadPassIndex at -1 - if (!resourceDesc.imported && resourceDesc.lastReadPassIndex <= passIndex) + var desc = resource.desc; + int hashCode = desc.GetHashCode(); + + if (resource.resource != null) + throw new InvalidOperationException(string.Format("Trying to create an already created Compute Buffer ({0}). Buffer was probably declared for writing more than once in the same pass.", resource.desc.name)); + + resource.resource = null; + if (!m_ComputeBufferPool.TryGetResource(hashCode, out resource.resource)) { - ReleaseTextureForPass(resource); + resource.resource = new ComputeBuffer(resource.desc.count, resource.desc.stride, resource.desc.type); + resource.resource.name = $"RenderGraphComputeBuffer_{resource.desc.count}_{resource.desc.stride}_{resource.desc.type}"; } + resource.cachedHash = hashCode; + + m_ComputeBufferPool.RegisterFrameAllocation(hashCode, resource.resource); + LogComputeBufferCreation(resource); } } - void ReleaseTextureForPass(RenderGraphResource res) + internal void ReleaseTexture(RenderGraphContext rgContext, int index) { - Debug.Assert(res.type == RenderGraphResourceType.Texture); - - ref var resource = ref m_TextureResources[res.handle]; + var resource = m_Resources[(int)RenderGraphResourceType.Texture][index] as TextureResource; - // This can happen because we release texture in two passes (see ReleaseTexturesForPass) and texture can be present in both passes - if (resource.rt != null) + if (!resource.imported) { - LogTextureRelease(resource.rt); - ReleaseTextureResource(resource.cachedHash, resource.rt); + if (resource.resource == null) + throw new InvalidOperationException($"Tried to release a texture ({resource.desc.name}) that was never created. Check that there is at least one pass writing to it first."); + + if (m_RenderGraphDebug.clearRenderTargetsAtRelease) + { + using (new ProfilingScope(rgContext.cmd, ProfilingSampler.Get(RenderGraphProfileId.RenderGraphClearDebug))) + { + var clearFlag = resource.desc.depthBufferBits != DepthBits.None ? ClearFlag.Depth : ClearFlag.Color; + // Not ideal to do new TextureHandle here but GetTexture is a public API and we rather have it take an explicit TextureHandle parameters. + // Everywhere else internally int is better because it allows us to share more code. + CoreUtils.SetRenderTarget(rgContext.cmd, GetTexture(new TextureHandle(index)), clearFlag, Color.magenta); + } + } + + LogTextureRelease(resource); + m_TexturePool.ReleaseResource(resource.cachedHash, resource.resource, m_CurrentFrameIndex); + m_TexturePool.UnregisterFrameAllocation(resource.cachedHash, resource.resource); resource.cachedHash = -1; - resource.rt = null; + resource.resource = null; resource.wasReleased = true; } } - void ReleaseTextureResource(int hash, RTHandle rt) + internal void ReleaseComputeBuffer(RenderGraphContext rgContext, int index) { - if (!m_TexturePool.TryGetValue(hash, out var stack)) - { - stack = new Stack(); - m_TexturePool.Add(hash, stack); - } + var resource = m_Resources[(int)RenderGraphResourceType.ComputeBuffer][index] as ComputeBufferResource; - stack.Push(rt); + if (!resource.imported) + { + if (resource.resource == null) + throw new InvalidOperationException($"Tried to release a compute buffer ({resource.desc.name}) that was never created. Check that there is at least one pass writing to it first."); -#if DEVELOPMENT_BUILD || UNITY_EDITOR - m_AllocatedTextures.Remove((hash, rt)); -#endif + LogComputeBufferRelease(resource); + m_ComputeBufferPool.ReleaseResource(resource.cachedHash, resource.resource, m_CurrentFrameIndex); + m_ComputeBufferPool.UnregisterFrameAllocation(resource.cachedHash, resource.resource); + resource.cachedHash = -1; + resource.resource = null; + resource.wasReleased = true; + } } void ValidateTextureDesc(in TextureDesc desc) @@ -613,82 +524,91 @@ void ValidateRendererListDesc(in RendererListDesc desc) #endif } - bool TryGetRenderTarget(int hashCode, out RTHandle rt) + void ValidateComputeBufferDesc(in ComputeBufferDesc desc) { - if (m_TexturePool.TryGetValue(hashCode, out var stack) && stack.Count > 0) +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (desc.stride % 4 != 0) { - rt = stack.Pop(); - return true; + throw new ArgumentException("Invalid Compute Buffer creation descriptor: Compute Buffer stride must be at least 4."); } - - rt = null; - return false; + if (desc.count == 0) + { + throw new ArgumentException("Invalid Compute Buffer creation descriptor: Compute Buffer count must be non zero."); + } +#endif } - internal void CreateRendererLists(List rendererLists) + internal void CreateRendererLists(List rendererLists) { // For now we just create a simple structure // but when the proper API is available in trunk we'll kick off renderer lists creation jobs here. foreach (var rendererList in rendererLists) { - Debug.Assert(rendererList.type == RenderGraphResourceType.RendererList); - - ref var rendererListResource = ref m_RendererListResources[rendererList.handle]; + ref var rendererListResource = ref m_RendererListResources[rendererList]; ref var desc = ref rendererListResource.desc; RendererList newRendererList = RendererList.Create(desc); rendererListResource.rendererList = newRendererList; } } - internal void Clear() + internal void Clear(bool onException) { LogResources(); - m_TextureResources.Clear(); + for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i) + m_Resources[i].Clear(); m_RendererListResources.Clear(); -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (m_AllocatedTextures.Count != 0) - { - Debug.LogWarning("RenderGraph: Not all textures were released."); - List<(int, RTHandle)> tempList = new List<(int, RTHandle)>(m_AllocatedTextures); - foreach (var value in tempList) - { - ReleaseTextureResource(value.Item1, value.Item2); - } - } -#endif + m_TexturePool.CheckFrameAllocation(onException, m_CurrentFrameIndex); + m_ComputeBufferPool.CheckFrameAllocation(onException, m_CurrentFrameIndex); } - internal void ResetRTHandleReferenceSize(int width, int height) + internal void PurgeUnusedResources() { - m_RTHandleSystem.ResetReferenceSize(width, height); + // TODO RENDERGRAPH: Might not be ideal to purge stale resources every frame. + // In case users enable/disable features along a level it might provoke performance spikes when things are reallocated... + // Will be much better when we have actual resource aliasing and we can manage memory more efficiently. + m_TexturePool.PurgeUnusedResources(m_CurrentFrameIndex); + m_ComputeBufferPool.PurgeUnusedResources(m_CurrentFrameIndex); } internal void Cleanup() { - foreach (var value in m_TexturePool) + m_TexturePool.Cleanup(); + m_ComputeBufferPool.Cleanup(); + + RTHandles.Release(m_CurrentBackbuffer); + } + + void LogTextureCreation(TextureResource rt) + { + if (m_RenderGraphDebug.logFrameInformation) { - foreach (var rt in value.Value) - { - m_RTHandleSystem.Release(rt); - } + m_Logger.LogLine($"Created Texture: {rt.desc.name} (Cleared: {rt.desc.clearBuffer || m_RenderGraphDebug.clearRenderTargetsAtCreation})"); } } - void LogTextureCreation(RTHandle rt, bool cleared) + void LogTextureRelease(TextureResource rt) { if (m_RenderGraphDebug.logFrameInformation) { - m_Logger.LogLine("Created Texture: {0} (Cleared: {1})", rt.rt.name, cleared); + m_Logger.LogLine($"Released Texture: {rt.desc.name}"); } } - void LogTextureRelease(RTHandle rt) + void LogComputeBufferCreation(ComputeBufferResource buffer) { if (m_RenderGraphDebug.logFrameInformation) { - m_Logger.LogLine("Released Texture: {0}", rt.rt.name); + m_Logger.LogLine($"Created ComputeBuffer: {buffer.desc.name}"); + } + } + + void LogComputeBufferRelease(ComputeBufferResource buffer) + { + if (m_RenderGraphDebug.logFrameInformation) + { + m_Logger.LogLine($"Released ComputeBuffer: {buffer.desc.name}"); } } @@ -698,19 +618,9 @@ void LogResources() { m_Logger.LogLine("==== Allocated Resources ====\n"); - List allocationList = new List(); - foreach (var stack in m_TexturePool) - { - foreach (var rt in stack.Value) - { - allocationList.Add(rt.rt.name); - } - } - - allocationList.Sort(); - int index = 0; - foreach (var element in allocationList) - m_Logger.LogLine("[{0}] {1}", index++, element); + m_TexturePool.LogResources(m_Logger); + m_Logger.LogLine(""); + m_ComputeBufferPool.LogResources(m_Logger); } } diff --git a/Runtime/RenderGraph/RenderGraphResources.cs b/Runtime/RenderGraph/RenderGraphResources.cs new file mode 100644 index 0000000..3a691ac --- /dev/null +++ b/Runtime/RenderGraph/RenderGraphResources.cs @@ -0,0 +1,449 @@ +using System.Diagnostics; +using UnityEngine.Rendering; + +namespace UnityEngine.Experimental.Rendering.RenderGraphModule +{ + // RendererList is a different case so not represented here. + internal enum RenderGraphResourceType + { + Texture = 0, + ComputeBuffer, + Count + } + + internal struct ResourceHandle + { + // Note on handles validity. + // PassData classes used during render graph passes are pooled and because of that, when users don't fill them completely, + // they can contain stale handles from a previous render graph execution that could still be considered valid if we only checked the index. + // In order to avoid using those, we incorporate the execution index in a 16 bits hash to make sure the handle is coming from the current execution. + // If not, it's considered invalid. + // We store this validity mask in the upper 16 bits of the index. + const uint kValidityMask = 0xFFFF0000; + const uint kIndexMask = 0xFFFF; + + uint m_Value; + + static uint s_CurrentValidBit = 1 << 16; + + public int index { get { return (int)(m_Value & kIndexMask); } } + public RenderGraphResourceType type { get; private set; } + public int iType { get { return (int)type; } } + + internal ResourceHandle(int value, RenderGraphResourceType type) + { + Debug.Assert(value <= 0xFFFF); + m_Value = ((uint)value & kIndexMask) | s_CurrentValidBit; + this.type = type; + } + + public static implicit operator int(ResourceHandle handle) => handle.index; + public bool IsValid() + { + var validity = m_Value & kValidityMask; + return validity != 0 && validity == s_CurrentValidBit; + } + + static public void NewFrame(int executionIndex) + { + // Scramble frame count to avoid collision when wrapping around. + s_CurrentValidBit = (uint)(((executionIndex >> 16) ^ (executionIndex & 0xffff) * 58546883) << 16); + // In case the current valid bit is 0, even though perfectly valid, 0 represents an invalid handle, hence we'll + // trigger an invalid state incorrectly. To account for this, we actually skip 0 as a viable s_CurrentValidBit and + // start from 1 again. + if (s_CurrentValidBit == 0) + { + s_CurrentValidBit = 1 << 16; + } + } + } + + /// + /// Texture resource handle. + /// + [DebuggerDisplay("Texture ({handle.index})")] + public struct TextureHandle + { + private static TextureHandle s_NullHandle = new TextureHandle(); + + /// + /// Returns a null texture handle + /// + /// A null texture handle. + public static TextureHandle nullHandle { get { return s_NullHandle; } } + + internal ResourceHandle handle; + + internal TextureHandle(int handle) { this.handle = new ResourceHandle(handle, RenderGraphResourceType.Texture); } + + /// + /// Cast to RTHandle + /// + /// Input TextureHandle. + /// Resource as a RTHandle. + public static implicit operator RTHandle(TextureHandle texture) => texture.IsValid() ? RenderGraphResourceRegistry.current.GetTexture(texture) : null; + + /// + /// Cast to RenderTargetIdentifier + /// + /// Input TextureHandle. + /// Resource as a RenderTargetIdentifier. + public static implicit operator RenderTargetIdentifier(TextureHandle texture) => texture.IsValid() ? RenderGraphResourceRegistry.current.GetTexture(texture) : null; + + /// + /// Cast to RenderTexture + /// + /// Input TextureHandle. + /// Resource as a RenderTexture. + public static implicit operator RenderTexture(TextureHandle texture) => texture.IsValid() ? RenderGraphResourceRegistry.current.GetTexture(texture) : null; + + /// + /// Return true if the handle is valid. + /// + /// True if the handle is valid. + public bool IsValid() => handle.IsValid(); + } + + /// + /// Compute Buffer resource handle. + /// + [DebuggerDisplay("ComputeBuffer ({handle.index})")] + public struct ComputeBufferHandle + { + private static ComputeBufferHandle s_NullHandle = new ComputeBufferHandle(); + + /// + /// Returns a null compute buffer handle + /// + /// A null compute buffer handle. + public static ComputeBufferHandle nullHandle { get { return s_NullHandle; } } + + internal ResourceHandle handle; + + internal ComputeBufferHandle(int handle) { this.handle = new ResourceHandle(handle, RenderGraphResourceType.ComputeBuffer); } + + /// + /// Cast to ComputeBuffer + /// + /// Input ComputeBufferHandle + /// Resource as a Compute Buffer. + public static implicit operator ComputeBuffer(ComputeBufferHandle buffer) => buffer.IsValid() ? RenderGraphResourceRegistry.current.GetComputeBuffer(buffer) : null; + + /// + /// Return true if the handle is valid. + /// + /// True if the handle is valid. + public bool IsValid() => handle.IsValid(); + } + + /// + /// Renderer List resource handle. + /// + [DebuggerDisplay("RendererList ({handle})")] + public struct RendererListHandle + { + bool m_IsValid; + internal int handle { get; private set; } + internal RendererListHandle(int handle) { this.handle = handle; m_IsValid = true; } + /// + /// Conversion to int. + /// + /// Renderer List handle to convert. + /// The integer representation of the handle. + public static implicit operator int(RendererListHandle handle) { return handle.handle; } + + /// + /// Cast to RendererList + /// + /// Input RendererListHandle. + /// Resource as a RendererList. + public static implicit operator RendererList(RendererListHandle rendererList) => rendererList.IsValid() ? RenderGraphResourceRegistry.current.GetRendererList(rendererList) : RendererList.nullRendererList; + + /// + /// Return true if the handle is valid. + /// + /// True if the handle is valid. + public bool IsValid() => m_IsValid; + } + + /// + /// The mode that determines the size of a Texture. + /// + public enum TextureSizeMode + { + ///Explicit size. + Explicit, + ///Size automatically scaled by a Vector. + Scale, + ///Size automatically scaled by a Functor. + Functor + } + +#if UNITY_2020_2_OR_NEWER + /// + /// Subset of the texture desc containing information for fast memory allocation (when platform supports it) + /// + public struct FastMemoryDesc + { + ///Whether the texture will be in fast memory. + public bool inFastMemory; + ///Flag to determine what parts of the render target is spilled if not fully resident in fast memory. + public FastMemoryFlags flags; + ///How much of the render target is to be switched into fast memory (between 0 and 1). + public float residencyFraction; + } +#endif + + /// + /// Descriptor used to create texture resources + /// + public struct TextureDesc + { + ///Texture sizing mode. + public TextureSizeMode sizeMode; + ///Texture width. + public int width; + ///Texture height. + public int height; + ///Number of texture slices.. + public int slices; + ///Texture scale. + public Vector2 scale; + ///Texture scale function. + public ScaleFunc func; + ///Depth buffer bit depth. + public DepthBits depthBufferBits; + ///Color format. + public GraphicsFormat colorFormat; + ///Filtering mode. + public FilterMode filterMode; + ///Addressing mode. + public TextureWrapMode wrapMode; + ///Texture dimension. + public TextureDimension dimension; + ///Enable random UAV read/write on the texture. + public bool enableRandomWrite; + ///Texture needs mip maps. + public bool useMipMap; + ///Automatically generate mip maps. + public bool autoGenerateMips; + ///Texture is a shadow map. + public bool isShadowMap; + ///Anisotropic filtering level. + public int anisoLevel; + ///Mip map bias. + public float mipMapBias; + ///Textre is multisampled. Only supported for Scale and Functor size mode. + public bool enableMSAA; + ///Number of MSAA samples. Only supported for Explicit size mode. + public MSAASamples msaaSamples; + ///Bind texture multi sampled. + public bool bindTextureMS; + ///Texture uses dynamic scaling. + public bool useDynamicScale; + ///Memory less flag. + public RenderTextureMemoryless memoryless; + ///Texture name. + public string name; +#if UNITY_2020_2_OR_NEWER + ///Descriptor to determine how the texture will be in fast memory on platform that supports it. + public FastMemoryDesc fastMemoryDesc; +#endif + + // Initial state. Those should not be used in the hash + ///Texture needs to be cleared on first use. + public bool clearBuffer; + ///Clear color. + public Color clearColor; + + void InitDefaultValues(bool dynamicResolution, bool xrReady) + { + useDynamicScale = dynamicResolution; + // XR Ready + if (xrReady) + { + slices = TextureXR.slices; + dimension = TextureXR.dimension; + } + else + { + slices = 1; + dimension = TextureDimension.Tex2D; + } + } + + /// + /// TextureDesc constructor for a texture using explicit size + /// + /// Texture width + /// Texture height + /// Use dynamic resolution + /// Set this to true if the Texture is a render texture in an XR setting. + public TextureDesc(int width, int height, bool dynamicResolution = false, bool xrReady = false) + : this() + { + // Size related init + sizeMode = TextureSizeMode.Explicit; + this.width = width; + this.height = height; + // Important default values not handled by zero construction in this() + msaaSamples = MSAASamples.None; + InitDefaultValues(dynamicResolution, xrReady); + } + + /// + /// TextureDesc constructor for a texture using a fixed scaling + /// + /// RTHandle scale used for this texture + /// Use dynamic resolution + /// Set this to true if the Texture is a render texture in an XR setting. + public TextureDesc(Vector2 scale, bool dynamicResolution = false, bool xrReady = false) + : this() + { + // Size related init + sizeMode = TextureSizeMode.Scale; + this.scale = scale; + // Important default values not handled by zero construction in this() + msaaSamples = MSAASamples.None; + dimension = TextureDimension.Tex2D; + InitDefaultValues(dynamicResolution, xrReady); + } + + /// + /// TextureDesc constructor for a texture using a functor for scaling + /// + /// Function used to determnine the texture size + /// Use dynamic resolution + /// Set this to true if the Texture is a render texture in an XR setting. + public TextureDesc(ScaleFunc func, bool dynamicResolution = false, bool xrReady = false) + : this() + { + // Size related init + sizeMode = TextureSizeMode.Functor; + this.func = func; + // Important default values not handled by zero construction in this() + msaaSamples = MSAASamples.None; + dimension = TextureDimension.Tex2D; + InitDefaultValues(dynamicResolution, xrReady); + } + + /// + /// Copy constructor + /// + /// + public TextureDesc(TextureDesc input) + { + this = input; + } + + /// + /// Hash function + /// + /// The texture descriptor hash. + public override int GetHashCode() + { + int hashCode = 17; + + unchecked + { + switch (sizeMode) + { + case TextureSizeMode.Explicit: + hashCode = hashCode * 23 + width; + hashCode = hashCode * 23 + height; + hashCode = hashCode * 23 + (int)msaaSamples; + break; + case TextureSizeMode.Functor: + if (func != null) + hashCode = hashCode * 23 + func.GetHashCode(); + hashCode = hashCode * 23 + (enableMSAA ? 1 : 0); + break; + case TextureSizeMode.Scale: + hashCode = hashCode * 23 + scale.x.GetHashCode(); + hashCode = hashCode * 23 + scale.y.GetHashCode(); + hashCode = hashCode * 23 + (enableMSAA ? 1 : 0); + break; + } + + hashCode = hashCode * 23 + mipMapBias.GetHashCode(); + hashCode = hashCode * 23 + slices; + hashCode = hashCode * 23 + (int)depthBufferBits; + hashCode = hashCode * 23 + (int)colorFormat; + hashCode = hashCode * 23 + (int)filterMode; + hashCode = hashCode * 23 + (int)wrapMode; + hashCode = hashCode * 23 + (int)dimension; + hashCode = hashCode * 23 + (int)memoryless; + hashCode = hashCode * 23 + anisoLevel; + hashCode = hashCode * 23 + (enableRandomWrite ? 1 : 0); + hashCode = hashCode * 23 + (useMipMap ? 1 : 0); + hashCode = hashCode * 23 + (autoGenerateMips ? 1 : 0); + hashCode = hashCode * 23 + (isShadowMap ? 1 : 0); + hashCode = hashCode * 23 + (bindTextureMS ? 1 : 0); + hashCode = hashCode * 23 + (useDynamicScale ? 1 : 0); +#if UNITY_2020_2_OR_NEWER + hashCode = hashCode * 23 + (fastMemoryDesc.inFastMemory ? 1 : 0); +#endif + } + + return hashCode; + } + } + + /// + /// Descriptor used to create compute buffer resources + /// + public struct ComputeBufferDesc + { + ///Number of elements in the buffer.. + public int count; + ///Size of one element in the buffer. Has to match size of buffer type in the shader. + public int stride; + ///Type of the buffer, default is ComputeBufferType.Default (structured buffer). + public ComputeBufferType type; + /// Compute Buffer name. + public string name; + + /// + /// ComputeBufferDesc constructor. + /// + /// Number of elements in the buffer. + /// Size of one element in the buffer. + public ComputeBufferDesc(int count, int stride) + : this() + { + this.count = count; + this.stride = stride; + type = ComputeBufferType.Default; + } + + /// + /// ComputeBufferDesc constructor. + /// + /// Number of elements in the buffer. + /// Size of one element in the buffer. + /// Type of the buffer. + public ComputeBufferDesc(int count, int stride, ComputeBufferType type) + : this() + { + this.count = count; + this.stride = stride; + this.type = type; + } + + /// + /// Hash function + /// + /// The texture descriptor hash. + public override int GetHashCode() + { + int hashCode = 17; + + hashCode = hashCode * 23 + count; + hashCode = hashCode * 23 + stride; + hashCode = hashCode * 23 + (int)type; + + return hashCode; + } + } + +} diff --git a/Runtime/RenderGraph/RenderGraphResources.cs.meta b/Runtime/RenderGraph/RenderGraphResources.cs.meta new file mode 100644 index 0000000..cb73e25 --- /dev/null +++ b/Runtime/RenderGraph/RenderGraphResources.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4b7ac9a635582ab4998d4179483bacc5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ShaderGenerator/ShaderGeneratorAttributes.cs b/Runtime/ShaderGenerator/ShaderGeneratorAttributes.cs index d380aa9..2888fca 100644 --- a/Runtime/ShaderGenerator/ShaderGeneratorAttributes.cs +++ b/Runtime/ShaderGenerator/ShaderGeneratorAttributes.cs @@ -94,6 +94,14 @@ public class GenerateHLSL : System.Attribute /// Generate structure declaration or not. /// public bool omitStructDeclaration; + /// + /// Generate constant buffer declaration or not. + /// + public bool generateCBuffer; + /// + /// If specified, when generating a constant buffer, use this explicit register. + /// + public int constantRegister; /// /// GenerateHLSL attribute constructor. @@ -105,7 +113,10 @@ public class GenerateHLSL : System.Attribute /// Start value of debug defines. /// Omit structure declaration. /// Contains packed fields. - public GenerateHLSL(PackingRules rules = PackingRules.Exact, bool needAccessors = true, bool needSetters = false, bool needParamDebug = false, int paramDefinesStart = 1, bool omitStructDeclaration = false, bool containsPackedFields = false) + /// Generate a constant buffer. + /// When generating a constant buffer, specify the optional constant register. + public GenerateHLSL(PackingRules rules = PackingRules.Exact, bool needAccessors = true, bool needSetters = false, bool needParamDebug = false, int paramDefinesStart = 1, + bool omitStructDeclaration = false, bool containsPackedFields = false, bool generateCBuffer = false, int constantRegister = -1) { packingRules = rules; this.needAccessors = needAccessors; @@ -114,6 +125,8 @@ public GenerateHLSL(PackingRules rules = PackingRules.Exact, bool needAccessors this.paramDefinesStart = paramDefinesStart; this.omitStructDeclaration = omitStructDeclaration; this.containsPackedFields = containsPackedFields; + this.generateCBuffer = generateCBuffer; + this.constantRegister = constantRegister; } } @@ -139,6 +152,10 @@ public class SurfaceDataAttributes : System.Attribute /// Field precision. /// public FieldPrecision precision; + /// + /// Field is a normalized vector. + /// + public bool checkIsNormalized; /// /// SurfaceDataAttributes constructor. @@ -147,13 +164,14 @@ public class SurfaceDataAttributes : System.Attribute /// Field is a direction. /// Field is an sRGB value. /// Field precision. - public SurfaceDataAttributes(string displayName = "", bool isDirection = false, bool sRGBDisplay = false, FieldPrecision precision = FieldPrecision.Default) + public SurfaceDataAttributes(string displayName = "", bool isDirection = false, bool sRGBDisplay = false, FieldPrecision precision = FieldPrecision.Default, bool checkIsNormalized = false) { displayNames = new string[1]; displayNames[0] = displayName; this.isDirection = isDirection; this.sRGBDisplay = sRGBDisplay; this.precision = precision; + this.checkIsNormalized = checkIsNormalized; } // We allow users to add several names for one field, so user can override the auto behavior and do something else with the same data @@ -165,12 +183,13 @@ public SurfaceDataAttributes(string displayName = "", bool isDirection = false, /// Field is a direction. /// Field is an sRGB value. /// Field precision. - public SurfaceDataAttributes(string[] displayNames, bool isDirection = false, bool sRGBDisplay = false, FieldPrecision precision = FieldPrecision.Default) + public SurfaceDataAttributes(string[] displayNames, bool isDirection = false, bool sRGBDisplay = false, bool checkIsNormalized = false, FieldPrecision precision = FieldPrecision.Default) { this.displayNames = displayNames; this.isDirection = isDirection; this.sRGBDisplay = sRGBDisplay; this.precision = precision; + this.checkIsNormalized = checkIsNormalized; } } @@ -183,7 +202,7 @@ public class HLSLArray : System.Attribute /// /// Size of the array. /// - public int arraySize; + public int arraySize; /// /// Type of the array elements. /// @@ -235,6 +254,10 @@ public class PackingAttribute : System.Attribute /// True if the field is an sRGB value. /// public bool sRGBDisplay; + /// + /// True if the field is an sRGB value. + /// + public bool checkIsNormalized; /// /// Packing Attribute constructor. @@ -247,13 +270,14 @@ public class PackingAttribute : System.Attribute /// Maximum value. /// Field is a direction. /// Field is an sRGB value. - public PackingAttribute(string[] displayNames, FieldPacking packingScheme = FieldPacking.NoPacking, int bitSize = 32, int offsetInSource = 0, float minValue = 0.0f, float maxValue = 1.0f, bool isDirection = false, bool sRGBDisplay = false) + public PackingAttribute(string[] displayNames, FieldPacking packingScheme = FieldPacking.NoPacking, int bitSize = 32, int offsetInSource = 0, float minValue = 0.0f, float maxValue = 1.0f, bool isDirection = false, bool sRGBDisplay = false, bool checkIsNormalized = false) { this.displayNames = displayNames; this.packingScheme = packingScheme; this.offsetInSource = offsetInSource; this.isDirection = isDirection; this.sRGBDisplay = sRGBDisplay; + this.checkIsNormalized = checkIsNormalized; this.sizeInBits = bitSize; this.range = new float[] { minValue, maxValue }; } @@ -269,7 +293,7 @@ public PackingAttribute(string[] displayNames, FieldPacking packingScheme = Fiel /// Maximum value. /// Field is a direction. /// Field is an sRGB value. - public PackingAttribute(string displayName = "", FieldPacking packingScheme = FieldPacking.NoPacking, int bitSize = 0, int offsetInSource = 0, float minValue = 0.0f, float maxValue = 1.0f, bool isDirection = false, bool sRGBDisplay = false) + public PackingAttribute(string displayName = "", FieldPacking packingScheme = FieldPacking.NoPacking, int bitSize = 0, int offsetInSource = 0, float minValue = 0.0f, float maxValue = 1.0f, bool isDirection = false, bool sRGBDisplay = false, bool checkIsNormalized = false) { displayNames = new string[1]; displayNames[0] = displayName; @@ -277,10 +301,17 @@ public PackingAttribute(string displayName = "", FieldPacking packingScheme = Fi this.offsetInSource = offsetInSource; this.isDirection = isDirection; this.sRGBDisplay = sRGBDisplay; + this.checkIsNormalized = checkIsNormalized; this.sizeInBits = bitSize; this.range = new float[] { minValue, maxValue }; } } + /// + /// This type needs to be used when generating unsigned integer arrays for constant buffers. + /// + public struct ShaderGenUInt4 + { + } } diff --git a/Runtime/Textures/RTHandle.cs b/Runtime/Textures/RTHandle.cs index 2f841b6..7441d16 100644 --- a/Runtime/Textures/RTHandle.cs +++ b/Runtime/Textures/RTHandle.cs @@ -50,6 +50,11 @@ public class RTHandle /// public string name { get { return m_Name; } } + /// + /// Returns true is MSAA is enabled, false otherwise. + /// + public bool isMSAAEnabled { get { return m_EnableMSAA; } } + // Keep constructor private internal RTHandle(RTHandleSystem owner) { @@ -63,6 +68,10 @@ internal RTHandle(RTHandleSystem owner) /// RenderTexture representation of the RTHandle. public static implicit operator RenderTexture(RTHandle handle) { + // If RTHandle is null then conversion should give a null RenderTexture + if (handle == null) + return null; + Debug.Assert(handle.rt != null, "RTHandle was created using a regular Texture and is used as a RenderTexture"); return handle.rt; } @@ -74,6 +83,10 @@ internal RTHandle(RTHandleSystem owner) /// Texture representation of the RTHandle. public static implicit operator Texture(RTHandle handle) { + // If RTHandle is null then conversion should give a null Texture + if (handle == null) + return null; + Debug.Assert(handle.m_ExternalTexture != null || handle.rt != null); return (handle.rt != null) ? handle.rt : handle.m_ExternalTexture; } @@ -102,6 +115,13 @@ internal void SetTexture(Texture tex) m_NameID = new RenderTargetIdentifier(tex); } + internal void SetTexture(RenderTargetIdentifier tex) + { + m_RT = null; + m_ExternalTexture = null; + m_NameID = tex; + } + /// /// Release the RTHandle /// @@ -133,5 +153,50 @@ public Vector2Int GetScaledSize(Vector2Int refSize) ); } } + +#if UNITY_2020_2_OR_NEWER + /// + /// Switch the render target to fast memory on platform that have it. + /// + /// Command buffer used for rendering. + /// How much of the render target is to be switched into fast memory (between 0 and 1). + /// Flag to determine what parts of the render target is spilled if not fully resident in fast memory. + /// Whether the content of render target are copied or not when switching to fast memory. + + public void SwitchToFastMemory(CommandBuffer cmd, + float residencyFraction = 1.0f, + FastMemoryFlags flags = FastMemoryFlags.SpillTop, + bool copyContents = false + ) + { + residencyFraction = Mathf.Clamp01(residencyFraction); + cmd.SwitchIntoFastMemory(m_RT, flags, residencyFraction, copyContents); + } + + /// + /// Switch the render target to fast memory on platform that have it and copies the content. + /// + /// Command buffer used for rendering. + /// How much of the render target is to be switched into fast memory (between 0 and 1). + /// Flag to determine what parts of the render target is spilled if not fully resident in fast memory. + public void CopyToFastMemory(CommandBuffer cmd, + float residencyFraction = 1.0f, + FastMemoryFlags flags = FastMemoryFlags.SpillTop + ) + { + SwitchToFastMemory(cmd, residencyFraction, flags, copyContents: true); + } + + /// + /// Switch out the render target from fast memory back to main memory on platforms that have fast memory. + /// + /// Command buffer used for rendering. + /// Whether the content of render target are copied or not when switching out fast memory. + public void SwitchOutFastMemory(CommandBuffer cmd, bool copyContents = true) + { + cmd.SwitchOutOfFastMemory(m_RT, copyContents); + } +#endif + } } diff --git a/Runtime/Textures/RTHandleSystem.cs b/Runtime/Textures/RTHandleSystem.cs index 3934b05..08ba14e 100644 --- a/Runtime/Textures/RTHandleSystem.cs +++ b/Runtime/Textures/RTHandleSystem.cs @@ -96,7 +96,15 @@ public void Dispose() /// Number of MSAA samples for automatically scaled RTHandles. public void Initialize(int width, int height, bool scaledRTsupportsMSAA, MSAASamples scaledRTMSAASamples) { - Debug.Assert(m_AutoSizedRTs.Count == 0, "RTHandle.Initialize should only be called once before allocating any Render Texture. This may be caused by an unreleased RTHandle resource."); + if (m_AutoSizedRTs.Count != 0) + { + string leakingResources = "Unreleased RTHandles:"; + foreach (var rt in m_AutoSizedRTs) + { + leakingResources = string.Format("{0}\n {1}", leakingResources, rt.name); + } + Debug.LogError(string.Format("RTHandle.Initialize should only be called once before allocating any Render Texture. This may be caused by an unreleased RTHandle resource.\n{0}\n", leakingResources)); + } m_MaxWidths = width; m_MaxHeights = height; @@ -185,9 +193,11 @@ public void SetReferenceSize(int width, int height, MSAASamples msaaSamples, boo lastFrameMaxSize = new Vector2(GetMaxWidth(), GetMaxHeight()); } - if (DynamicResolutionHandler.instance.HardwareDynamicResIsEnabled()) + if (DynamicResolutionHandler.instance.HardwareDynamicResIsEnabled() && m_HardwareDynamicResRequested) { - m_RTHandleProperties.rtHandleScale = new Vector4(1.0f, 1.0f, 1.0f, 1.0f); + float xScale = (float)DynamicResolutionHandler.instance.finalViewport.x / GetMaxWidth(); + float yScale = (float)DynamicResolutionHandler.instance.finalViewport.y / GetMaxHeight(); + m_RTHandleProperties.rtHandleScale = new Vector4(xScale, yScale, m_RTHandleProperties.rtHandleScale.x, m_RTHandleProperties.rtHandleScale.y); } else { @@ -204,10 +214,12 @@ public void SetReferenceSize(int width, int height, MSAASamples msaaSamples, boo /// State of hardware dynamic resolution. public void SetHardwareDynamicResolutionState(bool enableHWDynamicRes) { - if(enableHWDynamicRes != m_HardwareDynamicResRequested && m_AutoSizedRTsArray != null) + if(enableHWDynamicRes != m_HardwareDynamicResRequested) { m_HardwareDynamicResRequested = enableHWDynamicRes; + Array.Resize(ref m_AutoSizedRTsArray, m_AutoSizedRTs.Count); + m_AutoSizedRTs.CopyTo(m_AutoSizedRTsArray); for (int i = 0, c = m_AutoSizedRTsArray.Length; i < c; ++i) { var rth = m_AutoSizedRTsArray[i]; @@ -224,7 +236,6 @@ public void SetHardwareDynamicResolutionState(bool enableHWDynamicRes) // Create the render texture renderTexture.Create(); } - } } } @@ -480,7 +491,7 @@ void Resize(int width, int height, MSAASamples msaaSamples, bool sizeChanged, bo bindTextureMS = bindTextureMS, useDynamicScale = m_HardwareDynamicResRequested && useDynamicScale, memorylessMode = memoryless, - name = CoreUtils.GetRenderTargetAutoName(width, height, slices, GraphicsFormatUtility.GetRenderTextureFormat(colorFormat), name, mips: useMipMap, enableMSAA: enableMSAA, msaaSamples: msaaSamples) + name = CoreUtils.GetRenderTargetAutoName(width, height, slices, colorFormat, name, mips: useMipMap, enableMSAA: enableMSAA, msaaSamples: msaaSamples) }; } @@ -736,7 +747,7 @@ string name useDynamicScale = m_HardwareDynamicResRequested && useDynamicScale, memorylessMode = memoryless, stencilFormat = stencilFormat, - name = CoreUtils.GetRenderTargetAutoName(width, height, slices, GraphicsFormatUtility.GetRenderTextureFormat(colorFormat), name, mips: useMipMap, enableMSAA: allocForMSAA, msaaSamples: m_ScaledRTCurrentMSAASamples) + name = CoreUtils.GetRenderTargetAutoName(width, height, slices, colorFormat, name, mips: useMipMap, enableMSAA: allocForMSAA, msaaSamples: m_ScaledRTCurrentMSAASamples) }; } else @@ -757,7 +768,7 @@ string name bindTextureMS = bindTextureMS, useDynamicScale = m_HardwareDynamicResRequested && useDynamicScale, memorylessMode = memoryless, - name = CoreUtils.GetRenderTargetAutoName(width, height, slices, GraphicsFormatUtility.GetRenderTextureFormat(colorFormat), name, mips: useMipMap, enableMSAA: allocForMSAA, msaaSamples: m_ScaledRTCurrentMSAASamples) + name = CoreUtils.GetRenderTargetAutoName(width, height, slices, colorFormat, name, mips: useMipMap, enableMSAA: allocForMSAA, msaaSamples: m_ScaledRTCurrentMSAASamples) }; } @@ -774,6 +785,23 @@ string name return rth; } + /// + /// Allocate a RTHandle from a regular RenderTexture. + /// + /// Input texture + /// A new RTHandle referencing the input texture. + public RTHandle Alloc(RenderTexture texture) + { + var rth = new RTHandle(this); + rth.SetRenderTexture(texture); + rth.m_EnableMSAA = false; + rth.m_EnableRandomWrite = false; + rth.useScaling = false; + rth.m_EnableHWDynamicScale = false; + rth.m_Name = ""; + return rth; + } + /// /// Allocate a RTHandle from a regular Texture. /// @@ -791,6 +819,34 @@ public RTHandle Alloc(Texture texture) return rth; } + /// + /// Allocate a RTHandle from a regular render target identifier. + /// + /// Input render target identifier. + /// A new RTHandle referencing the input render target identifier. + public RTHandle Alloc(RenderTargetIdentifier texture) + { + return Alloc(texture, ""); + } + + /// + /// Allocate a RTHandle from a regular render target identifier. + /// + /// Input render target identifier. + /// Name of the texture. + /// A new RTHandle referencing the input render target identifier. + public RTHandle Alloc(RenderTargetIdentifier texture, string name) + { + var rth = new RTHandle(this); + rth.SetTexture(texture); + rth.m_EnableMSAA = false; + rth.m_EnableRandomWrite = false; + rth.useScaling = false; + rth.m_EnableHWDynamicScale = false; + rth.m_Name = name; + return rth; + } + private static RTHandle Alloc(RTHandle tex) { Debug.LogError("Allocation a RTHandle from another one is forbidden."); diff --git a/Runtime/Textures/RTHandles.cs b/Runtime/Textures/RTHandles.cs index 1994756..a22c2be 100644 --- a/Runtime/Textures/RTHandles.cs +++ b/Runtime/Textures/RTHandles.cs @@ -230,6 +230,37 @@ public static RTHandle Alloc(Texture tex) return s_DefaultInstance.Alloc(tex); } + /// + /// Allocate a RTHandle from a regular RenderTexture for the default RTHandle system. + /// + /// Input texture + /// A new RTHandle referencing the input texture. + public static RTHandle Alloc(RenderTexture tex) + { + return s_DefaultInstance.Alloc(tex); + } + + /// + /// Allocate a RTHandle from a regular render target identifier for the default RTHandle system. + /// + /// Input render target identifier. + /// A new RTHandle referencing the input render target identifier. + public static RTHandle Alloc(RenderTargetIdentifier tex) + { + return s_DefaultInstance.Alloc(tex); + } + + /// + /// Allocate a RTHandle from a regular render target identifier for the default RTHandle system. + /// + /// Input render target identifier. + /// Name of the render target. + /// A new RTHandle referencing the input render target identifier. + public static RTHandle Alloc(RenderTargetIdentifier tex, string name) + { + return s_DefaultInstance.Alloc(tex, name); + } + private static RTHandle Alloc(RTHandle tex) { Debug.LogError("Allocation a RTHandle from another one is forbidden."); diff --git a/Runtime/Textures/TextureXR.cs b/Runtime/Textures/TextureXR.cs index d73dded..e127f25 100644 --- a/Runtime/Textures/TextureXR.cs +++ b/Runtime/Textures/TextureXR.cs @@ -91,9 +91,11 @@ public static TextureDimension dimension /// The default magenta texture. public static RTHandle GetMagentaTexture() { return useTexArray ? m_MagentaTexture2DArrayRTH : m_MagentaTextureRTH; } + static Texture3D m_BlackTexture3D; static Texture2DArray m_BlackTexture2DArray; static RTHandle m_BlackTexture2DArrayRTH; static RTHandle m_BlackTextureRTH; + static RTHandle m_BlackTexture3DRTH; /// /// Default black texture. /// @@ -104,6 +106,11 @@ public static TextureDimension dimension /// /// The default black texture array. public static RTHandle GetBlackTextureArray() { return m_BlackTexture2DArrayRTH; } + /// + /// Default black texture 3D. + /// + /// The default black texture 3D. + public static RTHandle GetBlackTexture3D() { return m_BlackTexture3DRTH; } static Texture2DArray m_WhiteTexture2DArray; static RTHandle m_WhiteTexture2DArrayRTH; @@ -157,6 +164,9 @@ public static void Initialize(CommandBuffer cmd, ComputeShader clearR32_UIntShad RTHandles.Release(m_BlackTexture2DArrayRTH); m_BlackTexture2DArray = CreateTexture2DArrayFromTexture2D(Texture2D.blackTexture, "Black Texture2DArray"); m_BlackTexture2DArrayRTH = RTHandles.Alloc(m_BlackTexture2DArray); + RTHandles.Release(m_BlackTexture3DRTH); + m_BlackTexture3D = CreateBlackTexture3D("Black Texture3D"); + m_BlackTexture3DRTH = RTHandles.Alloc(m_BlackTexture3D); // White RTHandles.Release(m_WhiteTextureRTH); @@ -223,5 +233,14 @@ static Texture CreateBlackUintTexture(CommandBuffer cmd, ComputeShader clearR32_ return blackUIntTexture2D as Texture; } + + static Texture3D CreateBlackTexture3D(string name) + { + Texture3D texture3D = new Texture3D(width: 1, height: 1, depth: 1, textureFormat: TextureFormat.RGBA32, mipChain: false); + texture3D.name = name; + texture3D.SetPixel(0, 0, 0, Color.black, 0); + texture3D.Apply(updateMipmaps: false); + return texture3D; + } } } diff --git a/Runtime/Unity.RenderPipelines.Core.Runtime.asmdef b/Runtime/Unity.RenderPipelines.Core.Runtime.asmdef index ee0a377..5018922 100644 --- a/Runtime/Unity.RenderPipelines.Core.Runtime.asmdef +++ b/Runtime/Unity.RenderPipelines.Core.Runtime.asmdef @@ -25,7 +25,12 @@ "name": "com.unity.inputsystem", "expression": "0.0.0", "define": "ENABLE_INPUT_SYSTEM_PACKAGE" + }, + { + "name": "com.unity.rendering.hybrid", + "expression": "0.6.0-preview.0", + "define": "HYBRID_0_6_0_OR_NEWER" } ], "noEngineReferences": false -} \ No newline at end of file +} diff --git a/Runtime/Utilities/ColorUtils.cs b/Runtime/Utilities/ColorUtils.cs index 651b6c9..07c9f40 100644 --- a/Runtime/Utilities/ColorUtils.cs +++ b/Runtime/Utilities/ColorUtils.cs @@ -7,6 +7,28 @@ namespace UnityEngine.Rendering /// public static class ColorUtils { + /// + /// Calibration constant (K) used for our virtual reflected light meter. Modifying this will lead to a change on how average scene luminance + /// gets mapped to exposure. + /// + static public float s_LightMeterCalibrationConstant = 12.5f; + + /// + /// Factor used for our lens system w.r.t. exposure calculation. Modifying this will lead to a change on how linear exposure + /// multipliers are computed from EV100 values (and viceversa). s_LensAttenuation models transmission attenuation and lens vignetting. + /// Note that according to the standard ISO 12232, a lens saturates at s_LensAttenuation = 0.78f (under ISO 100). + /// + static public float s_LensAttenuation = 0.65f; + + /// + /// Scale applied to exposure caused by lens imperfection. It is computed from s_LensAttenuation as follow: + /// (78 / ( S * q )) where S = 100 and q = s_LensAttenuation + /// + static public float lensImperfectionExposureScale + { + get => (78.0f / (100.0f * s_LensAttenuation)); + } + /// /// An analytical model of chromaticity of the standard illuminant, by Judd et al. /// http://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D @@ -215,10 +237,10 @@ public static float ConvertEV100ToExposure(float EV100) // Compute the maximum luminance possible with H_sbs sensitivity // maxLum = 78 / ( S * q ) * N^2 / t // = 78 / ( S * q ) * 2^ EV_100 - // = 78 / (100 * 0.65) * 2^ EV_100 - // = 1.2 * 2^ EV + // = 78 / (100 * s_LensAttenuation) * 2^ EV_100 + // = lensImperfectionExposureScale * 2^ EV // Reference: http://en.wikipedia.org/wiki/Film_speed - float maxLuminance = 1.2f * Mathf.Pow(2.0f, EV100); + float maxLuminance = lensImperfectionExposureScale * Mathf.Pow(2.0f, EV100); return 1.0f / maxLuminance; } @@ -231,10 +253,10 @@ public static float ConvertExposureToEV100(float exposure) { // Compute the maximum luminance possible with H_sbs sensitivity // EV_100 = log2( S * q / (78 * exposure) ) - // = log2( 100 * 0.65 / (78 * exposure) ) - // = log2( 1.0f / (1.2 * exposure) ) + // = log2( 100 * s_LensAttenuation / (78 * exposure) ) + // = log2( 1.0f / (lensImperfectionExposureScale * exposure) ) // Reference: http://en.wikipedia.org/wiki/Film_speed - return Mathf.Log(1.0f / (1.2f * exposure), 2.0f); + return Mathf.Log(1.0f / (lensImperfectionExposureScale * exposure), 2.0f); } /// @@ -244,13 +266,14 @@ public static float ConvertExposureToEV100(float exposure) /// An exposure value, in EV100. public static float ComputeEV100FromAvgLuminance(float avgLuminance) { - // We later use the middle gray at 12.7% in order to have + // The middle grey used will be determined by the s_LightMeterCalibrationConstant. + // The suggested (ISO 2720) range is 10.64 to 13.4. Common values used by + // manufacturers range from 11.37 to 14. Ref: https://en.wikipedia.org/wiki/Light_meter + // The default is 12.5% as it is the closest to 12.7% in order to have // a middle gray at 18% with a sqrt(2) room for specular highlights - // But here we deal with the spot meter measuring the middle gray - // which is fixed at 12.5 for matching standard camera - // constructor settings (i.e. calibration constant K = 12.5) - // Reference: http://en.wikipedia.org/wiki/Film_speed - const float K = 12.5f; // Reflected-light meter calibration constant + // Note that this gives equivalent results as using an incident light meter + // with a calibration constant of C=314. + float K = s_LightMeterCalibrationConstant; return Mathf.Log(avgLuminance * 100f / K, 2f); } diff --git a/Runtime/Utilities/CoreMatrixUtils.cs b/Runtime/Utilities/CoreMatrixUtils.cs new file mode 100644 index 0000000..20e5fd7 --- /dev/null +++ b/Runtime/Utilities/CoreMatrixUtils.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine.Experimental.Rendering; + +namespace UnityEngine.Rendering +{ + using UnityObject = UnityEngine.Object; + + /// + /// Set of utility functions for the Core Scriptable Render Pipeline Library related to Matrix operations + /// + public static class CoreMatrixUtils + { + /// + /// This function provides the equivalent of multiplying matrix parameter inOutMatrix with a translation matrix defined by the parameter translation. + /// The order of the equivalent multiplication is inOutMatrix * translation. + /// + /// Matrix to multiply with translation. + /// Translation component to multiply to the matrix. + public static void MatrixTimesTranslation(ref Matrix4x4 inOutMatrix, Vector3 translation) + { + inOutMatrix.m03 += (inOutMatrix.m00 * translation.x + inOutMatrix.m01 * translation.y + inOutMatrix.m02 * translation.z); + inOutMatrix.m13 += (inOutMatrix.m10 * translation.x + inOutMatrix.m11 * translation.y + inOutMatrix.m12 * translation.z); + inOutMatrix.m23 += (inOutMatrix.m20 * translation.x + inOutMatrix.m21 * translation.y + inOutMatrix.m22 * translation.z); + } + + /// + /// This function provides the equivalent of multiplying a translation matrix defined by the parameter translation with the matrix specified by the parameter inOutMatrix. + /// The order of the equivalent multiplication is translation * inOutMatrix. + /// + /// Matrix to multiply with translation. + /// Translation component to multiply to the matrix. + public static void TranslationTimesMatrix(ref Matrix4x4 inOutMatrix, Vector3 translation) + { + inOutMatrix.m00 += translation.x * inOutMatrix.m30; + inOutMatrix.m01 += translation.x * inOutMatrix.m31; + inOutMatrix.m02 += translation.x * inOutMatrix.m32; + inOutMatrix.m03 += translation.x * inOutMatrix.m33; + + inOutMatrix.m10 += translation.y * inOutMatrix.m30; + inOutMatrix.m11 += translation.y * inOutMatrix.m31; + inOutMatrix.m12 += translation.y * inOutMatrix.m32; + inOutMatrix.m13 += translation.y * inOutMatrix.m33; + + inOutMatrix.m20 += translation.z * inOutMatrix.m30; + inOutMatrix.m21 += translation.z * inOutMatrix.m31; + inOutMatrix.m22 += translation.z * inOutMatrix.m32; + inOutMatrix.m23 += translation.z * inOutMatrix.m33; + } + + /// + /// Multiplies a matrix with a perspective matrix. This function is faster than performing the full matrix multiplication. + /// The operation order is perspective * rhs. + /// + /// The perspective matrix to multiply with rhs. + /// A matrix to be multiply to perspective. + /// Returns the matrix that is the result of the multiplication. + public static Matrix4x4 MultiplyPerspectiveMatrix(Matrix4x4 perspective, Matrix4x4 rhs) + { + Matrix4x4 outMat; + outMat.m00 = perspective.m00 * rhs.m00; + outMat.m01 = perspective.m00 * rhs.m01; + outMat.m02 = perspective.m00 * rhs.m02; + outMat.m03 = perspective.m00 * rhs.m03; + + outMat.m10 = perspective.m11 * rhs.m10; + outMat.m11 = perspective.m11 * rhs.m11; + outMat.m12 = perspective.m11 * rhs.m12; + outMat.m13 = perspective.m11 * rhs.m13; + + outMat.m20 = perspective.m22 * rhs.m20 + perspective.m23 * rhs.m30; + outMat.m21 = perspective.m22 * rhs.m21 + perspective.m23 * rhs.m31; + outMat.m22 = perspective.m22 * rhs.m22 + perspective.m23 * rhs.m32; + outMat.m23 = perspective.m22 * rhs.m23 + perspective.m23 * rhs.m33; + + outMat.m30 = -rhs.m20; + outMat.m31 = -rhs.m21; + outMat.m32 = -rhs.m22; + outMat.m33 = -rhs.m23; + + return outMat; + } + + // An orthographic projection is centered if (right+left) == 0 and (top+bottom) == 0 + private static Matrix4x4 MultiplyOrthoMatrixCentered(Matrix4x4 ortho, Matrix4x4 rhs) + { + Matrix4x4 outMat; + outMat.m00 = ortho.m00 * rhs.m00; + outMat.m01 = ortho.m00 * rhs.m01; + outMat.m02 = ortho.m00 * rhs.m02; + outMat.m03 = ortho.m00 * rhs.m03; + + outMat.m10 = ortho.m11 * rhs.m10; + outMat.m11 = ortho.m11 * rhs.m11; + outMat.m12 = ortho.m11 * rhs.m12; + outMat.m13 = ortho.m11 * rhs.m13; + + outMat.m20 = ortho.m22 * rhs.m20 + ortho.m23 * rhs.m30; + outMat.m21 = ortho.m22 * rhs.m21 + ortho.m23 * rhs.m31; + outMat.m22 = ortho.m22 * rhs.m22 + ortho.m23 * rhs.m32; + outMat.m23 = ortho.m22 * rhs.m23 + ortho.m23 * rhs.m33; + + outMat.m30 = rhs.m20; + outMat.m31 = rhs.m21; + outMat.m32 = rhs.m22; + outMat.m33 = rhs.m23; + + return outMat; + } + + // General case has m03 and m13 != 0 + private static Matrix4x4 MultiplyGenericOrthoMatrix(Matrix4x4 ortho, Matrix4x4 rhs) + { + Matrix4x4 outMat; + outMat.m00 = ortho.m00 * rhs.m00 + ortho.m03 * rhs.m30; + outMat.m01 = ortho.m00 * rhs.m01 + ortho.m03 * rhs.m31; + outMat.m02 = ortho.m00 * rhs.m02 + ortho.m03 * rhs.m32; + outMat.m03 = ortho.m00 * rhs.m03 + ortho.m03 * rhs.m33; + + outMat.m10 = ortho.m11 * rhs.m10 + ortho.m13 * rhs.m30; + outMat.m11 = ortho.m11 * rhs.m11 + ortho.m13 * rhs.m31; + outMat.m12 = ortho.m11 * rhs.m12 + ortho.m13 * rhs.m32; + outMat.m13 = ortho.m11 * rhs.m13 + ortho.m13 * rhs.m33; + + outMat.m20 = ortho.m22 * rhs.m20 + ortho.m23 * rhs.m30; + outMat.m21 = ortho.m22 * rhs.m21 + ortho.m23 * rhs.m31; + outMat.m22 = ortho.m22 * rhs.m22 + ortho.m23 * rhs.m32; + outMat.m23 = ortho.m22 * rhs.m23 + ortho.m23 * rhs.m33; + + outMat.m30 = rhs.m20; + outMat.m31 = rhs.m21; + outMat.m32 = rhs.m22; + outMat.m33 = rhs.m23; + return outMat; + } + + /// + /// Multiplies a matrix with an orthographic matrix. This function is faster than performing the full matrix multiplication. + /// The operation order is ortho * rhs. + /// + /// The ortho matrix to multiply with rhs. + /// A matrix to be multiply to perspective. + /// If true, it means that right and left are equivalently distant from center and similarly top/bottom are equivalently distant from center. + /// Returns the matrix that is the result of the multiplication. + public static Matrix4x4 MultiplyOrthoMatrix(Matrix4x4 ortho, Matrix4x4 rhs, bool centered) + { + return centered ? MultiplyGenericOrthoMatrix(ortho, rhs) : MultiplyOrthoMatrixCentered(ortho, rhs); + } + + + /// + /// Multiplies a matrix with a projection matrix. This function is faster than performing the full matrix multiplication. + /// The operation order is projMatrix * rhs. + /// + /// The projection matrix to multiply with rhs. + /// A matrix to be multiply to perspective. + /// If true, the projection matrix is a centered ( right+left == top+bottom == 0) orthographic projection, otherwise it is a perspective matrix.. + /// Returns the matrix that is the result of the multiplication. + public static Matrix4x4 MultiplyProjectionMatrix(Matrix4x4 projMatrix, Matrix4x4 rhs, bool orthoCentered) + { + return orthoCentered + ? MultiplyOrthoMatrixCentered(projMatrix, rhs) + : MultiplyPerspectiveMatrix(projMatrix, rhs); + } + } +} diff --git a/Runtime/Utilities/CoreMatrixUtils.cs.meta b/Runtime/Utilities/CoreMatrixUtils.cs.meta new file mode 100644 index 0000000..90b1e7e --- /dev/null +++ b/Runtime/Utilities/CoreMatrixUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d96647d3fcceac0429f12837ce7e2e3e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Utilities/CoreUtils.cs b/Runtime/Utilities/CoreUtils.cs index db7e59f..4b44df4 100644 --- a/Runtime/Utilities/CoreUtils.cs +++ b/Runtime/Utilities/CoreUtils.cs @@ -579,7 +579,7 @@ public static void SetViewport(CommandBuffer cmd, RTHandle target) /// Generated names bassed on the provided parameters. public static string GetRenderTargetAutoName(int width, int height, int depth, RenderTextureFormat format, string name, bool mips = false, bool enableMSAA = false, MSAASamples msaaSamples = MSAASamples.None) => GetRenderTargetAutoName(width, height, depth, format.ToString(), name, mips, enableMSAA, msaaSamples); - + /// /// Generate a name based on render texture parameters. /// @@ -879,6 +879,20 @@ public static void SetKeyword(Material material, string keyword, bool state) material.DisableKeyword(keyword); } + /// + /// Set a keyword to a compute shader + /// + /// Compute Shader on which to set the keyword. + /// Keyword to be set. + /// Value of the keyword to be set. + public static void SetKeyword(ComputeShader cs, string keyword, bool state) + { + if (state) + cs.EnableKeyword(keyword); + else + cs.DisableKeyword(keyword); + } + /// /// Destroys a UnityObject safely. /// @@ -1045,7 +1059,11 @@ public static bool AreAnimatedMaterialsEnabled(Camera camera) for (int i = 0; i < UnityEditor.SceneView.sceneViews.Count; i++) // Using a foreach on an ArrayList generates garbage ... { var sv = UnityEditor.SceneView.sceneViews[i] as UnityEditor.SceneView; + #if UNITY_2020_2_OR_NEWER + if (sv.camera == camera && sv.sceneViewState.alwaysRefreshEnabled) + #else if (sv.camera == camera && sv.sceneViewState.materialUpdateEnabled) + #endif { animateMaterials = true; break; @@ -1174,5 +1192,29 @@ public static bool IsSceneViewFogEnabled(Camera camera) return fogEnable; } + + /// + /// Draw a renderer list. + /// + /// Current Scriptable Render Context. + /// Command Buffer used for rendering. + /// Renderer List to render. + public static void DrawRendererList(ScriptableRenderContext renderContext, CommandBuffer cmd, RendererList rendererList) + { + if (!rendererList.isValid) + throw new ArgumentException("Invalid renderer list provided to DrawRendererList"); + + // This is done here because DrawRenderers API lives outside command buffers so we need to make call this before doing any DrawRenders or things will be executed out of order + renderContext.ExecuteCommandBuffer(cmd); + cmd.Clear(); + + if (rendererList.stateBlock == null) + renderContext.DrawRenderers(rendererList.cullingResult, ref rendererList.drawSettings, ref rendererList.filteringSettings); + else + { + var renderStateBlock = rendererList.stateBlock.Value; + renderContext.DrawRenderers(rendererList.cullingResult, ref rendererList.drawSettings, ref rendererList.filteringSettings, ref renderStateBlock); + } + } } } diff --git a/Runtime/Utilities/MaterialQuality.cs b/Runtime/Utilities/MaterialQuality.cs index 4418791..c1590ff 100644 --- a/Runtime/Utilities/MaterialQuality.cs +++ b/Runtime/Utilities/MaterialQuality.cs @@ -1,13 +1,13 @@ using System; -using UnityEngine; -using UnityEngine.Rendering; +using UnityEngine.Scripting.APIUpdating; -namespace Utilities +namespace UnityEngine.Rendering { /// /// Material quality flags. /// [Flags] + [MovedFrom("Utilities")] public enum MaterialQuality { /// Low Material Quality. @@ -21,6 +21,7 @@ public enum MaterialQuality /// /// Material Quality utility class. /// + [MovedFrom("Utilities")] public static class MaterialQualityUtilities { /// diff --git a/Runtime/Utilities/TextureCurve.cs b/Runtime/Utilities/TextureCurve.cs index 6d9f5a7..8a5e0cd 100644 --- a/Runtime/Utilities/TextureCurve.cs +++ b/Runtime/Utilities/TextureCurve.cs @@ -252,6 +252,9 @@ public class TextureCurveParameter : VolumeParameter public TextureCurveParameter(TextureCurve value, bool overrideState = false) : base(value, overrideState) { } + /// + /// Release implementation. + /// public override void Release() => m_Value.Release(); // TODO: TextureCurve interpolation diff --git a/Runtime/Volume/VolumeComponent.cs b/Runtime/Volume/VolumeComponent.cs index ab12104..0c00bfa 100644 --- a/Runtime/Volume/VolumeComponent.cs +++ b/Runtime/Volume/VolumeComponent.cs @@ -154,11 +154,12 @@ public virtual void Override(VolumeComponent state, float interpFactor) var stateParam = state.parameters[i]; var toParam = parameters[i]; - // Keep track of the override state for debugging purpose - stateParam.overrideState = toParam.overrideState; - if (toParam.overrideState) + { + // Keep track of the override state for debugging purpose + stateParam.overrideState = toParam.overrideState; stateParam.Interp(stateParam, toParam, interpFactor); + } } } diff --git a/Runtime/Volume/VolumeManager.cs b/Runtime/Volume/VolumeManager.cs index 0093f52..f333268 100644 --- a/Runtime/Volume/VolumeManager.cs +++ b/Runtime/Volume/VolumeManager.cs @@ -124,7 +124,8 @@ public void Register(Volume volume, int layer) // Look for existing cached layer masks and add it there if needed foreach (var kvp in m_SortedVolumes) { - if ((kvp.Key & (1 << layer)) != 0) + // We add the volume to sorted lists only if the layer match and if it doesn't contain the volume already. + if ((kvp.Key & (1 << layer)) != 0 && !kvp.Value.Contains(volume)) kvp.Value.Add(volume); } @@ -225,7 +226,10 @@ void ReplaceData(VolumeStack stack, List components) int count = component.parameters.Count; for (int i = 0; i < count; i++) + { + target.parameters[i].overrideState = false; target.parameters[i].SetValue(component.parameters[i]); + } } } @@ -383,6 +387,17 @@ public void Update(VolumeStack stack, Transform trigger, LayerMask layerMask) } } + /// + /// Get all volumes on a given layer mask sorted by influence. + /// + /// The LayerMask that Unity uses to filter Volumes that it should consider. + /// An array of volume. + public Volume[] GetVolumes(LayerMask layerMask) + { + var volumes = GrabVolumes(layerMask); + return volumes.ToArray(); + } + List GrabVolumes(LayerMask mask) { List list; diff --git a/Runtime/XR.meta b/Runtime/XR.meta new file mode 100644 index 0000000..7e92ea6 --- /dev/null +++ b/Runtime/XR.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8c62d11ee7af4f54abf48d79f70aee4d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/XR/XRGraphicsAutomatedTests.cs b/Runtime/XR/XRGraphicsAutomatedTests.cs new file mode 100644 index 0000000..50c7caa --- /dev/null +++ b/Runtime/XR/XRGraphicsAutomatedTests.cs @@ -0,0 +1,23 @@ +using System; + +namespace UnityEngine.Rendering +{ + /// + /// Utility class to connect SRP to automated test framework. + /// + public static class XRGraphicsAutomatedTests + { + // XR tests can be enabled from the command line. Cache result to avoid GC. + static bool activatedFromCommandLine { get => Array.Exists(Environment.GetCommandLineArgs(), arg => arg == "-xr-tests"); } + + /// + /// Used by render pipelines to initialize XR tests. + /// + public static bool enabled { get; } = activatedFromCommandLine; + + /// + /// Set by automated test framework and read by render pipelines. + /// + public static bool running = false; + } +} diff --git a/Runtime/XR/XRGraphicsAutomatedTests.cs.meta b/Runtime/XR/XRGraphicsAutomatedTests.cs.meta new file mode 100644 index 0000000..b56e633 --- /dev/null +++ b/Runtime/XR/XRGraphicsAutomatedTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a88e3e436344c9246acd63d2edca6fdf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/ShaderLibrary/ACES.hlsl b/ShaderLibrary/ACES.hlsl index 022ce74..1651e77 100644 --- a/ShaderLibrary/ACES.hlsl +++ b/ShaderLibrary/ACES.hlsl @@ -265,7 +265,9 @@ half3 ACEScc_to_ACES(half3 x) // converts ACES2065-1 (AP0 w/ linear encoding) to // ACEScg (AP1 w/ linear encoding) // -half3 ACES_to_ACEScg(half3 x) +// Uses float3 to avoid going out of half-precision bounds +// +float3 ACES_to_ACEScg(float3 x) { return mul(AP0_2_AP1_MAT, x); } @@ -276,7 +278,9 @@ half3 ACES_to_ACEScg(half3 x) // converts ACEScg (AP1 w/ linear encoding) to // ACES2065-1 (AP0 w/ linear encoding) // -half3 ACEScg_to_ACES(half3 x) +// Uses float3 to avoid going out of half-precision bounds +// +float3 ACEScg_to_ACES(float3 x) { return mul(AP1_2_AP0_MAT, x); } diff --git a/ShaderLibrary/API/D3D11.hlsl b/ShaderLibrary/API/D3D11.hlsl index d58682b..8fa81bf 100644 --- a/ShaderLibrary/API/D3D11.hlsl +++ b/ShaderLibrary/API/D3D11.hlsl @@ -17,6 +17,7 @@ #define PLATFORM_SUPPORTS_EXPLICIT_BINDING #define PLATFORM_NEEDS_UNORM_UAV_SPECIFIER #define PLATFORM_SUPPORTS_BUFFER_ATOMICS_IN_PIXEL_SHADER +#define PLATFORM_SUPPORTS_PRIMITIVE_ID_IN_PIXEL_SHADER // flow control attributes @@ -66,6 +67,7 @@ #define SAMPLER(samplerName) SamplerState samplerName #define SAMPLER_CMP(samplerName) SamplerComparisonState samplerName +#define ASSIGN_SAMPLER(samplerName, samplerValue) samplerName = samplerValue #define TEXTURE2D_PARAM(textureName, samplerName) TEXTURE2D(textureName), SAMPLER(samplerName) #define TEXTURE2D_ARRAY_PARAM(textureName, samplerName) TEXTURE2D_ARRAY(textureName), SAMPLER(samplerName) diff --git a/ShaderLibrary/API/GLCore.hlsl b/ShaderLibrary/API/GLCore.hlsl index 38b2f9b..35a1c4f 100644 --- a/ShaderLibrary/API/GLCore.hlsl +++ b/ShaderLibrary/API/GLCore.hlsl @@ -75,6 +75,7 @@ #define SAMPLER(samplerName) SamplerState samplerName #define SAMPLER_CMP(samplerName) SamplerComparisonState samplerName +#define ASSIGN_SAMPLER(samplerName, samplerValue) samplerName = samplerValue #define TEXTURE2D_PARAM(textureName, samplerName) TEXTURE2D(textureName), SAMPLER(samplerName) #define TEXTURE2D_ARRAY_PARAM(textureName, samplerName) TEXTURE2D_ARRAY(textureName), SAMPLER(samplerName) @@ -112,7 +113,7 @@ #ifdef UNITY_NO_CUBEMAP_ARRAY #define SAMPLE_TEXTURECUBE_ARRAY(textureName, samplerName, coord3, index) ERROR_ON_UNSUPPORTED_FUNCTION(SAMPLE_TEXTURECUBE_ARRAY) #define SAMPLE_TEXTURECUBE_ARRAY_LOD(textureName, samplerName, coord3, index, lod) ERROR_ON_UNSUPPORTED_FUNCTION(SAMPLE_TEXTURECUBE_ARRAY_LOD) -#define SAMPLE_TEXTURECUBE_ARRAY_LOD(textureName, samplerName, coord3, index, bias) ERROR_ON_UNSUPPORTED_FUNCTION(SAMPLE_TEXTURECUBE_ARRAY_LOD) +#define SAMPLE_TEXTURECUBE_ARRAY_BIAS(textureName, samplerName, coord3, index, bias) ERROR_ON_UNSUPPORTED_FUNCTION(SAMPLE_TEXTURECUBE_ARRAY_BIAS) #else #define SAMPLE_TEXTURECUBE_ARRAY(textureName, samplerName, coord3, index) textureName.Sample(samplerName, float4(coord3, index)) #define SAMPLE_TEXTURECUBE_ARRAY_LOD(textureName, samplerName, coord3, index, lod) textureName.SampleLevel(samplerName, float4(coord3, index), lod) diff --git a/ShaderLibrary/API/GLES2.hlsl b/ShaderLibrary/API/GLES2.hlsl index f1194a3..a6f61f2 100644 --- a/ShaderLibrary/API/GLES2.hlsl +++ b/ShaderLibrary/API/GLES2.hlsl @@ -90,6 +90,7 @@ #define SAMPLER(samplerName) #define SAMPLER_CMP(samplerName) +#define ASSIGN_SAMPLER(samplerName, samplerValue) #define TEXTURE2D_PARAM(textureName, samplerName) sampler2D textureName #define TEXTURE2D_ARRAY_PARAM(textureName, samplerName) samplerCUBE textureName diff --git a/ShaderLibrary/API/GLES3.hlsl b/ShaderLibrary/API/GLES3.hlsl index eb27e97..fb3cb1f 100644 --- a/ShaderLibrary/API/GLES3.hlsl +++ b/ShaderLibrary/API/GLES3.hlsl @@ -52,15 +52,15 @@ #define TEXTURE3D(textureName) Texture3D textureName #define TEXTURE2D_FLOAT(textureName) Texture2D_float textureName -#define TEXTURE2D_ARRAY_FLOAT(textureName) Texture2DArray textureName // no support to _float on Array, it's being added +#define TEXTURE2D_ARRAY_FLOAT(textureName) Texture2DArray_float textureName #define TEXTURECUBE_FLOAT(textureName) TextureCube_float textureName -#define TEXTURECUBE_ARRAY_FLOAT(textureName) TextureCubeArray textureName // no support to _float on Array, it's being added +#define TEXTURECUBE_ARRAY_FLOAT(textureName) TextureCubeArray_float textureName #define TEXTURE3D_FLOAT(textureName) Texture3D_float textureName #define TEXTURE2D_HALF(textureName) Texture2D_half textureName -#define TEXTURE2D_ARRAY_HALF(textureName) Texture2DArray textureName // no support to _float on Array, it's being added +#define TEXTURE2D_ARRAY_HALF(textureName) Texture2DArray_half textureName #define TEXTURECUBE_HALF(textureName) TextureCube_half textureName -#define TEXTURECUBE_ARRAY_HALF(textureName) TextureCubeArray textureName // no support to _float on Array, it's being added +#define TEXTURECUBE_ARRAY_HALF(textureName) TextureCubeArray_half textureName #define TEXTURE3D_HALF(textureName) Texture3D_half textureName #define TEXTURE2D_SHADOW(textureName) TEXTURE2D(textureName) @@ -80,6 +80,7 @@ #define SAMPLER(samplerName) SamplerState samplerName #define SAMPLER_CMP(samplerName) SamplerComparisonState samplerName +#define ASSIGN_SAMPLER(samplerName, samplerValue) samplerName = samplerValue #define TEXTURE2D_PARAM(textureName, samplerName) TEXTURE2D(textureName), SAMPLER(samplerName) #define TEXTURE2D_ARRAY_PARAM(textureName, samplerName) TEXTURE2D_ARRAY(textureName), SAMPLER(samplerName) diff --git a/ShaderLibrary/API/Metal.hlsl b/ShaderLibrary/API/Metal.hlsl index 34631ef..18abc4a 100644 --- a/ShaderLibrary/API/Metal.hlsl +++ b/ShaderLibrary/API/Metal.hlsl @@ -44,15 +44,15 @@ #define TEXTURE3D(textureName) Texture3D textureName #define TEXTURE2D_FLOAT(textureName) Texture2D_float textureName -#define TEXTURE2D_ARRAY_FLOAT(textureName) Texture2DArray textureName // no support to _float on Array, it's being added +#define TEXTURE2D_ARRAY_FLOAT(textureName) Texture2DArray_float textureName #define TEXTURECUBE_FLOAT(textureName) TextureCube_float textureName -#define TEXTURECUBE_ARRAY_FLOAT(textureName) TextureCubeArray textureName // no support to _float on Array, it's being added +#define TEXTURECUBE_ARRAY_FLOAT(textureName) TextureCubeArray_float textureName #define TEXTURE3D_FLOAT(textureName) Texture3D_float textureName #define TEXTURE2D_HALF(textureName) Texture2D_half textureName -#define TEXTURE2D_ARRAY_HALF(textureName) Texture2DArray textureName // no support to _float on Array, it's being added +#define TEXTURE2D_ARRAY_HALF(textureName) Texture2DArray_half textureName #define TEXTURECUBE_HALF(textureName) TextureCube_half textureName -#define TEXTURECUBE_ARRAY_HALF(textureName) TextureCubeArray textureName // no support to _float on Array, it's being added +#define TEXTURECUBE_ARRAY_HALF(textureName) TextureCubeArray_half textureName #define TEXTURE3D_HALF(textureName) Texture3D_half textureName #define TEXTURE2D_SHADOW(textureName) TEXTURE2D(textureName) @@ -66,6 +66,7 @@ #define SAMPLER(samplerName) SamplerState samplerName #define SAMPLER_CMP(samplerName) SamplerComparisonState samplerName +#define ASSIGN_SAMPLER(samplerName, samplerValue) samplerName = samplerValue #define TEXTURE2D_PARAM(textureName, samplerName) TEXTURE2D(textureName), SAMPLER(samplerName) #define TEXTURE2D_ARRAY_PARAM(textureName, samplerName) TEXTURE2D_ARRAY(textureName), SAMPLER(samplerName) diff --git a/ShaderLibrary/API/Switch.hlsl b/ShaderLibrary/API/Switch.hlsl index 81e45de..009617f 100644 --- a/ShaderLibrary/API/Switch.hlsl +++ b/ShaderLibrary/API/Switch.hlsl @@ -44,15 +44,15 @@ #define TEXTURE3D(textureName) Texture3D textureName #define TEXTURE2D_FLOAT(textureName) Texture2D_float textureName -#define TEXTURE2D_ARRAY_FLOAT(textureName) Texture2DArray textureName // no support to _float on Array, it's being added +#define TEXTURE2D_ARRAY_FLOAT(textureName) Texture2DArray_float textureName #define TEXTURECUBE_FLOAT(textureName) TextureCube_float textureName -#define TEXTURECUBE_ARRAY_FLOAT(textureName) TextureCubeArray textureName // no support to _float on Array, it's being added +#define TEXTURECUBE_ARRAY_FLOAT(textureName) TextureCubeArray_float textureName #define TEXTURE3D_FLOAT(textureName) Texture3D_float textureName #define TEXTURE2D_HALF(textureName) Texture2D_half textureName -#define TEXTURE2D_ARRAY_HALF(textureName) Texture2DArray textureName // no support to _float on Array, it's being added +#define TEXTURE2D_ARRAY_HALF(textureName) Texture2DArray_half textureName #define TEXTURECUBE_HALF(textureName) TextureCube_half textureName -#define TEXTURECUBE_ARRAY_HALF(textureName) TextureCubeArray textureName // no support to _float on Array, it's being added +#define TEXTURECUBE_ARRAY_HALF(textureName) TextureCubeArray_half textureName #define TEXTURE3D_HALF(textureName) Texture3D_half textureName #define TEXTURE2D_SHADOW(textureName) TEXTURE2D(textureName) @@ -66,6 +66,7 @@ #define SAMPLER(samplerName) SamplerState samplerName #define SAMPLER_CMP(samplerName) SamplerComparisonState samplerName +#define ASSIGN_SAMPLER(samplerName, samplerValue) samplerName = samplerValue #define TEXTURE2D_PARAM(textureName, samplerName) TEXTURE2D(textureName), SAMPLER(samplerName) #define TEXTURE2D_ARRAY_PARAM(textureName, samplerName) TEXTURE2D_ARRAY(textureName), SAMPLER(samplerName) diff --git a/ShaderLibrary/API/Vulkan.hlsl b/ShaderLibrary/API/Vulkan.hlsl index afc6ca3..a44a87e 100644 --- a/ShaderLibrary/API/Vulkan.hlsl +++ b/ShaderLibrary/API/Vulkan.hlsl @@ -18,6 +18,7 @@ #define PLATFORM_SUPPORTS_EXPLICIT_BINDING #define PLATFORM_NEEDS_UNORM_UAV_SPECIFIER #define PLATFORM_SUPPORTS_BUFFER_ATOMICS_IN_PIXEL_SHADER +#define PLATFORM_SUPPORTS_PRIMITIVE_ID_IN_PIXEL_SHADER // flow control attributes #define UNITY_BRANCH [branch] @@ -44,15 +45,15 @@ #define TEXTURE3D(textureName) Texture3D textureName #define TEXTURE2D_FLOAT(textureName) Texture2D_float textureName -#define TEXTURE2D_ARRAY_FLOAT(textureName) Texture2DArray textureName // no support to _float on Array, it's being added +#define TEXTURE2D_ARRAY_FLOAT(textureName) Texture2DArray_float textureName #define TEXTURECUBE_FLOAT(textureName) TextureCube_float textureName -#define TEXTURECUBE_ARRAY_FLOAT(textureName) TextureCubeArray textureName // no support to _float on Array, it's being added +#define TEXTURECUBE_ARRAY_FLOAT(textureName) TextureCubeArray_float textureName #define TEXTURE3D_FLOAT(textureName) Texture3D_float textureName #define TEXTURE2D_HALF(textureName) Texture2D_half textureName -#define TEXTURE2D_ARRAY_HALF(textureName) Texture2DArray textureName // no support to _float on Array, it's being added +#define TEXTURE2D_ARRAY_HALF(textureName) Texture2DArray_half textureName #define TEXTURECUBE_HALF(textureName) TextureCube_half textureName -#define TEXTURECUBE_ARRAY_HALF(textureName) TextureCubeArray textureName // no support to _float on Array, it's being added +#define TEXTURECUBE_ARRAY_HALF(textureName) TextureCubeArray_half textureName #define TEXTURE3D_HALF(textureName) Texture3D_half textureName #define TEXTURE2D_SHADOW(textureName) TEXTURE2D(textureName) @@ -66,6 +67,7 @@ #define SAMPLER(samplerName) SamplerState samplerName #define SAMPLER_CMP(samplerName) SamplerComparisonState samplerName +#define ASSIGN_SAMPLER(samplerName, samplerValue) samplerName = samplerValue #define TEXTURE2D_PARAM(textureName, samplerName) TEXTURE2D(textureName), SAMPLER(samplerName) #define TEXTURE2D_ARRAY_PARAM(textureName, samplerName) TEXTURE2D_ARRAY(textureName), SAMPLER(samplerName) diff --git a/ShaderLibrary/BSDF.hlsl b/ShaderLibrary/BSDF.hlsl index df1a2b9..52016bc 100644 --- a/ShaderLibrary/BSDF.hlsl +++ b/ShaderLibrary/BSDF.hlsl @@ -140,6 +140,9 @@ TEMPLATE_1_REAL(Fresnel0ToIor, fresnel0, return ((1.0 + sqrt(fresnel0)) / (1.0 - // Optimization: Fit of the function (3 mad) for range [0.04 (should return 0), 1 (should return 1)] TEMPLATE_1_REAL(ConvertF0ForAirInterfaceToF0ForClearCoat15, fresnel0, return saturate(-0.0256868 + fresnel0 * (0.326846 + (0.978946 - 0.283835 * fresnel0) * fresnel0))) +// Even coarser approximation of ConvertF0ForAirInterfaceToF0ForClearCoat15 (above) for mobile (2 mad) +TEMPLATE_1_REAL(ConvertF0ForAirInterfaceToF0ForClearCoat15Fast, fresnel0, return saturate(fresnel0 * (fresnel0 * 0.526868 + 0.529324) - 0.0482256)) + // Artist Friendly Metallic Fresnel Ref: http://jcgt.org/published/0003/04/03/paper.pdf real3 GetIorN(real3 f0, real3 edgeTint) @@ -194,12 +197,6 @@ real G_MaskingSmithGGX(real NdotV, real roughness) return 1.0 / (0.5 + 0.5 * sqrt(1.0 + Sq(roughness) * (1.0 / Sq(NdotV) - 1.0))); } -// Ref: Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs, p. 12. -real D_GGX_Visible(real NdotH, real NdotV, real VdotH, real roughness) -{ - return D_GGX(NdotH, roughness) * G_MaskingSmithGGX(NdotV, roughness) * VdotH / NdotV; -} - // Precompute part of lambdaV real GetSmithJointGGXPartLambdaV(real NdotV, real roughness) { @@ -223,7 +220,7 @@ real V_SmithJointGGX(real NdotL, real NdotV, real roughness, real partLambdaV) real lambdaL = NdotV * sqrt((-NdotL * a2 + NdotL) * NdotL + a2); // Simplify visibility term: (2.0 * NdotL * NdotV) / ((4.0 * NdotL * NdotV) * (lambda_v + lambda_l)) - return 0.5 / (lambdaV + lambdaL); + return 0.5 / max(lambdaV + lambdaL, REAL_MIN); } real V_SmithJointGGX(real NdotL, real NdotV, real roughness) diff --git a/ShaderLibrary/Color.hlsl b/ShaderLibrary/Color.hlsl index a94bb66..20187fc 100644 --- a/ShaderLibrary/Color.hlsl +++ b/ShaderLibrary/Color.hlsl @@ -547,6 +547,11 @@ real3 NeutralCurve(real3 x, real a, real b, real c, real d, real e, real f) return ((x * (a * x + c * b) + d * e) / (x * (a * x + b) + d * f)) - e / f; } +#define TONEMAPPING_CLAMP_MAX 435.18712 //(-b + sqrt(b * b - 4 * a * (HALF_MAX - d * f))) / (2 * a * whiteScale) +//Extremely high values cause NaN output when using fp16, we clamp to avoid the performace hit of switching to fp32 +//The overflow happens in (x * (a * x + b) + d * f) of the NeutralCurve, highest value that avoids fp16 precision errors is ~571.56873 +//Since whiteScale is constant (~1.31338) max input is ~435.18712 + real3 NeutralTonemap(real3 x) { // Tonemap @@ -559,6 +564,10 @@ real3 NeutralTonemap(real3 x) const real whiteLevel = 5.3; const real whiteClip = 1.0; +#if defined(SHADER_API_MOBILE) + x = min(x, TONEMAPPING_CLAMP_MAX); +#endif + real3 whiteScale = (1.0).xxx / NeutralCurve(whiteLevel, a, b, c, d, e, f); x = NeutralCurve(x * whiteScale, a, b, c, d, e, f); x *= whiteScale; diff --git a/ShaderLibrary/Common.hlsl b/ShaderLibrary/Common.hlsl index 3432df7..c5649e6 100644 --- a/ShaderLibrary/Common.hlsl +++ b/ShaderLibrary/Common.hlsl @@ -91,6 +91,7 @@ #define real2x2 half2x2 #define real2x3 half2x3 +#define real2x4 half2x4 #define real3x2 half3x2 #define real3x3 half3x3 #define real3x4 half3x4 @@ -126,6 +127,7 @@ #define real2x2 float2x2 #define real2x3 float2x3 +#define real2x4 float2x4 #define real3x2 float3x2 #define real3x3 float3x3 #define real3x4 float3x4 @@ -182,6 +184,10 @@ #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Macros.hlsl" #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Random.hlsl" +#ifdef SHADER_API_XBOXONE // TODO: to move in .nda package in 21.1 +#define PLATFORM_SUPPORTS_PRIMITIVE_ID_IN_PIXEL_SHADER +#endif + // ---------------------------------------------------------------------------- // Common intrinsic (general implementation of intrinsic available on some platform) // ---------------------------------------------------------------------------- @@ -197,8 +203,8 @@ #define LODDitheringTransition ERROR_ON_UNSUPPORTED_FUNCTION(LODDitheringTransition) #endif -// On everything but GCN consoles we error on cross-lane operations -#ifndef PLATFORM_SUPPORTS_WAVE_INTRINSICS +// On everything but GCN consoles or DXC compiled shaders we error on cross-lane operations +#if !defined(PLATFORM_SUPPORTS_WAVE_INTRINSICS) && !defined(UNITY_COMPILER_DXC) #define WaveActiveAllTrue ERROR_ON_UNSUPPORTED_FUNCTION(WaveActiveAllTrue) #define WaveActiveAnyTrue ERROR_ON_UNSUPPORTED_FUNCTION(WaveActiveAnyTrue) #define WaveGetLaneIndex ERROR_ON_UNSUPPORTED_FUNCTION(WaveGetLaneIndex) @@ -213,6 +219,12 @@ #define WaveGetLaneCount ERROR_ON_UNSUPPORTED_FUNCTION(WaveGetLaneCount) #endif +#if defined(PLATFORM_SUPPORTS_WAVE_INTRINSICS) +// Helper macro to compute lane swizzle offset starting from andMask, orMask and xorMask. +// IMPORTANT, to guarantee compatibility with all platforms, the masks need to be constant literals (constants at compile time) +#define LANE_SWIZZLE_OFFSET(andMask, orMask, xorMask) (andMask | (orMask << 5) | (xorMask << 10)) +#endif + #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonDeprecated.hlsl" #if !defined(SHADER_API_GLES) @@ -294,8 +306,13 @@ void ToggleBit(inout uint data, uint offset) TEMPLATE_3_REAL(Avg3, a, b, c, return (a + b + c) * 0.33333333) +// Important! Quad functions only valid in pixel shaders! + float2 GetQuadOffset(int2 screenPos) + { + return float2(float(screenPos.x & 1) * 2.0 - 1.0, float(screenPos.y & 1) * 2.0 - 1.0); + } + #ifndef INTRINSIC_QUAD_SHUFFLE - // Important! Only valid in pixel shaders! float QuadReadAcrossX(float value, int2 screenPos) { return value - (ddx_fine(value) * (float(screenPos.x & 1) * 2.0 - 1.0)); @@ -310,12 +327,42 @@ TEMPLATE_3_REAL(Avg3, a, b, c, return (a + b + c) * 0.33333333) { float dX = ddx_fine(value); float dY = ddy_fine(value); - float2 quadDir = float2(float(screenPos.x & 1) * 2.0 - 1.0, float(screenPos.y & 1) * 2.0 - 1.0); + float2 quadDir = GetQuadOffset(screenPos); float X = value - (dX * quadDir.x); return X - (ddy_fine(value) * quadDir.y); } #endif + float3 QuadReadFloat3AcrossX(float3 val, int2 positionSS) + { + return float3(QuadReadAcrossX(val.x, positionSS), QuadReadAcrossX(val.y, positionSS), QuadReadAcrossX(val.z, positionSS)); + } + + float4 QuadReadFloat4AcrossX(float4 val, int2 positionSS) + { + return float4(QuadReadAcrossX(val.x, positionSS), QuadReadAcrossX(val.y, positionSS), QuadReadAcrossX(val.z, positionSS), QuadReadAcrossX(val.w, positionSS)); + } + + float3 QuadReadFloat3AcrossY(float3 val, int2 positionSS) + { + return float3(QuadReadAcrossY(val.x, positionSS), QuadReadAcrossY(val.y, positionSS), QuadReadAcrossY(val.z, positionSS)); + } + + float4 QuadReadFloat4AcrossY(float4 val, int2 positionSS) + { + return float4(QuadReadAcrossY(val.x, positionSS), QuadReadAcrossY(val.y, positionSS), QuadReadAcrossY(val.z, positionSS), QuadReadAcrossY(val.w, positionSS)); + } + + float3 QuadReadFloat3AcrossDiagonal(float3 val, int2 positionSS) + { + return float3(QuadReadAcrossDiagonal(val.x, positionSS), QuadReadAcrossDiagonal(val.y, positionSS), QuadReadAcrossDiagonal(val.z, positionSS)); + } + + float4 QuadReadFloat4AcrossDiagonal(float4 val, int2 positionSS) + { + return float4(QuadReadAcrossDiagonal(val.x, positionSS), QuadReadAcrossDiagonal(val.y, positionSS), QuadReadAcrossDiagonal(val.z, positionSS), QuadReadAcrossDiagonal(val.w, positionSS)); + } + TEMPLATE_SWAP(Swap) // Define a Swap(a, b) function for all types #define CUBEMAPFACE_POSITIVE_X 0 @@ -649,36 +696,37 @@ TEMPLATE_3_FLT(RangeRemap, min, max, t, return saturate((t - min) / (max - min)) // Texture utilities // ---------------------------------------------------------------------------- -float ComputeTextureLOD(float2 uvdx, float2 uvdy, float2 scale) +float ComputeTextureLOD(float2 uvdx, float2 uvdy, float2 scale, float bias = 0.0) { float2 ddx_ = scale * uvdx; float2 ddy_ = scale * uvdy; - float d = max(dot(ddx_, ddx_), dot(ddy_, ddy_)); + float d = max(dot(ddx_, ddx_), dot(ddy_, ddy_)); - return max(0.5 * log2(d), 0.0); + return max(0.5 * log2(d) - bias, 0.0); } -float ComputeTextureLOD(float2 uv) +float ComputeTextureLOD(float2 uv, float bias = 0.0) { float2 ddx_ = ddx(uv); float2 ddy_ = ddy(uv); - return ComputeTextureLOD(ddx_, ddy_, 1.0); + return ComputeTextureLOD(ddx_, ddy_, 1.0, bias); } // x contains width, w contains height -float ComputeTextureLOD(float2 uv, float2 texelSize) +float ComputeTextureLOD(float2 uv, float2 texelSize, float bias = 0.0) { uv *= texelSize; - return ComputeTextureLOD(uv); + return ComputeTextureLOD(uv, bias); } // LOD clamp is optional and happens outside the function. -float ComputeTextureLOD(float3 duvw_dx, float3 duvw_dy, float3 duvw_dz, float scale) +float ComputeTextureLOD(float3 duvw_dx, float3 duvw_dy, float3 duvw_dz, float scale, float bias = 0.0) { float d = Max3(dot(duvw_dx, duvw_dx), dot(duvw_dy, duvw_dy), dot(duvw_dz, duvw_dz)); - return 0.5 * log2(d * (scale * scale)); + + return max(0.5f * log2(d * (scale * scale)) - bias, 0.0); } @@ -709,6 +757,48 @@ uint GetMipCount(Texture2D tex) // Texture format sampling // ---------------------------------------------------------------------------- +// DXC no longer supports DX9-style HLSL syntax for sampler2D, tex2D and the like. +// These are emulated for backwards compatibilit using our own small structs and functions which manually combine samplers and textures. +#if defined(UNITY_COMPILER_DXC) && !defined(DXC_SAMPLER_COMPATIBILITY) +#define DXC_SAMPLER_COMPATIBILITY 1 +struct sampler1D { Texture1D t; SamplerState s; }; +struct sampler2D { Texture2D t; SamplerState s; }; +struct sampler3D { Texture3D t; SamplerState s; }; +struct samplerCUBE { TextureCube t; SamplerState s; }; + +float4 tex1D(sampler1D x, float v) { return x.t.Sample(x.s, v); } +float4 tex2D(sampler2D x, float2 v) { return x.t.Sample(x.s, v); } +float4 tex3D(sampler3D x, float3 v) { return x.t.Sample(x.s, v); } +float4 texCUBE(samplerCUBE x, float3 v) { return x.t.Sample(x.s, v); } + +float4 tex1Dbias(sampler1D x, in float4 t) { return x.t.SampleBias(x.s, t.x, t.w); } +float4 tex2Dbias(sampler2D x, in float4 t) { return x.t.SampleBias(x.s, t.xy, t.w); } +float4 tex3Dbias(sampler3D x, in float4 t) { return x.t.SampleBias(x.s, t.xyz, t.w); } +float4 texCUBEbias(samplerCUBE x, in float4 t) { return x.t.SampleBias(x.s, t.xyz, t.w); } + +float4 tex1Dlod(sampler1D x, in float4 t) { return x.t.SampleLevel(x.s, t.x, t.w); } +float4 tex2Dlod(sampler2D x, in float4 t) { return x.t.SampleLevel(x.s, t.xy, t.w); } +float4 tex3Dlod(sampler3D x, in float4 t) { return x.t.SampleLevel(x.s, t.xyz, t.w); } +float4 texCUBElod(samplerCUBE x, in float4 t) { return x.t.SampleLevel(x.s, t.xyz, t.w); } + +float4 tex1Dgrad(sampler1D x, float t, float dx, float dy) { return x.t.SampleGrad(x.s, t, dx, dy); } +float4 tex2Dgrad(sampler2D x, float2 t, float2 dx, float2 dy) { return x.t.SampleGrad(x.s, t, dx, dy); } +float4 tex3Dgrad(sampler3D x, float3 t, float3 dx, float3 dy) { return x.t.SampleGrad(x.s, t, dx, dy); } +float4 texCUBEgrad(samplerCUBE x, float3 t, float3 dx, float3 dy) { return x.t.SampleGrad(x.s, t, dx, dy); } + +float4 tex1D(sampler1D x, float t, float dx, float dy) { return x.t.SampleGrad(x.s, t, dx, dy); } +float4 tex2D(sampler2D x, float2 t, float2 dx, float2 dy) { return x.t.SampleGrad(x.s, t, dx, dy); } +float4 tex3D(sampler3D x, float3 t, float3 dx, float3 dy) { return x.t.SampleGrad(x.s, t, dx, dy); } +float4 texCUBE(samplerCUBE x, float3 t, float3 dx, float3 dy) { return x.t.SampleGrad(x.s, t, dx, dy); } + +float4 tex1Dproj(sampler1D s, in float2 t) { return tex1D(s, t.x / t.y); } +float4 tex1Dproj(sampler1D s, in float4 t) { return tex1D(s, t.x / t.w); } +float4 tex2Dproj(sampler2D s, in float3 t) { return tex2D(s, t.xy / t.z); } +float4 tex2Dproj(sampler2D s, in float4 t) { return tex2D(s, t.xy / t.w); } +float4 tex3Dproj(sampler3D s, in float4 t) { return tex3D(s, t.xyz / t.w); } +float4 texCUBEproj(samplerCUBE s, in float4 t) { return texCUBE(s, t.xyz / t.w); } +#endif + float2 DirectionToLatLongCoordinate(float3 unDir) { float3 dir = normalize(unDir); @@ -933,6 +1023,12 @@ float3 ComputeWorldSpacePosition(float2 positionNDC, float deviceDepth, float4x4 return hpositionWS.xyz / hpositionWS.w; } +float3 ComputeWorldSpacePosition(float4 positionCS, float4x4 invViewProjMatrix) +{ + float4 hpositionWS = mul(invViewProjMatrix, positionCS); + return hpositionWS.xyz / hpositionWS.w; +} + // ---------------------------------------------------------------------------- // PositionInputs // ---------------------------------------------------------------------------- @@ -974,6 +1070,16 @@ PositionInputs GetPositionInput(float2 positionSS, float2 invScreenSize) return GetPositionInput(positionSS, invScreenSize, uint2(0, 0)); } +// For Raytracing only +// This function does not initialize deviceDepth and linearDepth +PositionInputs GetPositionInput(float2 positionSS, float2 invScreenSize, float3 positionWS) +{ + PositionInputs posInput = GetPositionInput(positionSS, invScreenSize, uint2(0, 0)); + posInput.positionWS = positionWS; + + return posInput; +} + // From forward // deviceDepth and linearDepth come directly from .zw of SV_Position PositionInputs GetPositionInput(float2 positionSS, float2 invScreenSize, float deviceDepth, float linearDepth, float3 positionWS, uint2 tileCoord) @@ -1069,10 +1175,17 @@ bool HasFlag(uint bitfield, uint flag) // Normalize that account for vectors with zero length real3 SafeNormalize(float3 inVec) { - float dp3 = max(FLT_MIN, dot(inVec, inVec)); + real dp3 = max(FLT_MIN, dot(inVec, inVec)); return inVec * rsqrt(dp3); } +// Checks if a vector is normalized +bool IsNormalized(float3 inVec) +{ + real l = length(inVec); + return length(l) < 1.0001 && length(l) > 0.9999; +} + // Division which returns 1 for (inf/inf) and (0/0). // If any of the input parameters are NaNs, the result is a NaN. real SafeDiv(real numer, real denom) @@ -1165,11 +1278,22 @@ void LODDitheringTransition(uint2 fadeMaskSeed, float ditherFactor) // while on other APIs is in the red channel. Note that on some platform, always using the green channel might work, but is not guaranteed. uint GetStencilValue(uint2 stencilBufferVal) { -#if defined(SHADER_API_D3D11) || defined(SHADER_API_XBOXONE) +#if defined(SHADER_API_D3D11) || defined(SHADER_API_XBOXONE) return stencilBufferVal.y; #else return stencilBufferVal.x; #endif -} +} + +// Sharpens the alpha of a texture to the width of a single pixel +// Used for alpha to coverage +// source: https://medium.com/@bgolus/anti-aliased-alpha-test-the-esoteric-alpha-to-coverage-8b177335ae4f +float SharpenAlpha(float alpha, float alphaClipTreshold) +{ + return saturate((alpha - alphaClipTreshold) / max(fwidth(alpha), 0.0001) + 0.5); +} + +// These clamping function to max of floating point 16 bit are use to prevent INF in code in case of extreme value +TEMPLATE_1_REAL(ClampToFloat16Max, value, return min(value, HALF_MAX)) #endif // UNITY_COMMON_INCLUDED diff --git a/ShaderLibrary/CommonLighting.hlsl b/ShaderLibrary/CommonLighting.hlsl index 1827255..583962b 100644 --- a/ShaderLibrary/CommonLighting.hlsl +++ b/ShaderLibrary/CommonLighting.hlsl @@ -1,9 +1,6 @@ #ifndef UNITY_COMMON_LIGHTING_INCLUDED #define UNITY_COMMON_LIGHTING_INCLUDED -// These clamping function to max of floating point 16 bit are use to prevent INF in code in case of extreme value -TEMPLATE_1_REAL(ClampToFloat16Max, value, return min(value, HALF_MAX)) - // Ligthing convention // Light direction is oriented backward (-Z). i.e in shader code, light direction is -lightData.forward diff --git a/ShaderLibrary/CommonMaterial.hlsl b/ShaderLibrary/CommonMaterial.hlsl index 5866b68..8d79688 100644 --- a/ShaderLibrary/CommonMaterial.hlsl +++ b/ShaderLibrary/CommonMaterial.hlsl @@ -329,5 +329,4 @@ real3 LerpWhiteTo(real3 b, real t) real oneMinusT = 1.0 - t; return real3(oneMinusT, oneMinusT, oneMinusT) + b * t; } - #endif // UNITY_COMMON_MATERIAL_INCLUDED diff --git a/ShaderLibrary/EntityLighting.hlsl b/ShaderLibrary/EntityLighting.hlsl index f9ed3d4..b921870 100644 --- a/ShaderLibrary/EntityLighting.hlsl +++ b/ShaderLibrary/EntityLighting.hlsl @@ -78,6 +78,7 @@ half3 SampleSH9(half4 SHCoefficients[7], half3 N) return res; } #endif + float3 SampleSH9(float4 SHCoefficients[7], float3 N) { float4 shAr = SHCoefficients[0]; @@ -105,8 +106,10 @@ float3 SampleSH9(float4 SHCoefficients[7], float3 N) // TODO: the packing here is inefficient as we will fetch values far away from each other and they may not fit into the cache - Suggest we pack RGB continuously // TODO: The calcul of texcoord could be perform with a single matrix multicplication calcualted on C++ side that will fold probeVolumeMin and probeVolumeSizeInv into it and handle the identity case, no reasons to do it in C++ (ask Ionut about it) // It should also handle the camera relative path (if the render pipeline use it) -float3 SampleProbeVolumeSH4(TEXTURE3D_PARAM(SHVolumeTexture, SHVolumeSampler), float3 positionWS, float3 normalWS, float4x4 WorldToTexture, - float transformToLocal, float texelSizeX, float3 probeVolumeMin, float3 probeVolumeSizeInv) +// bakeDiffuseLighting and backBakeDiffuseLighting must be initialize outside the function +void SampleProbeVolumeSH4(TEXTURE3D_PARAM(SHVolumeTexture, SHVolumeSampler), float3 positionWS, float3 normalWS, float3 backNormalWS, float4x4 WorldToTexture, + float transformToLocal, float texelSizeX, float3 probeVolumeMin, float3 probeVolumeSizeInv, + inout float3 bakeDiffuseLighting, inout float3 backBakeDiffuseLighting) { float3 position = (transformToLocal == 1.0) ? mul(WorldToTexture, float4(positionWS, 1.0)).xyz : positionWS; float3 texCoord = (position - probeVolumeMin) * probeVolumeSizeInv.xyz; @@ -123,14 +126,30 @@ float3 SampleProbeVolumeSH4(TEXTURE3D_PARAM(SHVolumeTexture, SHVolumeSampler), f texCoord.x += 0.25; float4 shAb = SAMPLE_TEXTURE3D_LOD(SHVolumeTexture, SHVolumeSampler, texCoord, 0); - return SHEvalLinearL0L1(normalWS, shAr, shAg, shAb); + bakeDiffuseLighting += SHEvalLinearL0L1(normalWS, shAr, shAg, shAb); + backBakeDiffuseLighting += SHEvalLinearL0L1(backNormalWS, shAr, shAg, shAb); +} + +// Just a shortcut that call function above +float3 SampleProbeVolumeSH4(TEXTURE3D_PARAM(SHVolumeTexture, SHVolumeSampler), float3 positionWS, float3 normalWS, float4x4 WorldToTexture, + float transformToLocal, float texelSizeX, float3 probeVolumeMin, float3 probeVolumeSizeInv) +{ + float3 backNormalWSUnused = 0.0; + float3 bakeDiffuseLighting = 0.0; + float3 backBakeDiffuseLightingUnused = 0.0; + SampleProbeVolumeSH4(TEXTURE3D_ARGS(SHVolumeTexture, SHVolumeSampler), positionWS, normalWS, backNormalWSUnused, WorldToTexture, + transformToLocal, texelSizeX, probeVolumeMin, probeVolumeSizeInv, + bakeDiffuseLighting, backBakeDiffuseLightingUnused); + return bakeDiffuseLighting; } // The SphericalHarmonicsL2 coefficients are packed into 7 coefficients per color channel instead of 9. // The packing from 9 to 7 is done from engine code and will use the alpha component of the pixel to store an additional SH coefficient. // The 3D atlas texture will contain 7 SH coefficient parts. -float3 SampleProbeVolumeSH9(TEXTURE3D_PARAM(SHVolumeTexture, SHVolumeSampler), float3 positionWS, float3 normalWS, float4x4 WorldToTexture, - float transformToLocal, float texelSizeX, float3 probeVolumeMin, float3 probeVolumeSizeInv) +// bakeDiffuseLighting and backBakeDiffuseLighting must be initialize outside the function +void SampleProbeVolumeSH9(TEXTURE3D_PARAM(SHVolumeTexture, SHVolumeSampler), float3 positionWS, float3 normalWS, float3 backNormalWS, float4x4 WorldToTexture, + float transformToLocal, float texelSizeX, float3 probeVolumeMin, float3 probeVolumeSizeInv, + inout float3 bakeDiffuseLighting, inout float3 backBakeDiffuseLighting) { float3 position = (transformToLocal == 1.0f) ? mul(WorldToTexture, float4(positionWS, 1.0)).xyz : positionWS; float3 texCoord = (position - probeVolumeMin) * probeVolumeSizeInv; @@ -152,8 +171,23 @@ float3 SampleProbeVolumeSH9(TEXTURE3D_PARAM(SHVolumeTexture, SHVolumeSampler), f SHCoefficients[i] = SAMPLE_TEXTURE3D_LOD(SHVolumeTexture, SHVolumeSampler, texCoord, 0); } - return SampleSH9(SHCoefficients, normalize(normalWS)); + bakeDiffuseLighting += SampleSH9(SHCoefficients, normalize(normalWS)); + backBakeDiffuseLighting += SampleSH9(SHCoefficients, normalize(backNormalWS)); +} + +// Just a shortcut that call function above +float3 SampleProbeVolumeSH9(TEXTURE3D_PARAM(SHVolumeTexture, SHVolumeSampler), float3 positionWS, float3 normalWS, float4x4 WorldToTexture, + float transformToLocal, float texelSizeX, float3 probeVolumeMin, float3 probeVolumeSizeInv) +{ + float3 backNormalWSUnused = 0.0; + float3 bakeDiffuseLighting = 0.0; + float3 backBakeDiffuseLightingUnused = 0.0; + SampleProbeVolumeSH9(TEXTURE3D_ARGS(SHVolumeTexture, SHVolumeSampler), positionWS, normalWS, backNormalWSUnused, WorldToTexture, + transformToLocal, texelSizeX, probeVolumeMin, probeVolumeSizeInv, + bakeDiffuseLighting, backBakeDiffuseLightingUnused); + return bakeDiffuseLighting; } + #endif float4 SampleProbeOcclusion(TEXTURE3D_PARAM(SHVolumeTexture, SHVolumeSampler), float3 positionWS, float4x4 WorldToTexture, @@ -234,7 +268,21 @@ real3 DecodeHDREnvironment(real4 encodedIrradiance, real4 decodeInstructions) return (decodeInstructions.x * PositivePow(alpha, decodeInstructions.y)) * encodedIrradiance.rgb; } -real3 SampleSingleLightmap(TEXTURE2D_PARAM(lightmapTex, lightmapSampler), float2 uv, float4 transform, bool encodedLightmap, real4 decodeInstructions) +#if defined(UNITY_DOTS_INSTANCING_ENABLED) +#define TEXTURE2D_LIGHTMAP_PARAM TEXTURE2D_ARRAY_PARAM +#define TEXTURE2D_LIGHTMAP_ARGS TEXTURE2D_ARRAY_ARGS +#define SAMPLE_TEXTURE2D_LIGHTMAP SAMPLE_TEXTURE2D_ARRAY +#define LIGHTMAP_EXTRA_ARGS float2 uv, float slice +#define LIGHTMAP_EXTRA_ARGS_USE uv, slice +#else +#define TEXTURE2D_LIGHTMAP_PARAM TEXTURE2D_PARAM +#define TEXTURE2D_LIGHTMAP_ARGS TEXTURE2D_ARGS +#define SAMPLE_TEXTURE2D_LIGHTMAP SAMPLE_TEXTURE2D +#define LIGHTMAP_EXTRA_ARGS float2 uv +#define LIGHTMAP_EXTRA_ARGS_USE uv +#endif + +real3 SampleSingleLightmap(TEXTURE2D_LIGHTMAP_PARAM(lightmapTex, lightmapSampler), LIGHTMAP_EXTRA_ARGS, float4 transform, bool encodedLightmap, real4 decodeInstructions) { // transform is scale and bias uv = uv * transform.xy + transform.zw; @@ -242,19 +290,20 @@ real3 SampleSingleLightmap(TEXTURE2D_PARAM(lightmapTex, lightmapSampler), float2 // Remark: baked lightmap is RGBM for now, dynamic lightmap is RGB9E5 if (encodedLightmap) { - real4 encodedIlluminance = SAMPLE_TEXTURE2D(lightmapTex, lightmapSampler, uv).rgba; + real4 encodedIlluminance = SAMPLE_TEXTURE2D_LIGHTMAP(lightmapTex, lightmapSampler, LIGHTMAP_EXTRA_ARGS_USE).rgba; illuminance = DecodeLightmap(encodedIlluminance, decodeInstructions); } else { - illuminance = SAMPLE_TEXTURE2D(lightmapTex, lightmapSampler, uv).rgb; + illuminance = SAMPLE_TEXTURE2D_LIGHTMAP(lightmapTex, lightmapSampler, LIGHTMAP_EXTRA_ARGS_USE).rgb; } return illuminance; } -real3 SampleDirectionalLightmap(TEXTURE2D_PARAM(lightmapTex, lightmapSampler), TEXTURE2D_PARAM(lightmapDirTex, lightmapDirSampler), float2 uv, float4 transform, float3 normalWS, bool encodedLightmap, real4 decodeInstructions) +void SampleDirectionalLightmap(TEXTURE2D_LIGHTMAP_PARAM(lightmapTex, lightmapSampler), TEXTURE2D_LIGHTMAP_PARAM(lightmapDirTex, lightmapDirSampler), LIGHTMAP_EXTRA_ARGS, float4 transform, + float3 normalWS, float3 backNormalWS, bool encodedLightmap, real4 decodeInstructions, inout real3 bakeDiffuseLighting, inout real3 backBakeDiffuseLighting) { - // In directional mode Enlighten bakes dominant light direction + // In directional mode Enlighten bakes dominant light direction // in a way, that using it for half Lambert and then dividing by a "rebalancing coefficient" // gives a result close to plain diffuse response lightmaps, but normalmapped. @@ -264,20 +313,38 @@ real3 SampleDirectionalLightmap(TEXTURE2D_PARAM(lightmapTex, lightmapSampler), T // transform is scale and bias uv = uv * transform.xy + transform.zw; - real4 direction = SAMPLE_TEXTURE2D(lightmapDirTex, lightmapDirSampler, uv); + real4 direction = SAMPLE_TEXTURE2D_LIGHTMAP(lightmapDirTex, lightmapDirSampler, LIGHTMAP_EXTRA_ARGS_USE); // Remark: baked lightmap is RGBM for now, dynamic lightmap is RGB9E5 real3 illuminance = real3(0.0, 0.0, 0.0); if (encodedLightmap) { - real4 encodedIlluminance = SAMPLE_TEXTURE2D(lightmapTex, lightmapSampler, uv).rgba; + real4 encodedIlluminance = SAMPLE_TEXTURE2D_LIGHTMAP(lightmapTex, lightmapSampler, LIGHTMAP_EXTRA_ARGS_USE).rgba; illuminance = DecodeLightmap(encodedIlluminance, decodeInstructions); } else { - illuminance = SAMPLE_TEXTURE2D(lightmapTex, lightmapSampler, uv).rgb; + illuminance = SAMPLE_TEXTURE2D_LIGHTMAP(lightmapTex, lightmapSampler, LIGHTMAP_EXTRA_ARGS_USE).rgb; } + real halfLambert = dot(normalWS, direction.xyz - 0.5) + 0.5; - return illuminance * halfLambert / max(1e-4, direction.w); + bakeDiffuseLighting += illuminance * halfLambert / max(1e-4, direction.w); + + real backHalfLambert = dot(backNormalWS, direction.xyz - 0.5) + 0.5; + backBakeDiffuseLighting += illuminance * backHalfLambert / max(1e-4, direction.w); } +// Just a shortcut that call function above +real3 SampleDirectionalLightmap(TEXTURE2D_LIGHTMAP_PARAM(lightmapTex, lightmapSampler), TEXTURE2D_LIGHTMAP_PARAM(lightmapDirTex, lightmapDirSampler), LIGHTMAP_EXTRA_ARGS, float4 transform, + float3 normalWS, bool encodedLightmap, real4 decodeInstructions) +{ + float3 backNormalWSUnused = 0.0; + real3 bakeDiffuseLighting = 0.0; + real3 backBakeDiffuseLightingUnused = 0.0; + SampleDirectionalLightmap(TEXTURE2D_LIGHTMAP_ARGS(lightmapTex, lightmapSampler), TEXTURE2D_LIGHTMAP_ARGS(lightmapDirTex, lightmapDirSampler), LIGHTMAP_EXTRA_ARGS_USE, transform, + normalWS, backNormalWSUnused, encodedLightmap, decodeInstructions, bakeDiffuseLighting, backBakeDiffuseLightingUnused); + + return bakeDiffuseLighting; +} + + #endif // UNITY_ENTITY_LIGHTING_INCLUDED diff --git a/ShaderLibrary/GeometricTools.hlsl b/ShaderLibrary/GeometricTools.hlsl index db8500b..999aedd 100644 --- a/ShaderLibrary/GeometricTools.hlsl +++ b/ShaderLibrary/GeometricTools.hlsl @@ -132,6 +132,23 @@ float3 IntersectRayPlane(float3 rayOrigin, float3 rayDirection, float3 planeOrig return rayOrigin + rayDirection * dist; } +// Same as above but return intersection distance and true / false if the ray hit/miss +bool IntersectRayPlane(float3 rayOrigin, float3 rayDirection, float3 planePosition, float3 planeNormal, out float t) +{ + bool res = false; + t = -1.0; + + float denom = dot(planeNormal, rayDirection); + if (abs(denom) > 1e-5) + { + float3 d = planePosition - rayOrigin; + t = dot(d, planeNormal) / denom; + res = (t >= 0); + } + + return res; +} + // Can support cones with an elliptic base: pre-scale 'coneAxisX' and 'coneAxisY' by (h/r_x) and (h/r_y). // Returns parametric distances 'tEntr' and 'tExit' along the ray, // subject to constraints 'tMin' and 'tMax'. diff --git a/ShaderLibrary/GraniteShaderLibBase.hlsl b/ShaderLibrary/GraniteShaderLibBase.hlsl new file mode 100644 index 0000000..038c761 --- /dev/null +++ b/ShaderLibrary/GraniteShaderLibBase.hlsl @@ -0,0 +1,864 @@ +#ifndef GRA_HLSL_3 +#define GRA_HLSL_3 0 +#endif + +#ifndef GRA_HLSL_4 +#define GRA_HLSL_4 0 +#endif + +#ifndef GRA_HLSL_5 +#define GRA_HLSL_5 0 +#endif + +#ifndef GRA_GLSL_120 +#define GRA_GLSL_120 0 +#endif + +#ifndef GRA_GLSL_130 +#define GRA_GLSL_130 0 +#endif + +#ifndef GRA_GLSL_330 +#define GRA_GLSL_330 0 +#endif + +#ifndef GRA_VERTEX_SHADER +#define GRA_VERTEX_SHADER 0 +#endif + +#ifndef GRA_PIXEL_SHADER +#define GRA_PIXEL_SHADER 0 +#endif + +#ifndef GRA_HQ_CUBEMAPPING +#define GRA_HQ_CUBEMAPPING 0 +#endif + +#ifndef GRA_DEBUG_TILES +#define GRA_DEBUG_TILES 0 +#endif + +#ifndef GRA_BGRA +#define GRA_BGRA 0 +#endif + +#ifndef GRA_ROW_MAJOR +#define GRA_ROW_MAJOR 1 +#endif + +#ifndef GRA_DEBUG +#define GRA_DEBUG 1 +#endif + +#ifndef GRA_64BIT_RESOLVER +#define GRA_64BIT_RESOLVER 0 +#endif + +#ifndef GRA_RWTEXTURE2D_SCALE +#define GRA_RWTEXTURE2D_SCALE 16 +#endif + +#ifndef GRA_DISABLE_TEX_LOAD +#define GRA_DISABLE_TEX_LOAD 0 +#endif + +#ifndef GRA_PACK_RESOLVE_OUTPUT +#define GRA_PACK_RESOLVE_OUTPUT 1 +#endif + +// Temp workaround for some platforms's lack of unorm. +#ifdef GRA_NO_UNORM + #define GRA_UNORM +#else + #define GRA_UNORM unorm +#endif + +#ifndef GRA_TEXTURE_ARRAY_SUPPORT + #if (GRA_HLSL_5 == 1) || (GRA_HLSL_4 == 1) || (GRA_GLSL_330 == 1) + #define GRA_TEXTURE_ARRAY_SUPPORT 1 + #else + #define GRA_TEXTURE_ARRAY_SUPPORT 0 + #endif +#endif + +#define GRA_HLSL_FAMILY ((GRA_HLSL_3 == 1) || (GRA_HLSL_5 == 1) || (GRA_HLSL_4 == 1)) +#define GRA_GLSL_FAMILY ((GRA_GLSL_120 == 1) || (GRA_GLSL_130 == 1) || (GRA_GLSL_330 == 1)) + +#if GRA_HLSL_FAMILY + #define gra_Float2 float2 + #define gra_Float3 float3 + #define gra_Float4 float4 + #define gra_Int3 int3 + #define gra_Float4x4 float4x4 + #define gra_Unroll [unroll] + #define gra_Branch [branch] +#elif GRA_GLSL_FAMILY + #if (GRA_VERTEX_SHADER == 0) && (GRA_PIXEL_SHADER ==0) + #error GLSL requires knowledge of the shader stage! Neither GRA_VERTEX_SHADER or GRA_PIXEL_SHADER are defined! + #else + #define gra_Float2 vec2 + #define gra_Float3 vec3 + #define gra_Float4 vec4 + #define gra_Int3 ivec3 + #define gra_Float4x4 mat4 + #define gra_Unroll + #define gra_Branch + #if (GRA_VERTEX_SHADER == 1) + #define ddx + #define ddy + #elif (GRA_PIXEL_SHADER == 1) + #define ddx dFdx + #define ddy dFdy + #endif + #define frac fract + #define lerp mix + /** This is not correct (http://stackoverflow.com/questions/7610631/glsl-mod-vs-hlsl-fmod) but it is for our case */ + #define fmod mod + #endif +#else + #error unknown shader architecture +#endif + +#if (GRA_DISABLE_TEX_LOAD!=1) + #if (GRA_HLSL_5 == 1) || (GRA_HLSL_4 == 1) || (GRA_GLSL_130 == 1) || (GRA_GLSL_330 == 1) + #define GRA_LOAD_INSTR 1 + #else + #define GRA_LOAD_INSTR 0 + #endif +#else + #define GRA_LOAD_INSTR 0 +#endif + +/** + a cross API texture handle +*/ +#if (GRA_HLSL_5 == 1) || (GRA_HLSL_4 == 1) + struct GraniteTranslationTexture + { + SamplerState Sampler; + Texture2D Texture; + }; + struct GraniteCacheTexture + { + SamplerState Sampler; + + #if GRA_TEXTURE_ARRAY_SUPPORT + Texture2DArray TextureArray; + #else + Texture2D Texture; + #endif + }; +#elif (GRA_HLSL_3 == 1) || (GRA_GLSL_120 == 1) || (GRA_GLSL_130 == 1) || (GRA_GLSL_330 == 1) + #define GraniteTranslationTexture sampler2D + + #if GRA_TEXTURE_ARRAY_SUPPORT + #define GraniteCacheTexture sampler2DArray + #else + #define GraniteCacheTexture sampler2D + #endif + +#else + #error unknow shader archtecture +#endif + +/** + Struct defining the constant buffer for each streaming texture. + Use IStreamingTexture::GetConstantBuffer to fill this struct. +*/ +struct GraniteStreamingTextureConstantBuffer +{ + #define _grStreamingTextureCBSize 2 + gra_Float4 data[_grStreamingTextureCBSize]; +}; + +/** + Struct defining the constant buffer for each cube streaming texture. + Use multiple calls to IStreamingTexture::GetConstantBuffer this struct (one call for each face). + */ +struct GraniteStreamingTextureCubeConstantBuffer +{ + #define _grStreamingTextureCubeCBSize 6 + GraniteStreamingTextureConstantBuffer data[_grStreamingTextureCubeCBSize]; +}; + +/** + Struct defining the constant buffer for each tileset. + Use ITileSet::GetConstantBuffer to fill this struct. +*/ +struct GraniteTilesetConstantBuffer +{ + #define _grTilesetCBSize 2 + gra_Float4x4 data[_grTilesetCBSize]; +}; + +/** + Utility struct used by the shaderlib to wrap up all required constant buffers needed to perform a VT lookup/sample. + */ +struct GraniteConstantBuffers +{ + GraniteTilesetConstantBuffer tilesetBuffer; + GraniteStreamingTextureConstantBuffer streamingTextureBuffer; +}; + +/** + Utility struct used by the shaderlib to wrap up all required constant buffers needed to perform a Cube VT lookup/sample. + */ +struct GraniteCubeConstantBuffers +{ + GraniteTilesetConstantBuffer tilesetBuffer; + GraniteStreamingTextureCubeConstantBuffer streamingTextureCubeBuffer; +}; + +/** + The Granite lookup data for the different sampling functions. +*/ + +// Granite lookup data for automatic mip level selecting sampling +struct GraniteLookupData +{ + gra_Float4 translationTableData; + gra_Float2 textureCoordinates; + gra_Float2 dX; + gra_Float2 dY; +}; + +// Granite lookup data for explicit level-of-detail sampling +struct GraniteLODLookupData +{ + gra_Float4 translationTableData; + gra_Float2 textureCoordinates; + float cacheLevel; +}; +//@IGNORE_END + +// public interface + +/* + END OF PUBLIC INTERFACE + Everything below this point should be treated as private to GraniteShaderLib.h +*/ + +//@INSERT_DEFINES +#define gra_TilesetBuffer grCB.tilesetBuffer +#define gra_TilesetBufferInternal tsCB.data[0] +#define gra_TilesetCacheBuffer tsCB.data[1] + +#define gra_StreamingTextureCB grCB.streamingTextureBuffer +#define gra_StreamingTextureCubeCB grCB.streamingTextureCubeBuffer + +#define gra_Transform grCB.streamingTextureBuffer.data[0] +#define gra_CubeTransform grCB.streamingTextureCubeBuffer.data + +#define gra_StreamingTextureTransform grSTCB.data[0] +#define gra_StreamingTextureInfo grSTCB.data[1] + +#define gra_NumLevels gra_StreamingTextureInfo.x +#define gra_AssetWidthRcp gra_StreamingTextureInfo.y +#define gra_AssetHeightRcp gra_StreamingTextureInfo.z + +#if GRA_ROW_MAJOR == 1 + + #define gra_TranslationTableBias gra_TilesetBufferInternal[0][0] + #define gra_MaxAnisotropyLog2 gra_TilesetBufferInternal[1][0] + #define gra_CalcMiplevelDeltaScale gra_Float2(gra_TilesetBufferInternal[2][0], gra_TilesetBufferInternal[3][0]) + #define gra_CalcMiplevelDeltaScaleX gra_TilesetBufferInternal[2][0] + #define gra_CalcMiplevelDeltaScaleY gra_TilesetBufferInternal[3][0] + #define gra_LodBiasPow2 gra_TilesetBufferInternal[0][1] + #define gra_TrilinearOffset gra_TilesetBufferInternal[0][2] + #define gra_TileContentInTiles gra_Float2(gra_TilesetBufferInternal[0][2], gra_TilesetBufferInternal[1][2]) + #define gra_Level0NumTilesX gra_TilesetBufferInternal[0][3] + #define gra_NumTilesYScale gra_TilesetBufferInternal[1][3] + #define gra_TextureMagic gra_TilesetBufferInternal[2][3] + #define gra_TextureId gra_TilesetBufferInternal[3][3] + + #define gra_RcpCacheInTiles(l) gra_Float2(gra_TilesetCacheBuffer[0][l], gra_TilesetCacheBuffer[1][l]) + #define gra_BorderPixelsRcpCache(l) gra_Float2(gra_TilesetCacheBuffer[2][l], gra_TilesetCacheBuffer[3][l]) + +#else + + #define gra_TranslationTableBias gra_TilesetBufferInternal[0][0] + #define gra_MaxAnisotropyLog2 gra_TilesetBufferInternal[0][1] + #define gra_CalcMiplevelDeltaScale gra_Float2(gra_TilesetBufferInternal[0][2], gra_TilesetBufferInternal[0][3]) + #define gra_CalcMiplevelDeltaScaleX gra_TilesetBufferInternal[0][2] + #define gra_CalcMiplevelDeltaScaleY gra_TilesetBufferInternal[0][3] + #define gra_LodBiasPow2 gra_TilesetBufferInternal[1][0] + #define gra_TrilinearOffset gra_TilesetBufferInternal[2][0] + #define gra_TileContentInTiles gra_Float2(gra_TilesetBufferInternal[2][0], gra_TilesetBufferInternal[2][1]) + #define gra_Level0NumTilesX gra_TilesetBufferInternal[3][0] + #define gra_NumTilesYScale gra_TilesetBufferInternal[3][1] + #define gra_TextureMagic gra_TilesetBufferInternal[3][2] + #define gra_TextureId gra_TilesetBufferInternal[3][3] + + #define gra_RcpCacheInTiles(l) gra_Float2(gra_TilesetCacheBuffer[l][0], gra_TilesetCacheBuffer[l][1]) + #define gra_BorderPixelsRcpCache(l) gra_Float2(gra_TilesetCacheBuffer[l][2], gra_TilesetCacheBuffer[l][3]) + +#endif + +#if (GRA_GLSL_120==1) + // Extension needed for texture2DLod + //extension GL_ARB_shader_texture_lod : enable + // Extensions needed fot texture2DGrad + //extension GL_EXT_gpu_shader4 : enable + // Extensions needed for bit manipulation + //extension GL_ARB_shader_bit_encoding : enable +#endif + + +#if (GRA_TEXTURE_ARRAY_SUPPORT==1) + gra_Float4 GranitePrivate_SampleArray(in GraniteCacheTexture tex, in gra_Float3 texCoord) + { + #if (GRA_HLSL_5 == 1) || (GRA_HLSL_4 == 1) + return tex.TextureArray.Sample(tex.Sampler, texCoord); + #elif (GRA_GLSL_330 == 1) + return texture(tex, texCoord); + #else + #error using unsupported function + #endif + } + + gra_Float4 GranitePrivate_SampleGradArray(in GraniteCacheTexture tex, in gra_Float3 texCoord, in gra_Float2 dX, in gra_Float2 dY) + { + #if (GRA_HLSL_5 == 1) || (GRA_HLSL_4 == 1) + return tex.TextureArray.SampleGrad(tex.Sampler,texCoord,dX,dY); + #elif (GRA_GLSL_330 == 1) + return textureGrad(tex, texCoord, dX, dY); + #else + #error using unsupported function + #endif + } + + gra_Float4 GranitePrivate_SampleLevelArray(in GraniteCacheTexture tex, in gra_Float3 texCoord, in float level) + { + #if (GRA_HLSL_5 == 1) || (GRA_HLSL_4 == 1) + return tex.TextureArray.SampleLevel(tex.Sampler, texCoord, level); + #elif (GRA_GLSL_330 == 1) + return textureLod(tex, texCoord, level); + #else + #error using unsupported function + #endif + } +#else + gra_Float4 GranitePrivate_Sample(in GraniteCacheTexture tex, in gra_Float2 texCoord) + { + #if (GRA_HLSL_5 == 1) || (GRA_HLSL_4 == 1) + return tex.Texture.Sample(tex.Sampler,texCoord); + #elif (GRA_HLSL_3 == 1) + return tex2D(tex,texCoord); + #elif (GRA_GLSL_120 == 1) || (GRA_GLSL_130 == 1) + return texture2D(tex, texCoord); + #elif (GRA_GLSL_330 == 1) + return texture(tex, texCoord); + #endif + } + + gra_Float4 GranitePrivate_SampleLevel(in GraniteCacheTexture tex, in gra_Float2 texCoord, in float level) + { + #if (GRA_HLSL_5 == 1) || (GRA_HLSL_4 == 1) + return tex.Texture.SampleLevel(tex.Sampler, texCoord, level); + #elif (GRA_HLSL_3 == 1) + return tex2Dlod(tex,gra_Float4(texCoord,0.0,level)); + #elif (GRA_GLSL_120 == 1) + return texture2DLod(tex, texCoord, level); + #elif (GRA_GLSL_130 == 1) || (GRA_GLSL_330 == 1) + return textureLod(tex, texCoord, level); + #endif + } + + gra_Float4 GranitePrivate_SampleGrad(in GraniteCacheTexture tex, in gra_Float2 texCoord, in gra_Float2 dX, in gra_Float2 dY) + { + #if (GRA_HLSL_5 == 1) || (GRA_HLSL_4 == 1) + return tex.Texture.SampleGrad(tex.Sampler,texCoord,dX,dY); + #elif (GRA_HLSL_3 == 1) + return tex2D(tex,texCoord,dX,dY); + #elif (GRA_GLSL_120 == 1) + return texture2DGrad(tex, texCoord, dX, dY); + #elif (GRA_GLSL_130 == 1) || (GRA_GLSL_330 == 1) + return textureGrad(tex, texCoord, dX, dY); + #endif + } +#endif //#if (GRA_TEXTURE_ARRAY_SUPPORT==1) + +#if (GRA_LOAD_INSTR==1) +gra_Float4 GranitePrivate_Load(in GraniteTranslationTexture tex, in gra_Int3 location) +{ +#if (GRA_HLSL_5 == 1) || (GRA_HLSL_4 == 1) + return tex.Texture.Load(location); +#elif (GRA_GLSL_130 == 1) || (GRA_GLSL_330 == 1) + return texelFetch(tex, location.xy, location.z); +#elif (GRA_HLSL_3 == 1) || (GRA_GLSL_120 == 1) + #error using unsupported function +#endif +} +#endif + +//work-around shader compiler bug +//compiler gets confused with GranitePrivate_SampleLevel taking a GraniteCacheTexture as argument when array support is disabled +//Without array support, GraniteCacheTexture and GraniteTranslationTexture are the same (but still different types!) +//compiler is confused (ERR_AMBIGUOUS_FUNCTION_CALL). Looks like somebody is over enthusiastic optimizing... +gra_Float4 GranitePrivate_SampleLevel_Translation(in GraniteTranslationTexture tex, in gra_Float2 texCoord, in float level) +{ +#if (GRA_HLSL_5 == 1) || (GRA_HLSL_4 == 1) + return tex.Texture.SampleLevel(tex.Sampler, texCoord, level); +#elif (GRA_HLSL_3 == 1) + return tex2Dlod(tex,gra_Float4(texCoord,0.0,level)); +#elif (GRA_GLSL_120 == 1) + return texture2DLod(tex, texCoord, level); +#elif (GRA_GLSL_130 == 1) || (GRA_GLSL_330 == 1) + return textureLod(tex, texCoord, level); +#endif +} + +float GranitePrivate_Saturate(in float value) +{ +#if GRA_HLSL_FAMILY + return saturate(value); +#elif GRA_GLSL_FAMILY + return clamp(value, 0.0f, 1.0f); +#endif +} + +#if (GRA_HLSL_5 == 1) || (GRA_HLSL_4 == 1) || (GRA_GLSL_330 == 1) +uint GranitePrivate_FloatAsUint(float value) +{ +#if (GRA_HLSL_5 == 1) || (GRA_HLSL_4 == 1) + return asuint(value); +#elif (GRA_GLSL_330 == 1) + return floatBitsToUint(value); +#endif +} +#endif + +float GranitePrivate_Pow2(uint exponent) +{ +#if GRA_HLSL_FAMILY + return pow(2.0, exponent); +#else + return pow(2.0, float(exponent)); +#endif +} + +gra_Float2 GranitePrivate_RepeatUV(in gra_Float2 uv, in GraniteStreamingTextureConstantBuffer grSTCB) +{ + return frac(uv); +} + +gra_Float2 GranitePrivate_UdimUV(in gra_Float2 uv, in GraniteStreamingTextureConstantBuffer grSTCB) +{ + return uv; +} + +gra_Float2 GranitePrivate_ClampUV(in gra_Float2 uv, in GraniteStreamingTextureConstantBuffer grSTCB) +{ + gra_Float2 epsilon2 = gra_Float2(gra_AssetWidthRcp, gra_AssetHeightRcp); + return clamp(uv, epsilon2, gra_Float2(1,1) - epsilon2); +} + +gra_Float2 GranitePrivate_MirrorUV(in gra_Float2 uv, in GraniteStreamingTextureConstantBuffer grSTCB) +{ + gra_Float2 t = frac(uv*0.5)*2.0; + gra_Float2 l = gra_Float2(1.0,1.0); + return l-abs(t-l); +} + +// function definitons for private functions +gra_Float4 GranitePrivate_PackTileId(in gra_Float2 tileXY, in float level, in float textureID); + +gra_Float4 Granite_DebugPackedTileId64(in gra_Float4 PackedTile) +{ +#if GRA_64BIT_RESOLVER + gra_Float4 output; + + const float scale = 1.0f / 65535.0f; + gra_Float4 temp = PackedTile / scale; + + output.x = fmod(temp.x, 256.0f); + output.y = floor(temp.x / 256.0f) + fmod(temp.y, 16.0f) * 16.0f; + output.z = floor(temp.y / 16.0f); + output.w = temp.z + temp.a * 16.0f; + + return gra_Float4 + ( + (float)output.x / 255.0f, + (float)output.y / 255.0f, + (float)output.z / 255.0f, + (float)output.w / 255.0f + ); +#else + return PackedTile; +#endif +} + +gra_Float3 Granite_UnpackNormal(in gra_Float4 PackedNormal, float scale) +{ + gra_Float2 reconstructed = gra_Float2(PackedNormal.x * PackedNormal.a, PackedNormal.y) * 2.0f - 1.0f; + reconstructed *= scale; + float z = sqrt(1.0f - GranitePrivate_Saturate(dot(reconstructed, reconstructed))); + return gra_Float3(reconstructed, z); +} + +gra_Float3 Granite_UnpackNormal(in gra_Float4 PackedNormal) +{ + return Granite_UnpackNormal(PackedNormal, 1.0); +} + +#if GRA_HLSL_FAMILY +GraniteTilesetConstantBuffer Granite_ApplyResolutionOffset(in GraniteTilesetConstantBuffer INtsCB, in float resolutionOffsetPow2) +{ + GraniteTilesetConstantBuffer tsCB = INtsCB; + gra_LodBiasPow2 *= resolutionOffsetPow2; + //resolutionOffsetPow2 *= resolutionOffsetPow2; //Square it before multiplying it in below + gra_CalcMiplevelDeltaScaleX *= resolutionOffsetPow2; + gra_CalcMiplevelDeltaScaleY *= resolutionOffsetPow2; + return tsCB; +} + +GraniteTilesetConstantBuffer Granite_SetMaxAnisotropy(in GraniteTilesetConstantBuffer INtsCB, in float maxAnisotropyLog2) +{ + GraniteTilesetConstantBuffer tsCB = INtsCB; + gra_MaxAnisotropyLog2 = min(gra_MaxAnisotropyLog2, maxAnisotropyLog2); + return tsCB; +} +#else +void Granite_ApplyResolutionOffset(inout GraniteTilesetConstantBuffer tsCB, in float resolutionOffsetPow2) +{ + gra_LodBiasPow2 *= resolutionOffsetPow2; + //resolutionOffsetPow2 *= resolutionOffsetPow2; //Square it before multiplying it in below + gra_CalcMiplevelDeltaScaleX *= resolutionOffsetPow2; + gra_CalcMiplevelDeltaScaleY *= resolutionOffsetPow2; +} + +void Granite_SetMaxAnisotropy(inout GraniteTilesetConstantBuffer tsCB, in float maxAnisotropyLog2) +{ + gra_MaxAnisotropyLog2 = min(gra_MaxAnisotropyLog2, maxAnisotropyLog2); +} +#endif + +gra_Float2 Granite_Transform(in GraniteStreamingTextureConstantBuffer grSTCB, in gra_Float2 textureCoord) +{ + return textureCoord * gra_StreamingTextureTransform.zw + gra_StreamingTextureTransform.xy; +} + +gra_Float4 Granite_MergeResolveOutputs(in gra_Float4 resolve0, in gra_Float4 resolve1, in gra_Float2 pixelLocation) +{ + gra_Float2 screenPos = frac(pixelLocation * 0.5f); + bool dither = (screenPos.x != screenPos.y); + return (dither) ? resolve0 : resolve1; +} + +gra_Float4 Granite_PackTileId(in gra_Float4 unpackedTileID) +{ + return GranitePrivate_PackTileId(unpackedTileID.xy, unpackedTileID.z, unpackedTileID.w); +} + +#if (GRA_HLSL_5 == 1) +void Granite_DitherResolveOutput(in gra_Float4 resolve, in RWTexture2D resolveTexture, in gra_Float2 screenPos, in float alpha) +{ + const uint2 pixelPos = int2(screenPos); + const uint2 pixelLocation = pixelPos % GRA_RWTEXTURE2D_SCALE; + bool dither = (pixelLocation.x == 0) && (pixelLocation.y == 0); + uint2 writePos = pixelPos / GRA_RWTEXTURE2D_SCALE; + + if ( alpha == 0 ) + { + dither = false; + } + else if (alpha != 1.0) + { + // Do a 4x4 dither patern so alternating pixels resolve to the first or the second texture + gra_Float2 pixelLocationAlpha = frac(screenPos * 0.25f); // We don't scale after the frac so this will give coords 0, 0.25, 0.5, 0.75 + int pixelId = (int)(pixelLocationAlpha.y * 16 + pixelLocationAlpha.x * 4); //faster as a dot2 ? + + // Clamp + // This ensures that for example alpha=0.95 still resolves some tiles of the surfaces behind it + // and alpha=0.05 still resolves some tiles of this surface + alpha = min(max(alpha, 0.0625), 0.9375); + + // Modern hardware supports array indexing with per pixel varying indexes + // on old hardware this will be expanded to a conditional tree by the compiler + const float thresholdMaxtrix[16] = { 1.0f / 17.0f, 9.0f / 17.0f, 3.0f / 17.0f, 11.0f / 17.0f, + 13.0f / 17.0f, 5.0f / 17.0f, 15.0f / 17.0f, 7.0f / 17.0f, + 4.0f / 17.0f, 12.0f / 17.0f, 2.0f / 17.0f, 10.0f / 17.0f, + 16.0f / 17.0f, 8.0f / 17.0f, 14.0f / 17.0f, 6.0f / 17.0f}; + float threshold = thresholdMaxtrix[pixelId]; + + if (alpha < threshold) + { + dither = false; + } + } + + gra_Branch if (dither) + { +#if (GRA_PACK_RESOLVE_OUTPUT==0) + resolveTexture[writePos] = Granite_PackTileId(resolve); +#else + resolveTexture[writePos] = resolve; +#endif + } +} +#endif + +float GranitePrivate_CalcMiplevelAnisotropic(in GraniteTilesetConstantBuffer tsCB, in GraniteStreamingTextureConstantBuffer grSTCB, in gra_Float2 ddxTc, in gra_Float2 ddyTc) +{ + // Calculate the required mipmap level, this uses a similar + // formula as the GL spec. + // To reduce sqrt's and log2's we do some stuff in squared space here and further below in log space + // i.e. we wait with the sqrt untill we can do it for 'free' later during the log2 + + ddxTc *= gra_CalcMiplevelDeltaScale; + ddyTc *= gra_CalcMiplevelDeltaScale; + + float lenDxSqr = dot(ddxTc, ddxTc); + float lenDySqr = dot(ddyTc, ddyTc); + float dMaxSqr = max(lenDxSqr, lenDySqr); + float dMinSqr = min(lenDxSqr, lenDySqr); + + // Calculate mipmap levels directly from sqared distances. This uses log2(sqrt(x)) = 0.5 * log2(x) to save some sqrt's + float maxLevel = 0.5 * log2( dMaxSqr ); + float minLevel = 0.5 * log2( dMinSqr ); + + // Calculate the log2 of the anisotropy and clamp it by the max supported. This uses log2(a/b) = log2(a)-log2(b) and min(log(a),log(b)) = log(min(a,b)) + float anisoLog2 = maxLevel - minLevel; + anisoLog2 = min( anisoLog2, gra_MaxAnisotropyLog2 ); + + // Adjust for anisotropy & clamp to level 0 + float result = max(maxLevel - anisoLog2 - 0.5f, 0.0f); //Subtract 0.5 to compensate for trilinear mipmapping + + // Added clamping to avoid "hot pink" on small tilesets that try to sample past the 1x1 tile miplevel + // This happens if you for example import a relatively small texture and zoom out + return min(result, gra_NumLevels); +} + +float GranitePrivate_CalcMiplevelLinear(in GraniteTilesetConstantBuffer tsCB, in GraniteStreamingTextureConstantBuffer grSTCB, in gra_Float2 ddxTc, in gra_Float2 ddyTc) +{ + // Calculate the required mipmap level, this uses a similar + // formula as the GL spec. + // To reduce sqrt's and log2's we do some stuff in squared space here and further below in log space + // i.e. we wait with the sqrt untill we can do it for 'free' later during the log2 + + ddxTc *= gra_CalcMiplevelDeltaScale; + ddyTc *= gra_CalcMiplevelDeltaScale; + + float lenDxSqr = dot(ddxTc, ddxTc); + float lenDySqr = dot(ddyTc, ddyTc); + float dMaxSqr = max(lenDxSqr, lenDySqr); + + // Calculate mipmap levels directly from squared distances. This uses log2(sqrt(x)) = 0.5 * log2(x) to save some sqrt's + float maxLevel = 0.5 * log2(dMaxSqr) - 0.5f; //Subtract 0.5 to compensate for trilinear mipmapping + + return clamp(maxLevel, 0.0f, gra_NumLevels); +} + +gra_Float4 GranitePrivate_PackTileId(in gra_Float2 tileXY, in float level, in float textureID) +{ +#if GRA_64BIT_RESOLVER == 0 + gra_Float4 resultBits; + + resultBits.x = fmod(tileXY.x, 256.0f); + resultBits.y = floor(tileXY.x / 256.0f) + fmod(tileXY.y, 32.0f) * 8.0f; + resultBits.z = floor(tileXY.y / 32.0f) + fmod(level, 4.0f) * 64.0f; + resultBits.w = floor(level / 4.0f) + textureID * 4.0f; + + const float scale = 1.0f / 255.0f; + +#if GRA_BGRA == 0 + return scale * gra_Float4 + ( + float(resultBits.x), + float(resultBits.y), + float(resultBits.z), + float(resultBits.w) + ); +#else + return scale * gra_Float4 + ( + float(resultBits.z), + float(resultBits.y), + float(resultBits.x), + float(resultBits.w) + ); +#endif +#else + const float scale = 1.0f / 65535.0f; + return gra_Float4(tileXY.x, tileXY.y, level, textureID) * scale; +#endif + +} + +gra_Float4 GranitePrivate_UnpackTileId(in gra_Float4 packedTile) +{ + gra_Float4 swiz; +#if GRA_BGRA == 0 + swiz = packedTile; +#else + swiz = packedTile.zyxw; +#endif + swiz *= 255.0f; + + float tileX = swiz.x + fmod(swiz.y, 16.0f) * 256.0f; + float tileY = floor(swiz.y / 16.0f) + swiz.z * 16.0f; + float level = fmod(swiz.w, 16.0f); + float tex = floor(swiz.w / 16.0f); + + return gra_Float4(tileX, tileY, level, tex); +} + +gra_Float3 GranitePrivate_TranslateCoord(in GraniteTilesetConstantBuffer tsCB, in gra_Float2 inputTexCoord, in gra_Float4 translationData, in int layer, out gra_Float2 numPagesOnLevel) +{ + // The translation table contains uint32_t values so we have to get to the individual bits of the float data + uint data = GranitePrivate_FloatAsUint(translationData[layer]); + + // Slice Index: 7 bits, Cache X: 10 bits, Cache Y: 10 bits, Tile Level: 4 bits + uint slice = (data >> 24u) & 0x7Fu; + uint cacheX = (data >> 14u) & 0x3FFu; + uint cacheY = (data >> 4u) & 0x3FFu; + uint revLevel = data & 0xFu; + + gra_Float2 numTilesOnLevel; + numTilesOnLevel.x = GranitePrivate_Pow2(revLevel); + numTilesOnLevel.y = numTilesOnLevel.x * gra_NumTilesYScale; + + gra_Float2 tileTexCoord = frac(inputTexCoord * numTilesOnLevel); + + gra_Float2 tileTexCoordCache = tileTexCoord * gra_TileContentInTiles + gra_Float2(cacheX, cacheY); + gra_Float3 final = gra_Float3(tileTexCoordCache * gra_RcpCacheInTiles(layer) + gra_BorderPixelsRcpCache(layer), slice); + + numPagesOnLevel = numTilesOnLevel * gra_TileContentInTiles * gra_RcpCacheInTiles(layer); + + return final; +} + +gra_Float4 GranitePrivate_DrawDebugTiles(in gra_Float4 sourceColor, in gra_Float2 textureCoord, in gra_Float2 numPagesOnLevel) +{ + // Calculate the border values + gra_Float2 cacheOffs = frac(textureCoord * numPagesOnLevel); + float borderTemp = max(cacheOffs.x, 1.0-cacheOffs.x); + borderTemp = max(max(cacheOffs.y, 1.0-cacheOffs.y), borderTemp); + float border = smoothstep(0.98, 0.99, borderTemp); + + // White + gra_Float4 borderColor = gra_Float4(1,1,1,1); + + //Lerp it over the source color + return lerp(sourceColor, borderColor, border); +} + +gra_Float4 GranitePrivate_MakeResolveOutput(in GraniteTilesetConstantBuffer tsCB, in gra_Float2 tileXY, in float level) +{ +#if GRA_PACK_RESOLVE_OUTPUT + return GranitePrivate_PackTileId(tileXY, level, gra_TextureId); +#else + return gra_Float4(tileXY, level, gra_TextureId); +#endif +} + +gra_Float4 GranitePrivate_ResolverPixel(in GraniteTilesetConstantBuffer tsCB, in gra_Float2 inputTexCoord, in float LOD) +{ + float level = floor(LOD + 0.5f); + + // Number of tiles on level zero + gra_Float2 level0NumTiles; + level0NumTiles.x = gra_Level0NumTilesX; + level0NumTiles.y = gra_Level0NumTilesX * gra_NumTilesYScale; + + // Calculate xy of the tiles to load + gra_Float2 virtualTilesUv = floor(inputTexCoord * level0NumTiles * pow(0.5, level)); + + return GranitePrivate_MakeResolveOutput(tsCB, virtualTilesUv, level); +} + +void GranitePrivate_CalculateCubemapCoordinates(in gra_Float3 inputTexCoord, in gra_Float3 dVx, in gra_Float3 dVy, in GraniteStreamingTextureCubeConstantBuffer transforms, out int faceIdx, out gra_Float2 texCoord, out gra_Float2 dX, out gra_Float2 dY) +{ + gra_Float2 contTexCoord; + gra_Float3 derivX; + gra_Float3 derivY; + + float majorAxis; + if (abs(inputTexCoord.z) >= abs(inputTexCoord.x) && abs(inputTexCoord.z) >= abs(inputTexCoord.y)) + { + // Z major axis + if(inputTexCoord.z < 0.0) + { + faceIdx = 5; + texCoord.x = -inputTexCoord.x; + } + else + { + faceIdx = 4; + texCoord.x = inputTexCoord.x; + } + texCoord.y = -inputTexCoord.y; + majorAxis = inputTexCoord.z; + + contTexCoord = gra_Float2(inputTexCoord.x, inputTexCoord.y); + derivX = gra_Float3(dVx.x, dVx.y, dVx.z); + derivY = gra_Float3(dVy.x, dVy.y, dVy.z); + } + else if (abs(inputTexCoord.y) >= abs(inputTexCoord.x)) + { + // Y major axis + if(inputTexCoord.y < 0.0) + { + faceIdx = 3; + texCoord.y = -inputTexCoord.z; + } + else + { + faceIdx = 2; + texCoord.y = inputTexCoord.z; + } + texCoord.x = inputTexCoord.x; + majorAxis = inputTexCoord.y; + + contTexCoord = gra_Float2(inputTexCoord.x, inputTexCoord.z); + derivX = gra_Float3(dVx.x, dVx.z, dVx.y); + derivY = gra_Float3(dVy.x, dVy.z, dVy.y); + } + else + { + // X major axis + if(inputTexCoord.x < 0.0) + { + faceIdx = 1; + texCoord.x = inputTexCoord.z; + } + else + { + faceIdx = 0; + texCoord.x = -inputTexCoord.z; + } + texCoord.y = -inputTexCoord.y; + majorAxis = inputTexCoord.x; + + contTexCoord = gra_Float2(inputTexCoord.z, inputTexCoord.y); + derivX = gra_Float3(dVx.z, dVx.y, dVx.x); + derivY = gra_Float3(dVy.z, dVy.y, dVy.x); + } + texCoord = (texCoord + majorAxis) / (2.0 * abs(majorAxis)); + +#if GRA_HQ_CUBEMAPPING + dX = /*contTexCoord **/ ((contTexCoord + derivX.xy) / ( 2.0 * (majorAxis + derivX.z)) - (contTexCoord / (2.0 * majorAxis))); + dY = /*contTexCoord **/ ((contTexCoord + derivY.xy) / ( 2.0 * (majorAxis + derivY.z)) - (contTexCoord / (2.0 * majorAxis))); +#else + dX = ((/*contTexCoord **/ derivX.xy) / (2.0 * abs(majorAxis))); + dY = ((/*contTexCoord **/ derivY.xy) / (2.0 * abs(majorAxis))); +#endif + + // Now scale the derivatives with the texture transform scale + dX *= transforms.data[faceIdx].data[0].zw; + dY *= transforms.data[faceIdx].data[0].zw; +} + +// Auto-level +void GranitePrivate_CalculateCubemapCoordinates(in gra_Float3 inputTexCoord, in GraniteStreamingTextureCubeConstantBuffer transforms, out int faceIdx, out gra_Float2 texCoord, out gra_Float2 dX, out gra_Float2 dY) +{ + gra_Float3 dVx = ddx(inputTexCoord); + gra_Float3 dVy = ddy(inputTexCoord); + + GranitePrivate_CalculateCubemapCoordinates(inputTexCoord, dVx, dVy, transforms, faceIdx, texCoord, dX, dY); +} + +gra_Float2 Granite_GetTextureDimensions(in GraniteStreamingTextureConstantBuffer grSTCB) +{ + return gra_Float2(1.0 / gra_AssetWidthRcp, 1.0 / gra_AssetHeightRcp); //TODO(ddebaets) use HLSL rcp here +} diff --git a/ShaderLibrary/GraniteShaderLibBase.hlsl.meta b/ShaderLibrary/GraniteShaderLibBase.hlsl.meta new file mode 100644 index 0000000..67d49ae --- /dev/null +++ b/ShaderLibrary/GraniteShaderLibBase.hlsl.meta @@ -0,0 +1,27 @@ +fileFormatVersion: 2 +guid: c5520328ccc5d6c4e8a923677cbb21cf +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/ShaderLibrary/ImageBasedLighting.hlsl b/ShaderLibrary/ImageBasedLighting.hlsl index fb60bc0..acc2075 100644 --- a/ShaderLibrary/ImageBasedLighting.hlsl +++ b/ShaderLibrary/ImageBasedLighting.hlsl @@ -32,12 +32,6 @@ real PerceptualRoughnessToMipmapLevel(real perceptualRoughness) return PerceptualRoughnessToMipmapLevel(perceptualRoughness, UNITY_SPECCUBE_LOD_STEPS); } -// Mapping for convolved Texture2D, this is an empirical remapping to match GGX version of cubemap convolution -real PlanarPerceptualRoughnessToMipmapLevel(real perceptualRoughness, uint mipMapcount) -{ - return PositivePow(perceptualRoughness, 0.8) * uint(max(mipMapcount - 1, 0)); -} - // The *accurate* version of the non-linear remapping. It works by // approximating the cone of the specular lobe, and then computing the MIP map level // which (approximately) covers the footprint of the lobe with a single texel. @@ -165,71 +159,6 @@ void SampleGGXDir(real2 u, L = mul(localL, localToWorld); } -// Ref: "A Simpler and Exact Sampling Routine for the GGX Distribution of Visible Normals". -void SampleVisibleAnisoGGXDir(real2 u, - real3 V, - real3x3 localToWorld, - real roughnessT, - real roughnessB, - out real3 L, - out real NdotL, - out real NdotH, - out real VdotH, - bool VeqN = false) -{ - real3 localV = mul(V, transpose(localToWorld)); - - // Construct an orthonormal basis around the stretched view direction - real3x3 viewToLocal; - if (VeqN) - { - viewToLocal = k_identity3x3; - } - else - { - // TODO: this code is tacky. We should make it cleaner - viewToLocal[2] = normalize(real3(roughnessT * localV.x, roughnessB * localV.y, localV.z)); - viewToLocal[0] = (viewToLocal[2].z < 0.9999) ? normalize(cross(real3(0, 0, 1), viewToLocal[2])) : real3(1, 0, 0); - viewToLocal[1] = cross(viewToLocal[2], viewToLocal[0]); - } - - // Compute a sample point with polar coordinates (r, phi) - real r = sqrt(u.x); - real phi = 2.0 * PI * u.y; - real t1 = r * cos(phi); - real t2 = r * sin(phi); - float s = 0.5 * (1.0 + viewToLocal[2].z); - t2 = (1.0 - s) * sqrt(1.0 - t1 * t1) + s * t2; - - // Reproject onto hemisphere - real3 localH = t1 * viewToLocal[0] + t2 * viewToLocal[1] + sqrt(max(0.0, 1.0 - t1 * t1 - t2 * t2)) * viewToLocal[2]; - - // Transform the normal back to the ellipsoid configuration - localH = normalize(real3(roughnessT * localH.x, roughnessB * localH.y, max(0.0, localH.z))); - - NdotH = localH.z; - VdotH = saturate(dot(localV, localH)); - - // Compute the reflection direction - real3 localL = 2.0 * VdotH * localH - localV; - NdotL = localL.z; - - L = mul(localL, localToWorld); -} - -void SampleVisibleGGXDir(real2 u, - real3 V, - real3x3 localToWorld, - real roughness, - out real3 L, - out real NdotL, - out real NdotH, - out real VdotH, - bool VeqN = false) -{ - SampleVisibleAnisoGGXDir(u, V, localToWorld, roughness, roughness, L, NdotL, NdotH, VdotH, VeqN); -} - // ref: http://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf p26 void SampleAnisoGGXDir(real2 u, real3 V, diff --git a/ShaderLibrary/Macros.hlsl b/ShaderLibrary/Macros.hlsl index 5e75816..524c4bc 100644 --- a/ShaderLibrary/Macros.hlsl +++ b/ShaderLibrary/Macros.hlsl @@ -29,6 +29,8 @@ #define HALF_PI 1.57079632679489661923 #define INV_HALF_PI 0.63661977236758134308 #define LOG2_E 1.44269504088896340736 +#define INV_SQRT2 0.70710678118654752440 +#define PI_DIV_FOUR 0.78539816339744830961 #define MILLIMETERS_PER_METER 1000 #define METERS_PER_MILLIMETER rcp(MILLIMETERS_PER_METER) @@ -41,21 +43,23 @@ #define FLT_MAX 3.402823466e+38 // Maximum representable floating-point number #define HALF_EPS 4.8828125e-4 // 2^-11, machine epsilon: 1 + EPS = 1 (half of the ULP for 1.0f) #define HALF_MIN 6.103515625e-5 // 2^-14, the same value for 10, 11 and 16-bit: https://www.khronos.org/opengl/wiki/Small_Float_Formats +#define HALF_MIN_SQRT 0.0078125 // 2^-7 == sqrt(HALF_MIN), useful for ensuring HALF_MIN after x^2 #define HALF_MAX 65504.0 #define UINT_MAX 0xFFFFFFFFu +#define INT_MAX 0x7FFFFFFF #ifdef SHADER_API_GLES #define GENERATE_INT_FLOAT_1_ARG(FunctionName, Parameter1, FunctionBody) \ float FunctionName(float Parameter1) { FunctionBody; } \ - int FunctionName(int Parameter1) { FunctionBody; } + int FunctionName(int Parameter1) { FunctionBody; } #else #define GENERATE_INT_FLOAT_1_ARG(FunctionName, Parameter1, FunctionBody) \ float FunctionName(float Parameter1) { FunctionBody; } \ uint FunctionName(uint Parameter1) { FunctionBody; } \ - int FunctionName(int Parameter1) { FunctionBody; } + int FunctionName(int Parameter1) { FunctionBody; } #endif @@ -227,11 +231,11 @@ #define GET_TEXELSIZE_NAME(name) (name##_TexelSize) #if UNITY_REVERSED_Z -# define COMPARE_DEVICE_DEPTH_CLOSER(shadowMapDepth, zDevice) (shadowMapDepth > zDevice) -# define COMPARE_DEVICE_DEPTH_CLOSEREQUAL(shadowMapDepth, zDevice) (shadowMapDepth >= zDevice) +# define COMPARE_DEVICE_DEPTH_CLOSER(shadowMapDepth, zDevice) (shadowMapDepth > zDevice) +# define COMPARE_DEVICE_DEPTH_CLOSEREQUAL(shadowMapDepth, zDevice) (shadowMapDepth >= zDevice) #else -# define COMPARE_DEVICE_DEPTH_CLOSER(shadowMapDepth, zDevice) (shadowMapDepth < zDevice) -# define COMPARE_DEVICE_DEPTH_CLOSEREQUAL(shadowMapDepth, zDevice) (shadowMapDepth <= zDevice) +# define COMPARE_DEVICE_DEPTH_CLOSER(shadowMapDepth, zDevice) (shadowMapDepth < zDevice) +# define COMPARE_DEVICE_DEPTH_CLOSEREQUAL(shadowMapDepth, zDevice) (shadowMapDepth <= zDevice) #endif #endif // UNITY_MACROS_INCLUDED diff --git a/ShaderLibrary/Packing.hlsl b/ShaderLibrary/Packing.hlsl index 7ab354b..b05a2ff 100644 --- a/ShaderLibrary/Packing.hlsl +++ b/ShaderLibrary/Packing.hlsl @@ -15,7 +15,7 @@ real3 UnpackNormalMaxComponent(real3 n) return normalize(n * 2.0 - 1.0); } -// Ref: http://www.vis.uni-stuttgart.de/~engelhts/paper/vmvOctaMaps.pdf +// Ref: http://www.vis.uni-stuttgart.de/~engelhts/paper/vmvOctaMaps.pdf "Octahedron Environment Maps" // Encode with Oct, this function work with any size of output // return real between [-1, 1] real2 PackNormalOctRectEncode(real3 n) @@ -47,7 +47,7 @@ real3 UnpackNormalOctRectEncode(real2 f) return normalize(p); } -// Ref: http://jcgt.org/published/0003/02/01/paper.pdf +// Ref: http://jcgt.org/published/0003/02/01/paper.pdf "A Survey of Efficient Representations for Independent Unit Vectors" // Encode with Oct, this function work with any size of output // return float between [-1, 1] float2 PackNormalOctQuadEncode(float3 n) @@ -177,7 +177,7 @@ real3 UnpackNormalAG(real4 packedNormal, real scale = 1.0) { real3 normal; normal.xy = packedNormal.ag * 2.0 - 1.0; - normal.z = sqrt(1.0 - saturate(dot(normal.xy, normal.xy))); + normal.z = max(1.0e-16, sqrt(1.0 - saturate(dot(normal.xy, normal.xy)))); // must scale after reconstruction of normal.z which also // mirrors UnpackNormalRGB(). This does imply normal is not returned @@ -200,7 +200,9 @@ real3 UnpackNormalmapRGorAG(real4 packedNormal, real scale = 1.0) real3 UnpackNormal(real4 packedNormal) { -#if defined(UNITY_NO_DXT5nm) +#if defined(UNITY_ASTC_NORMALMAP_ENCODING) + return UnpackNormalAG(packedNormal, 1.0); +#elif defined(UNITY_NO_DXT5nm) return UnpackNormalRGBNoScale(packedNormal); #else // Compiler will optimize the scale away @@ -210,7 +212,9 @@ real3 UnpackNormal(real4 packedNormal) real3 UnpackNormalScale(real4 packedNormal, real bumpScale) { -#if defined(UNITY_NO_DXT5nm) +#if defined(UNITY_ASTC_NORMALMAP_ENCODING) + return UnpackNormalAG(packedNormal, bumpScale); +#elif defined(UNITY_NO_DXT5nm) return UnpackNormalRGB(packedNormal, bumpScale); #else return UnpackNormalmapRGorAG(packedNormal, bumpScale); @@ -279,6 +283,15 @@ float3 UnpackFromR11G11B10f(uint rgb) #endif // SHADER_API_GLES +//----------------------------------------------------------------------------- +// Color packing +//----------------------------------------------------------------------------- + +float4 UnpackFromR8G8B8A8(uint rgba) +{ + return float4(rgba & 255, (rgba >> 8) & 255, (rgba >> 16) & 255, (rgba >> 24) & 255) * (1.0 / 255); +} + //----------------------------------------------------------------------------- // Quaternion packing //----------------------------------------------------------------------------- @@ -535,7 +548,7 @@ float3 PackFloat2To888(float2 f) // Unpack 2 float of 12bit packed into a 888 float2 Unpack888ToFloat2(float3 x) { - uint3 i = (uint3)(x * 255.0); + uint3 i = (uint3)(x * 255.5); // +0.5 to fix precision error on iOS // 8 bit in lo, 4 bit in hi uint hi = i.z >> 4; uint lo = i.z & 15; @@ -545,4 +558,27 @@ float2 Unpack888ToFloat2(float3 x) } #endif // SHADER_API_GLES +// Pack 2 float values from the [0, 1] range, to an 8 bits float from the [0, 1] range +float PackFloat2To8(float2 f) +{ + float x_expanded = f.x * 15.0; // f.x encoded over 4 bits, can have 2^4 = 16 distinct values mapped to [0, 1, ..., 15] + float y_expanded = f.y * 15.0; // f.y encoded over 4 bits, can have 2^4 = 16 distinct values mapped to [0, 1, ..., 15] + float x_y_expanded = x_expanded * 16.0 + y_expanded; // f.x encoded over higher bits, f.y encoded over the lower bits - x_y values in range [0, 1, ..., 255] + return x_y_expanded / 255.0; + + // above 4 lines equivalent to: + //return (16.0 * f.x + f.y) / 17.0; +} + +// Unpack 2 float values from the [0, 1] range, packed in an 8 bits float from the [0, 1] range +float2 Unpack8ToFloat2(float f) +{ + float x_y_expanded = 255.0 * f; + float x_expanded = floor(x_y_expanded / 16.0); + float y_expanded = x_y_expanded - 16.0 * x_expanded; + float x = x_expanded / 15.0; + float y = y_expanded / 15.0; + return float2(x, y); +} + #endif // UNITY_PACKING_INCLUDED diff --git a/ShaderLibrary/ParallaxMapping.hlsl b/ShaderLibrary/ParallaxMapping.hlsl new file mode 100644 index 0000000..b05b299 --- /dev/null +++ b/ShaderLibrary/ParallaxMapping.hlsl @@ -0,0 +1,44 @@ +#ifndef UNIVERSAL_PARALLAX_MAPPING_INCLUDED +#define UNIVERSAL_PARALLAX_MAPPING_INCLUDED + +// Return view direction in tangent space, make sure tangentWS.w is already multiplied by GetOddNegativeScale() +half3 GetViewDirectionTangentSpace(half4 tangentWS, half3 normalWS, half3 viewDirWS) +{ + // must use interpolated tangent, bitangent and normal before they are normalized in the pixel shader. + half3 unnormalizedNormalWS = normalWS; + const half renormFactor = 1.0 / length(unnormalizedNormalWS); + + // use bitangent on the fly like in hdrp + // IMPORTANT! If we ever support Flip on double sided materials ensure bitangent and tangent are NOT flipped. + half crossSign = (tangentWS.w > 0.0 ? 1.0 : -1.0); // we do not need to multiple GetOddNegativeScale() here, as it is done in vertex shader + half3 bitang = crossSign * cross(normalWS.xyz, tangentWS.xyz); + + half3 WorldSpaceNormal = renormFactor * normalWS.xyz; // we want a unit length Normal Vector node in shader graph + + // to preserve mikktspace compliance we use same scale renormFactor as was used on the normal. + // This is explained in section 2.2 in "surface gradient based bump mapping framework" + half3 WorldSpaceTangent = renormFactor * tangentWS.xyz; + half3 WorldSpaceBiTangent = renormFactor * bitang; + + half3x3 tangentSpaceTransform = half3x3(WorldSpaceTangent, WorldSpaceBiTangent, WorldSpaceNormal); + half3 viewDirTS = mul(tangentSpaceTransform, viewDirWS); + + return viewDirTS; +} + +half2 ParallaxOffset1Step(half height, half amplitude, half3 viewDirTS) +{ + height = height * amplitude - amplitude / 2.0; + half3 v = normalize(viewDirTS); + v.z += 0.42; + return height * (v.xy / v.z); +} + +float2 ParallaxMapping(TEXTURE2D_PARAM(heightMap, sampler_heightMap), half3 viewDirTS, half scale, float2 uv) +{ + half h = SAMPLE_TEXTURE2D(heightMap, sampler_heightMap, uv).g; + float2 offset = ParallaxOffset1Step(h, scale, viewDirTS); + return offset; +} + +#endif // UNIVERSAL_PARALLAX_MAPPING_INCLUDED diff --git a/ShaderLibrary/ParallaxMapping.hlsl.meta b/ShaderLibrary/ParallaxMapping.hlsl.meta new file mode 100644 index 0000000..2e15d47 --- /dev/null +++ b/ShaderLibrary/ParallaxMapping.hlsl.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: c6976b17260ed7d4ca3810ae8488f67f +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/ShaderLibrary/PerPixelDisplacement.hlsl b/ShaderLibrary/PerPixelDisplacement.hlsl index 0b90213..95208d1 100644 --- a/ShaderLibrary/PerPixelDisplacement.hlsl +++ b/ShaderLibrary/PerPixelDisplacement.hlsl @@ -6,7 +6,13 @@ // it return the offset to apply to the UVSet provide in PerPixelHeightDisplacementParam // viewDirTS is view vector in texture space matching the UVSet // ref: https://www.gamedev.net/resources/_/technical/graphics-programming-and-theory/a-closer-look-at-parallax-occlusion-mapping-r3262 -real2 ParallaxOcclusionMapping(real lod, real lodThreshold, int numSteps, real3 viewDirTS, PerPixelHeightDisplacementParam ppdParam, out real outHeight) +real2 +#ifdef POM_NAME_ID +MERGE_NAME(ParallaxOcclusionMapping,POM_NAME_ID) +#else +ParallaxOcclusionMapping +#endif +(real lod, real lodThreshold, int numSteps, real3 viewDirTS, PerPixelHeightDisplacementParam ppdParam, out real outHeight) { // Convention: 1.0 is top, 0.0 is bottom - POM is always inward, no extrusion real stepSize = 1.0 / (real)numSteps; diff --git a/ShaderLibrary/PhysicalCamera.hlsl b/ShaderLibrary/PhysicalCamera.hlsl index 82748dc..89a7405 100644 --- a/ShaderLibrary/PhysicalCamera.hlsl +++ b/ShaderLibrary/PhysicalCamera.hlsl @@ -21,6 +21,12 @@ float ComputeEV100(float aperture, float shutterSpeed, float ISO) return log2((aperture * aperture) / shutterSpeed * 100.0 / ISO); } +float ComputeEV100FromAvgLuminance(float avgLuminance, float calibrationConstant) +{ + const float K = calibrationConstant; + return log2(avgLuminance * 100.0 / K); +} + float ComputeEV100FromAvgLuminance(float avgLuminance) { // We later use the middle gray at 12.7% in order to have @@ -30,21 +36,27 @@ float ComputeEV100FromAvgLuminance(float avgLuminance) // constructor settings (i.e. calibration constant K = 12.5) // Reference: http://en.wikipedia.org/wiki/Film_speed const float K = 12.5; // Reflected-light meter calibration constant - return log2(avgLuminance * 100.0 / K); + return ComputeEV100FromAvgLuminance(avgLuminance, K); } -float ConvertEV100ToExposure(float EV100) +float ConvertEV100ToExposure(float EV100, float exposureScale) { // Compute the maximum luminance possible with H_sbs sensitivity // maxLum = 78 / ( S * q ) * N^2 / t // = 78 / ( S * q ) * 2^ EV_100 - // = 78 / (100 * 0.65) * 2^ EV_100 - // = 1.2 * 2^ EV + // = 78 / (100 * s_LensAttenuation) * 2^ EV_100 + // = exposureScale * 2^ EV // Reference: http://en.wikipedia.org/wiki/Film_speed - float maxLuminance = 1.2 * pow(2.0, EV100); + float maxLuminance = exposureScale * pow(2.0, EV100); return 1.0 / maxLuminance; } +float ConvertEV100ToExposure(float EV100) +{ + const float exposureScale = 1.2; + return ConvertEV100ToExposure(EV100, exposureScale); +} + float ComputeISO(float aperture, float shutterSpeed, float targetEV100) { // Compute the required ISO to reach the target EV100 diff --git a/ShaderLibrary/Sampling/Fibonacci.hlsl b/ShaderLibrary/Sampling/Fibonacci.hlsl index 111c5d8..a6a963b 100644 --- a/ShaderLibrary/Sampling/Fibonacci.hlsl +++ b/ShaderLibrary/Sampling/Fibonacci.hlsl @@ -12,8 +12,8 @@ real2 Fibonacci2dSeq(real fibN1, real fibN2, uint i) return real2(i / fibN1 + (0.5 / fibN1), frac(i * (fibN2 / fibN1))); } -#define GOLDEN_RATIO 1.61803 -#define GOLDEN_ANGLE 2.39996 +#define GOLDEN_RATIO 1.618033988749895 +#define GOLDEN_ANGLE 2.399963229728653 // Replaces the Fibonacci sequence in Fibonacci2dSeq() with the Golden ratio. real2 Golden2dSeq(uint i, real n) @@ -268,6 +268,12 @@ real2 Fibonacci2d(uint i, uint sampleCount) } } +real2 SampleDiskGolden(uint i, uint sampleCount) +{ + real2 f = Golden2dSeq(i, sampleCount); + return real2(sqrt(f.x), TWO_PI * f.y); +} + // Returns the radius as the X coordinate, and the angle as the Y coordinate. real2 SampleDiskFibonacci(uint i, uint sampleCount) { diff --git a/ShaderLibrary/Sampling/SampleUVMappingInternal.hlsl b/ShaderLibrary/Sampling/SampleUVMappingInternal.hlsl index 054ec2a..8427855 100644 --- a/ShaderLibrary/Sampling/SampleUVMappingInternal.hlsl +++ b/ShaderLibrary/Sampling/SampleUVMappingInternal.hlsl @@ -30,7 +30,10 @@ real4 ADD_FUNC_SUFFIX(SampleUVMapping)(TEXTURE2D_PARAM(textureName, samplerName) // This version is use for the base normal map (BC5 or DXT5nm) #define ADD_NORMAL_FUNC_SUFFIX(Name) Name -#if defined(UNITY_NO_DXT5nm) +#if defined(UNITY_ASTC_NORMALMAP_ENCODING) +#define UNPACK_NORMAL_FUNC UnpackNormalAG +#define UNPACK_DERIVATIVE_FUNC UnpackDerivativeNormalAG +#elif defined(UNITY_NO_DXT5nm) #define UNPACK_NORMAL_FUNC UnpackNormalRGB #define UNPACK_DERIVATIVE_FUNC UnpackDerivativeNormalRGB #else diff --git a/ShaderLibrary/Sampling/Sampling.hlsl b/ShaderLibrary/Sampling/Sampling.hlsl index 09bc06e..2228d5a 100644 --- a/ShaderLibrary/Sampling/Sampling.hlsl +++ b/ShaderLibrary/Sampling/Sampling.hlsl @@ -120,6 +120,18 @@ real2 SampleDiskUniform(real u1, real u2) return r * real2(cosPhi, sinPhi); } +// Performs cubic sampling of the unit disk. +real2 SampleDiskCubic(real u1, real u2) +{ + real r = u1; + real phi = TWO_PI * u2; + + real sinPhi, cosPhi; + sincos(phi, sinPhi, cosPhi); + + return r * real2(cosPhi, sinPhi); +} + real3 SampleConeUniform(real u1, real u2, real cos_theta) { float r0 = cos_theta + u1 * (1.0f - cos_theta); diff --git a/ShaderLibrary/SpaceTransforms.hlsl b/ShaderLibrary/SpaceTransforms.hlsl index 10af2b7..e576c7f 100644 --- a/ShaderLibrary/SpaceTransforms.hlsl +++ b/ShaderLibrary/SpaceTransforms.hlsl @@ -33,7 +33,7 @@ float4x4 GetViewToHClipMatrix() float3 GetAbsolutePositionWS(float3 positionRWS) { #if (SHADEROPTIONS_CAMERA_RELATIVE_RENDERING != 0) - positionRWS += _WorldSpaceCameraPos; + positionRWS += _WorldSpaceCameraPos.xyz; #endif return positionRWS; } @@ -42,14 +42,17 @@ float3 GetAbsolutePositionWS(float3 positionRWS) float3 GetCameraRelativePositionWS(float3 positionWS) { #if (SHADEROPTIONS_CAMERA_RELATIVE_RENDERING != 0) - positionWS -= _WorldSpaceCameraPos; + positionWS -= _WorldSpaceCameraPos.xyz; #endif return positionWS; } real GetOddNegativeScale() { - return unity_WorldTransformParams.w; + // FIXME: We should be able to just return unity_WorldTransformParams.w, but it is not + // properly set at the moment, when doing ray-tracing; once this has been fixed in cpp, + // we can revert back to the former implementation. + return unity_WorldTransformParams.w >= 0.0 ? 1.0 : -1.0; } float3 TransformObjectToWorld(float3 positionOS) @@ -113,7 +116,7 @@ real3 TransformWorldToViewDir(real3 dirWS, bool doNormalize = false) if (doNormalize) return normalize(dirVS); - return dirVS; + return dirVS; } // Tranforms vector from world space to homogenous space @@ -180,20 +183,20 @@ real3 TransformWorldToTangent(real3 dirWS, real3x3 tangentToWorld) float3 row0 = tangentToWorld[0]; float3 row1 = tangentToWorld[1]; float3 row2 = tangentToWorld[2]; - + // these are the columns of the inverse matrix but scaled by the determinant float3 col0 = cross(row1, row2); float3 col1 = cross(row2, row0); float3 col2 = cross(row0, row1); - + float determinant = dot(row0, col0); float sgn = determinant<0.0 ? (-1.0) : 1.0; - + // inverse transposed but scaled by determinant // Will remove transpose part by using matrix as the first arg in the mul() below // this makes it the exact inverse of what TransformTangentToWorld() does. real3x3 matTBN_I_T = real3x3(col0, col1, col2); - + return SafeNormalize( sgn * mul(matTBN_I_T, dirWS) ); } @@ -208,9 +211,9 @@ real3 TransformObjectToTangent(real3 dirOS, real3x3 tangentToWorld) { // Note matrix is in row major convention with left multiplication as it is build on the fly - // don't normalize, as normalWS will be normalized after TransformWorldToTangent + // don't normalize, as normalWS will be normalized after TransformWorldToTangent float3 normalWS = TransformObjectToWorldNormal(dirOS, false); - + // transform from world to tangent return TransformWorldToTangent(normalWS, tangentToWorld); } diff --git a/ShaderLibrary/TextureStack.hlsl b/ShaderLibrary/TextureStack.hlsl new file mode 100644 index 0000000..635d474 --- /dev/null +++ b/ShaderLibrary/TextureStack.hlsl @@ -0,0 +1,421 @@ +#ifndef TEXTURESTACK_include +#define TEXTURESTACK_include + +#define GRA_HLSL_5 1 +#define GRA_ROW_MAJOR 1 +#define GRA_TEXTURE_ARRAY_SUPPORT 1 +#define GRA_PACK_RESOLVE_OUTPUT 0 +#if SHADER_API_PSSL +#define GRA_NO_UNORM 1 +#endif +#include "VirtualTexturing.hlsl" +#include "Packing.hlsl" + +/* + This header adds the following pseudo definitions. Actual types etc may vary depending + on vt- being on or off. + + struct StackInfo { opaque struct ... } + StackInfo PrepareStack(float2 uv, Stack object); + float4 SampleStack(StackInfo info, Texture tex); + + To use this in your materials add the following to various locations in the shader: + + In shaderlab "Properties" section add: + + [TextureStack.MyFancyStack] DiffuseTexture ("DiffuseTexture", 2D) = "white" {} + [TextureStack.MyFancyStack] NormalTexture ("NormalTexture", 2D) = "white" {} + + This will declare a texture stack with two textures. + + Then add the following to the PerMaterial constant buffer: + + CBUFFER_START(UnityPerMaterial) + ... + DECLARE_STACK_CB(MyFancyStack) + ... + CBUFFER_END + + Then in your shader root add the following: + + ... + + DECLARE_STACK(MyFancyStack, DiffuseTexture) + or + DECLARE_STACK2(MyFancyStack, DiffuseTexture, NormalTexture) + or + DECLARE_STACK3(MyFancyStack, TextureSlot1, TextureSlot2, TextureSlot2) + etc... + + NOTE: The Stack shaderlab property and DECLARE_STACKn define need to match i.e. the same name and same texture slots. + + Then in the pixel shader function (likely somewhere at the beginning) do a call: + + StackInfo info = PrepareStack(uvs, MyFancyStack); + + Then later on when you want to sample the actual texture do a call(s): + + float4 color = SampleStack(info, TextureSlot1); + float4 color2 = SampleStack(info, TextureSlot2); + ... + + The above steps can be repeated for multiple stacks. But be sure that when using the SampleStack you always + pass in the result of the PrepareStack for the correct stack the texture belongs to. + +*/ + +#if defined(UNITY_VIRTUAL_TEXTURING) && !defined(FORCE_VIRTUAL_TEXTURING_OFF) + +struct StackInfo +{ + GraniteLookupData lookupData; + GraniteLODLookupData lookupDataLod; + float4 resolveOutput; +}; + +struct VTProperty +{ + GraniteConstantBuffers grCB; + GraniteTranslationTexture translationTable; + GraniteCacheTexture cacheLayer[4]; + int layerCount; + int layerIndex[4]; +}; + +#ifdef TEXTURESTACK_CLAMP + #define GR_LOOKUP Granite_Lookup_Clamp_Linear + #define GR_LOOKUP_LOD Granite_Lookup_Clamp +#else + #define GR_LOOKUP Granite_Lookup_Anisotropic + #define GR_LOOKUP_LOD Granite_Lookup +#endif + +// This can be used by certain resolver implementations to override screen space derivatives +#ifndef RESOLVE_SCALE_OVERRIDE +#define RESOLVE_SCALE_OVERRIDE float2(1,1) +#endif + +StructuredBuffer _VTTilesetBuffer; + +#define DECLARE_STACK_CB(stackName) \ + float4 stackName##_atlasparams[2] + +#define DECLARE_STACK_BASE(stackName) \ +TEXTURE2D(stackName##_transtab);\ +SAMPLER(sampler##stackName##_transtab);\ +\ +GraniteTilesetConstantBuffer GetConstantBuffer_##stackName() \ +{ \ + int idx = (int)stackName##_atlasparams[1].w; \ + GraniteTilesetConstantBuffer graniteParamBlock; \ + graniteParamBlock = _VTTilesetBuffer[idx]; \ + \ + graniteParamBlock.data[0][2][0] *= RESOLVE_SCALE_OVERRIDE.x; \ + graniteParamBlock.data[0][3][0] *= RESOLVE_SCALE_OVERRIDE.y; \ + \ + return graniteParamBlock; \ +} \ +StackInfo PrepareVT_##stackName(VtInputParameters par)\ +{\ + GraniteStreamingTextureConstantBuffer textureParamBlock;\ + textureParamBlock.data[0] = stackName##_atlasparams[0];\ + textureParamBlock.data[1] = stackName##_atlasparams[1];\ +\ + GraniteTilesetConstantBuffer graniteParamBlock = GetConstantBuffer_##stackName(); \ +\ + GraniteConstantBuffers grCB;\ + grCB.tilesetBuffer = graniteParamBlock;\ + grCB.streamingTextureBuffer = textureParamBlock;\ +\ + GraniteTranslationTexture translationTable;\ + translationTable.Texture = stackName##_transtab;\ + translationTable.Sampler = sampler##stackName##_transtab;\ +\ + StackInfo info;\ + VirtualTexturingLookup(grCB, translationTable, par, info.lookupData, info.resolveOutput);\ + return info;\ +} + +// TODO: we could replace all uses of GetConstantBuffer_*() with this one: +GraniteTilesetConstantBuffer GetConstantBuffer(GraniteStreamingTextureConstantBuffer textureParamBlock) +{ + int idx = (int)textureParamBlock.data[1].w; + GraniteTilesetConstantBuffer graniteParamBlock; + graniteParamBlock = _VTTilesetBuffer[idx]; + + graniteParamBlock.data[0][2][0] *= RESOLVE_SCALE_OVERRIDE.x; + graniteParamBlock.data[0][3][0] *= RESOLVE_SCALE_OVERRIDE.y; + + return graniteParamBlock; +} + +#define jj2(a, b) a##b +#define jj(a, b) jj2(a, b) + +#define DECLARE_STACK_LAYER(stackName, layerSamplerName, layerIndex) \ +TEXTURE2D_ARRAY(stackName##_c##layerIndex);\ +SAMPLER(sampler##stackName##_c##layerIndex);\ +\ +float4 SampleVT_##layerSamplerName(StackInfo info, int lodCalculation, int quality)\ +{\ + GraniteStreamingTextureConstantBuffer textureParamBlock;\ + textureParamBlock.data[0] = stackName##_atlasparams[0];\ + textureParamBlock.data[1] = stackName##_atlasparams[1];\ +\ + GraniteTilesetConstantBuffer graniteParamBlock = GetConstantBuffer_##stackName(); \ +\ + GraniteConstantBuffers grCB;\ + grCB.tilesetBuffer = graniteParamBlock;\ + grCB.streamingTextureBuffer = textureParamBlock;\ +\ + GraniteCacheTexture cache;\ + cache.TextureArray = stackName##_c##layerIndex;\ + cache.Sampler = sampler##stackName##_c##layerIndex;\ +\ + float4 output;\ + VirtualTexturingSample(grCB.tilesetBuffer, info.lookupData, cache, layerIndex, lodCalculation, quality, output);\ + return output;\ +} + +#define DECLARE_BUILD_PROPERTIES(stackName, layers, layer0Index, layer1Index, layer2Index, layer3Index)\ + VTProperty BuildVTProperties_##stackName()\ + {\ + VTProperty vtProperty; \ + \ + GraniteStreamingTextureConstantBuffer textureParamBlock; \ + textureParamBlock.data[0] = stackName##_atlasparams[0]; \ + textureParamBlock.data[1] = stackName##_atlasparams[1]; \ + \ + vtProperty.grCB.tilesetBuffer = GetConstantBuffer(textureParamBlock); \ + vtProperty.grCB.streamingTextureBuffer = textureParamBlock; \ + \ + vtProperty.translationTable.Texture = stackName##_transtab; \ + vtProperty.translationTable.Sampler = sampler##stackName##_transtab; \ + \ + vtProperty.layerCount = layers; \ + vtProperty.layerIndex[0] = layer0Index; \ + vtProperty.layerIndex[1] = layer1Index; \ + vtProperty.layerIndex[2] = layer2Index; \ + vtProperty.layerIndex[3] = layer3Index; \ + \ + vtProperty.cacheLayer[0].TextureArray = stackName##_c##layer0Index; \ + vtProperty.cacheLayer[0].Sampler = sampler##stackName##_c##layer0Index;\ + vtProperty.cacheLayer[1].TextureArray = stackName##_c##layer1Index; \ + vtProperty.cacheLayer[1].Sampler = sampler##stackName##_c##layer1Index;\ + vtProperty.cacheLayer[2].TextureArray = stackName##_c##layer2Index; \ + vtProperty.cacheLayer[2].Sampler = sampler##stackName##_c##layer2Index;\ + vtProperty.cacheLayer[3].TextureArray = stackName##_c##layer3Index; \ + vtProperty.cacheLayer[3].Sampler = sampler##stackName##_c##layer3Index;\ + \ + return vtProperty; \ + } + +#define DECLARE_STACK(stackName, layer0SamplerName)\ + DECLARE_STACK_BASE(stackName)\ + DECLARE_STACK_LAYER(stackName, layer0SamplerName, 0)\ + DECLARE_BUILD_PROPERTIES(stackName, 1, 0, 0, 0, 0) + +#define DECLARE_STACK2(stackName, layer0SamplerName, layer1SamplerName)\ + DECLARE_STACK_BASE(stackName)\ + DECLARE_STACK_LAYER(stackName, layer0SamplerName, 0)\ + DECLARE_STACK_LAYER(stackName, layer1SamplerName, 1)\ + DECLARE_BUILD_PROPERTIES(stackName, 2, 0, 1, 1, 1) + +#define DECLARE_STACK3(stackName, layer0SamplerName, layer1SamplerName, layer2SamplerName)\ + DECLARE_STACK_BASE(stackName)\ + DECLARE_STACK_LAYER(stackName, layer0SamplerName, 0)\ + DECLARE_STACK_LAYER(stackName, layer1SamplerName, 1)\ + DECLARE_STACK_LAYER(stackName, layer2SamplerName, 2)\ + DECLARE_BUILD_PROPERTIES(stackName, 3, 0, 1, 2, 2) + +#define DECLARE_STACK4(stackName, layer0SamplerName, layer1SamplerName, layer2SamplerName, layer3SamplerName)\ + DECLARE_STACK_BASE(stackName)\ + DECLARE_STACK_LAYER(stackName, layer0SamplerName, 0)\ + DECLARE_STACK_LAYER(stackName, layer1SamplerName, 1)\ + DECLARE_STACK_LAYER(stackName, layer2SamplerName, 2)\ + DECLARE_STACK_LAYER(stackName, layer3SamplerName, 3)\ + DECLARE_BUILD_PROPERTIES(stackName, 4, 0, 1, 2, 3) + +#define PrepareStack(inputParams, stackName) PrepareVT_##stackName(inputParams) +#define SampleStack(info, lodMode, quality, textureName) SampleVT_##textureName(info, lodMode, quality) +#define GetResolveOutput(info) info.resolveOutput +#define PackResolveOutput(output) Granite_PackTileId(output) + +StackInfo PrepareVT(VTProperty vtProperty, VtInputParameters vtParams) +{ + StackInfo info; + VirtualTexturingLookup(vtProperty.grCB, vtProperty.translationTable, vtParams, info.lookupData, info.resolveOutput); + return info; +} + +// NOTE: layerIndex here can only be an immediate constant (i.e. 0,1,2, or 3) -- it CANNOT be a variable or expression +// this is because we use macro concatentation on it when VT is disabled +float4 SampleVTLayer(VTProperty vtProperty, VtInputParameters vtParams, StackInfo info, int layerIndex) +{ + float4 result; + VirtualTexturingSample(vtProperty.grCB.tilesetBuffer, info.lookupData, vtProperty.cacheLayer[layerIndex], vtProperty.layerIndex[layerIndex], vtParams.levelMode, vtParams.sampleQuality, result); + return result; +} + +float4 GetPackedVTFeedback(float4 feedback) +{ + return Granite_PackTileId(feedback); +} + +#define VIRTUAL_TEXTURING_SHADER_ENABLED + +#else +// Virtual Texturing Disabled -- fallback to regular texture sampling + +#define DECLARE_BUILD_PROPERTIES(stackName, layers, layer0, layer1, layer2, layer3)\ + VTProperty BuildVTProperties_##stackName()\ + {\ + VTProperty vtProperty; \ + \ + vtProperty.layerCount = layers; \ + vtProperty.Layer0 = layer0; \ + vtProperty.Layer1 = layer1; \ + vtProperty.Layer2 = layer2; \ + vtProperty.Layer3 = layer3; \ + \ + ASSIGN_SAMPLER(vtProperty.samplerLayer0, sampler##layer0); \ + ASSIGN_SAMPLER(vtProperty.samplerLayer1, sampler##layer1); \ + ASSIGN_SAMPLER(vtProperty.samplerLayer2, sampler##layer2); \ + ASSIGN_SAMPLER(vtProperty.samplerLayer3, sampler##layer3); \ + \ + return vtProperty; \ + } + +// Stacks amount to nothing when VT is off +#define DECLARE_STACK(stackName, layer0) \ + DECLARE_BUILD_PROPERTIES(stackName, 1, layer0, layer0, layer0, layer0) + +#define DECLARE_STACK2(stackName, layer0, layer1) \ + DECLARE_BUILD_PROPERTIES(stackName, 2, layer0, layer1, layer1, layer1) + +#define DECLARE_STACK3(stackName, layer0, layer1, layer2) \ + DECLARE_BUILD_PROPERTIES(stackName, 3, layer0, layer1, layer2, layer2) + +#define DECLARE_STACK4(stackName, layer0, layer1, layer2, layer3) \ + DECLARE_BUILD_PROPERTIES(stackName, 4, layer0, layer1, layer2, layer3) + +#define DECLARE_STACK_CB(stackName) + +// Info is just the uv's +// We could do a straight #define StackInfo float2 but this makes it a bit more type safe +// and allows us to do things like function overloads,... +struct StackInfo +{ + VtInputParameters vt; +}; + +struct VTProperty +{ + int layerCount; + TEXTURE2D(Layer0); + TEXTURE2D(Layer1); + TEXTURE2D(Layer2); + TEXTURE2D(Layer3); +#ifndef SHADER_API_GLES + SAMPLER(samplerLayer0); + SAMPLER(samplerLayer1); + SAMPLER(samplerLayer2); + SAMPLER(samplerLayer3); +#endif +}; + +StackInfo MakeStackInfo(VtInputParameters vt) +{ + StackInfo result; + result.vt = vt; + return result; +} + +// Prepare just passes the texture coord around +#define PrepareStack(inputParams, stackName) MakeStackInfo(inputParams) + +// Sample just samples the texture +#define SampleStack(info, vtLevelMode, quality, texture) \ + SampleVTFallbackToTexture(info, vtLevelMode, TEXTURE2D_ARGS(texture, sampler##texture)) + + +float4 SampleVTFallbackToTexture(StackInfo info, int vtLevelMode, TEXTURE2D_PARAM(layerTexture, layerSampler)) +{ + if (vtLevelMode == VtLevel_Automatic) + return SAMPLE_TEXTURE2D(layerTexture, layerSampler, info.vt.uv); + else if (vtLevelMode == VtLevel_Lod) + return SAMPLE_TEXTURE2D_LOD(layerTexture, layerSampler, info.vt.uv, info.vt.lodOrOffset); + else if (vtLevelMode == VtLevel_Bias) + return SAMPLE_TEXTURE2D_BIAS(layerTexture, layerSampler, info.vt.uv, info.vt.lodOrOffset); + else // vtLevelMode == VtLevel_Derivatives + return SAMPLE_TEXTURE2D_GRAD(layerTexture, layerSampler, info.vt.uv, info.vt.dx, info.vt.dy); +} + +StackInfo PrepareVT(VTProperty vtProperty, VtInputParameters vtParams) +{ + StackInfo result; + result.vt = vtParams; + return result; +} + +// NOTE: layerIndex here can only be an immediate constant (i.e. 0,1,2, or 3) -- it CANNOT be a variable or expression +// this is because we use macro concatentation on it when VT is disabled +#define SampleVTLayer(vtProperty, vtParams, info, layerIndex) \ + SampleVTFallbackToTexture(info, vtParams.levelMode, TEXTURE2D_ARGS(vtProperty.Layer##layerIndex, vtProperty.samplerLayer##layerIndex)) + +// Resolve does nothing +#define GetResolveOutput(info) float4(1,1,1,1) +#define PackResolveOutput(output) output +#define GetPackedVTFeedback(feedback) feedback + +#endif + + + +// Shared code between VT enabled and VT disabled, adding TextureType handling + +// these texture types should be kept in sync with LayerTextureType in C# code +#define TEXTURETYPE_DEFAULT 0 // LayerTextureType.Default +#define TEXTURETYPE_NORMALTANGENTSPACE 1 // LayerTextureType.NormalTangentSpace +#define TEXTURETYPE_NORMALOBJECTSPACE 2 // LayerTextureType.NormalObjectSpace + +struct VTPropertyWithTextureType +{ + VTProperty vtProperty; + int layerTextureType[4]; +}; + +VTPropertyWithTextureType AddTextureType(VTProperty vtProperty, int layer0TextureType, int layer1TextureType = TEXTURETYPE_DEFAULT, int layer2TextureType = TEXTURETYPE_DEFAULT, int layer3TextureType = TEXTURETYPE_DEFAULT) +{ + VTPropertyWithTextureType result; + result.vtProperty = vtProperty; + result.layerTextureType[0] = layer0TextureType; + result.layerTextureType[1] = layer1TextureType; + result.layerTextureType[2] = layer2TextureType; + result.layerTextureType[3] = layer3TextureType; + return result; +} + +float4 ApplyTextureType(float4 value, int textureType) +{ + // NOTE: when textureType is a compile-time constant, the branches compile out + if (textureType == TEXTURETYPE_NORMALTANGENTSPACE) + { + value.rgb = UnpackNormalmapRGorAG(value); + } + else if (textureType == TEXTURETYPE_NORMALOBJECTSPACE) + { + value.rgb = UnpackNormalRGB(value); + } + return value; +} + +// if we _could_ express it as a function, the function signature would be: +// float4 SampleVTLayerWithTextureType(VTPropertyWithTextureType vtProperty, VtInputParameters vtParams, StackInfo info, [immediate] int layerIndex) +// NOTE: layerIndex here can only be an immediate constant (i.e. 0,1,2, or 3) -- it CANNOT be a variable or expression +// this is because we use macro concatentation on it when VT is disabled + +#define SampleVTLayerWithTextureType(vtProperty, vtParams, info, layerIndex) \ + ApplyTextureType(SampleVTLayer(vtProperty.vtProperty, vtParams, info, layerIndex), vtProperty.layerTextureType[layerIndex]) + +#endif //TEXTURESTACK_include diff --git a/ShaderLibrary/TextureStack.hlsl.meta b/ShaderLibrary/TextureStack.hlsl.meta new file mode 100644 index 0000000..90afd94 --- /dev/null +++ b/ShaderLibrary/TextureStack.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 79641ba14fe04324b8905bafbf3e82ed +ShaderImporter: + defaultTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/ShaderLibrary/UnityDOTSInstancing.hlsl b/ShaderLibrary/UnityDOTSInstancing.hlsl new file mode 100644 index 0000000..17a0f30 --- /dev/null +++ b/ShaderLibrary/UnityDOTSInstancing.hlsl @@ -0,0 +1,239 @@ +#ifndef UNITY_DOTS_INSTANCING_INCLUDED +#define UNITY_DOTS_INSTANCING_INCLUDED + +#ifdef UNITY_DOTS_INSTANCING_ENABLED + +/* +Here's a bit of python code to generate these repetitive typespecs without +a lot of C macro magic + +def print_dots_instancing_typespecs(elem_type, id_char, elem_size): + print(f"#define UNITY_DOTS_INSTANCING_TYPESPEC_{elem_type} {id_char}{elem_size}") + for y in range(1, 5): + for x in range(1, 5): + rows = "" if y == 1 else f"x{y}" + size = elem_size * x * y + print(f"#define UNITY_DOTS_INSTANCING_TYPESPEC_{elem_type}{x}{rows} {id_char}{size}") + +for t, c, sz in ( + ('float', 'F', 4), + ('int', 'I', 4), + ('uint', 'U', 4), + ('half', 'H', 2) + ): + print_dots_instancing_typespecs(t, c, sz) +*/ + +#define UNITY_DOTS_INSTANCING_TYPESPEC_float F4 +#define UNITY_DOTS_INSTANCING_TYPESPEC_float1 F4 +#define UNITY_DOTS_INSTANCING_TYPESPEC_float2 F8 +#define UNITY_DOTS_INSTANCING_TYPESPEC_float3 F12 +#define UNITY_DOTS_INSTANCING_TYPESPEC_float4 F16 +#define UNITY_DOTS_INSTANCING_TYPESPEC_float1x2 F8 +#define UNITY_DOTS_INSTANCING_TYPESPEC_float2x2 F16 +#define UNITY_DOTS_INSTANCING_TYPESPEC_float3x2 F24 +#define UNITY_DOTS_INSTANCING_TYPESPEC_float4x2 F32 +#define UNITY_DOTS_INSTANCING_TYPESPEC_float1x3 F12 +#define UNITY_DOTS_INSTANCING_TYPESPEC_float2x3 F24 +#define UNITY_DOTS_INSTANCING_TYPESPEC_float3x3 F36 +#define UNITY_DOTS_INSTANCING_TYPESPEC_float4x3 F48 +#define UNITY_DOTS_INSTANCING_TYPESPEC_float1x4 F16 +#define UNITY_DOTS_INSTANCING_TYPESPEC_float2x4 F32 +#define UNITY_DOTS_INSTANCING_TYPESPEC_float3x4 F48 +#define UNITY_DOTS_INSTANCING_TYPESPEC_float4x4 F64 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int I4 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int1 I4 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int2 I8 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int3 I12 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int4 I16 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int1x2 I8 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int2x2 I16 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int3x2 I24 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int4x2 I32 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int1x3 I12 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int2x3 I24 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int3x3 I36 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int4x3 I48 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int1x4 I16 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int2x4 I32 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int3x4 I48 +#define UNITY_DOTS_INSTANCING_TYPESPEC_int4x4 I64 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint U4 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint1 U4 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint2 U8 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint3 U12 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint4 U16 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint1x2 U8 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint2x2 U16 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint3x2 U24 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint4x2 U32 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint1x3 U12 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint2x3 U24 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint3x3 U36 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint4x3 U48 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint1x4 U16 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint2x4 U32 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint3x4 U48 +#define UNITY_DOTS_INSTANCING_TYPESPEC_uint4x4 U64 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half H2 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half1 H2 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half2 H4 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half3 H6 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half4 H8 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half1x2 H4 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half2x2 H8 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half3x2 H12 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half4x2 H16 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half1x3 H6 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half2x3 H12 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half3x3 H18 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half4x3 H24 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half1x4 H8 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half2x4 H16 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half3x4 H24 +#define UNITY_DOTS_INSTANCING_TYPESPEC_half4x4 H32 + +#define UNITY_DOTS_INSTANCING_CONCAT2(a, b) a ## b +#define UNITY_DOTS_INSTANCING_CONCAT4(a, b, c, d) a ## b ## c ## d +#define UNITY_DOTS_INSTANCING_CONCAT_WITHOUT_METADATA(metadata_prefix, typespec, metadata_underscore_var) UNITY_DOTS_INSTANCING_CONCAT4(metadata_prefix, typespec, _, metadata_underscore_var) +#define UNITY_DOTS_INSTANCING_CONCAT_WITH_METADATA(metadata_prefix, typespec, name) UNITY_DOTS_INSTANCING_CONCAT4(metadata_prefix, typespec, _Metadata_, name) + +// Metadata constants for properties have the following name format: +// unity_DOTSInstancing__Metadata_ +// where +// is a single character element type specifier (e.g. F for float4x4) +// F = float, I = int, U = uint, H = half +// is the total size of the property in bytes (e.g. 64 for float4x4) +// is the name of the property +#define UNITY_DOTS_INSTANCED_METADATA_NAME(type, name) UNITY_DOTS_INSTANCING_CONCAT_WITH_METADATA(unity_DOTSInstancing_, UNITY_DOTS_INSTANCING_CONCAT2(UNITY_DOTS_INSTANCING_TYPESPEC_, type), name) +#define UNITY_DOTS_INSTANCED_METADATA_NAME_FROM_MACRO(type, metadata_underscore_var) UNITY_DOTS_INSTANCING_CONCAT_WITHOUT_METADATA(unity_DOTSInstancing_, UNITY_DOTS_INSTANCING_CONCAT2(UNITY_DOTS_INSTANCING_TYPESPEC_, type), metadata_underscore_var) + +#define UNITY_DOTS_INSTANCING_START(name) cbuffer UnityDOTSInstancing_##name { +#define UNITY_DOTS_INSTANCING_END(name) } +#define UNITY_DOTS_INSTANCED_PROP(type, name) uint UNITY_DOTS_INSTANCED_METADATA_NAME(type, name); + +// There is a separate FROM_MACRO variant to be used in macros of the form +// #define UNITY_DOTS_INSTANCED_METADATA_NAME_FROM_MACRO(float4, Metadata_) +// These kinds of macros can be used to have shader code load constants from DOTS instancing +// as if they were normal constants. +// The reason for having the FROM_MACRO variant is that the fxc shader preprocessor is buggy, +// and refuses to expand macros correctly if the macro body contains the macro itself. +// The correct behavior would be for the macro name to appear in the expansion as is (i.e. no recursion), +// but fxc's preprocessor completely breaks down in this situation. + +#define UNITY_ACCESS_DOTS_INSTANCED_PROP(type, var) LoadDOTSInstancedData_##type(UNITY_DOTS_INSTANCED_METADATA_NAME(type, var)) +#define UNITY_ACCESS_DOTS_INSTANCED_PROP_FROM_MACRO(type, metadata_underscore_var) LoadDOTSInstancedData_##type(UNITY_DOTS_INSTANCED_METADATA_NAME_FROM_MACRO(type, metadata_underscore_var)) +#define UNITY_ACCESS_DOTS_AND_TRADITIONAL_INSTANCED_PROP(type, arr, var) LoadDOTSInstancedData_##type(UNITY_DOTS_INSTANCED_METADATA_NAME(type, var)) + +// TODO: Shader feature level to compute only +ByteAddressBuffer unity_DOTSInstanceData; + + +// The data has to be wrapped inside a struct, otherwise the instancing code path +// on some platforms does not trigger. +struct DOTSVisibleData +{ + uint4 VisibleData; +}; + +// The name of this cbuffer has to start with "UnityInstancing" and a struct so it's +// detected as an "instancing cbuffer" by some platforms that use string matching +// to detect this. +CBUFFER_START(UnityInstancingDOTS_InstanceVisibility) + DOTSVisibleData unity_DOTSVisibleInstances[UNITY_INSTANCED_ARRAY_SIZE]; +CBUFFER_END + +uint GetDOTSInstanceIndex() +{ + return unity_DOTSVisibleInstances[unity_InstanceID].VisibleData.x; +} + +uint ComputeDOTSInstanceDataAddress(uint metadata, uint stride) +{ + uint isOverridden = metadata & 0x80000000; + uint baseAddress = metadata & 0x7fffffff; + uint offset = isOverridden + ? (GetDOTSInstanceIndex() * stride) + : 0; + return baseAddress + offset; +} + +#define DEFINE_DOTS_LOAD_INSTANCE_SCALAR(type, conv, sizeof_type) \ +type LoadDOTSInstancedData_##type(uint metadata) \ +{ \ + uint address = ComputeDOTSInstanceDataAddress(metadata, sizeof_type); \ + return conv(unity_DOTSInstanceData.Load(address)); \ +} + +#define DEFINE_DOTS_LOAD_INSTANCE_VECTOR(type, width, conv, sizeof_type) \ +type##width LoadDOTSInstancedData_##type##width(uint metadata) \ +{ \ + uint address = ComputeDOTSInstanceDataAddress(metadata, sizeof_type * width); \ + return conv(unity_DOTSInstanceData.Load##width(address)); \ +} + +DEFINE_DOTS_LOAD_INSTANCE_SCALAR(float, asfloat, 4) +DEFINE_DOTS_LOAD_INSTANCE_SCALAR(int, int, 4) +DEFINE_DOTS_LOAD_INSTANCE_SCALAR(uint, uint, 4) +DEFINE_DOTS_LOAD_INSTANCE_SCALAR(half, half, 2) + +DEFINE_DOTS_LOAD_INSTANCE_VECTOR(float, 2, asfloat, 4) +DEFINE_DOTS_LOAD_INSTANCE_VECTOR(float, 3, asfloat, 4) +DEFINE_DOTS_LOAD_INSTANCE_VECTOR(float, 4, asfloat, 4) +DEFINE_DOTS_LOAD_INSTANCE_VECTOR(int, 2, int2, 4) +DEFINE_DOTS_LOAD_INSTANCE_VECTOR(int, 3, int3, 4) +DEFINE_DOTS_LOAD_INSTANCE_VECTOR(int, 4, int4, 4) +DEFINE_DOTS_LOAD_INSTANCE_VECTOR(uint, 2, uint2, 4) +DEFINE_DOTS_LOAD_INSTANCE_VECTOR(uint, 3, uint3, 4) +DEFINE_DOTS_LOAD_INSTANCE_VECTOR(uint, 4, uint4, 4) +DEFINE_DOTS_LOAD_INSTANCE_VECTOR(half, 2, half2, 2) +DEFINE_DOTS_LOAD_INSTANCE_VECTOR(half, 3, half3, 2) +DEFINE_DOTS_LOAD_INSTANCE_VECTOR(half, 4, half4, 2) + +// TODO: Other matrix sizes +float4x4 LoadDOTSInstancedData_float4x4(uint metadata) +{ + uint address = ComputeDOTSInstanceDataAddress(metadata, 4 * 16); + float4 p1 = asfloat(unity_DOTSInstanceData.Load4(address + 0 * 16)); + float4 p2 = asfloat(unity_DOTSInstanceData.Load4(address + 1 * 16)); + float4 p3 = asfloat(unity_DOTSInstanceData.Load4(address + 2 * 16)); + float4 p4 = asfloat(unity_DOTSInstanceData.Load4(address + 3 * 16)); + return float4x4( + p1.x, p2.x, p3.x, p4.x, + p1.y, p2.y, p3.y, p4.y, + p1.z, p2.z, p3.z, p4.z, + p1.w, p2.w, p3.w, p4.w); +} +float4x4 LoadDOTSInstancedData(float4x4 dummy, uint metadata) { return LoadDOTSInstancedData_float4x4(metadata); } + +float4x4 LoadDOTSInstancedData_float4x4_from_float3x4(uint metadata) +{ + uint address = ComputeDOTSInstanceDataAddress(metadata, 3 * 16); + float4 p1 = asfloat(unity_DOTSInstanceData.Load4(address + 0 * 16)); + float4 p2 = asfloat(unity_DOTSInstanceData.Load4(address + 1 * 16)); + float4 p3 = asfloat(unity_DOTSInstanceData.Load4(address + 2 * 16)); + + return float4x4( + p1.x, p1.w, p2.z, p3.y, + p1.y, p2.x, p2.w, p3.z, + p1.z, p2.y, p3.x, p3.w, + 0.0, 0.0, 0.0, 1.0 + ); +} + +float2x4 LoadDOTSInstancedData_float2x4(uint metadata) +{ + uint address = ComputeDOTSInstanceDataAddress(metadata, 4 * 8); + return float2x4( + asfloat(unity_DOTSInstanceData.Load4(address + 0 * 8)), + asfloat(unity_DOTSInstanceData.Load4(address + 1 * 8))); +} +float2x4 LoadDOTSInstancedData(float2x4 dummy, uint metadata) { return LoadDOTSInstancedData_float2x4(metadata); } + +#undef DEFINE_DOTS_LOAD_INSTANCE_SCALAR +#undef DEFINE_DOTS_LOAD_INSTANCE_VECTOR + +#endif // UNITY_DOTS_INSTANCING_ENABLED + +#endif // UNITY_DOTS_INSTANCING_INCLUDED + diff --git a/ShaderLibrary/UnityDOTSInstancing.hlsl.meta b/ShaderLibrary/UnityDOTSInstancing.hlsl.meta new file mode 100644 index 0000000..3babd57 --- /dev/null +++ b/ShaderLibrary/UnityDOTSInstancing.hlsl.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 89aebf2868d41e34abc02aae1d41b4cd +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/ShaderLibrary/UnityInstancing.hlsl b/ShaderLibrary/UnityInstancing.hlsl index efb822a..e74b1db 100644 --- a/ShaderLibrary/UnityInstancing.hlsl +++ b/ShaderLibrary/UnityInstancing.hlsl @@ -34,16 +34,23 @@ #if defined(UNITY_SUPPORT_INSTANCING) && defined(PROCEDURAL_INSTANCING_ON) #define UNITY_PROCEDURAL_INSTANCING_ENABLED #endif +#if defined(UNITY_SUPPORT_INSTANCING) && defined(DOTS_INSTANCING_ON) + #define UNITY_DOTS_INSTANCING_ENABLED +#endif #if defined(UNITY_SUPPORT_STEREO_INSTANCING) && defined(STEREO_INSTANCING_ON) #define UNITY_STEREO_INSTANCING_ENABLED #endif -#if defined(UNITY_INSTANCING_ENABLED) || defined(UNITY_PROCEDURAL_INSTANCING_ENABLED) || defined(UNITY_STEREO_INSTANCING_ENABLED) +#if defined(UNITY_INSTANCING_ENABLED) || defined(UNITY_PROCEDURAL_INSTANCING_ENABLED) || defined(UNITY_DOTS_INSTANCING_ENABLED) || defined(UNITY_STEREO_INSTANCING_ENABLED) #define UNITY_ANY_INSTANCING_ENABLED 1 #else #define UNITY_ANY_INSTANCING_ENABLED 0 #endif +#if defined(DOTS_INSTANCING_ON) && (SHADER_TARGET < 45) +#error The DOTS_INSTANCING_ON keyword requires shader model 4.5 or greater ("#pragma target 4.5" or greater). +#endif + #if defined(SHADER_API_GLES3) || defined(SHADER_API_GLCORE) || defined(SHADER_API_METAL) || defined(SHADER_API_VULKAN) // These platforms have constant buffers disabled normally, but not here (see CBUFFER_START/CBUFFER_END in HLSLSupport.cginc). #define UNITY_INSTANCING_CBUFFER_SCOPE_BEGIN(name) cbuffer name { @@ -57,7 +64,7 @@ // basic instancing setups // - UNITY_VERTEX_INPUT_INSTANCE_ID Declare instance ID field in vertex shader input / output struct. // - UNITY_GET_INSTANCE_ID (Internal) Get the instance ID from input struct. -#if defined(UNITY_INSTANCING_ENABLED) || defined(UNITY_PROCEDURAL_INSTANCING_ENABLED) || defined(UNITY_STEREO_INSTANCING_ENABLED) +#if UNITY_ANY_INSTANCING_ENABLED // A global instance ID variable that functions can directly access. static uint unity_InstanceID; @@ -107,12 +114,12 @@ #define DEFAULT_UNITY_VERTEX_OUTPUT_STEREO uint stereoTargetEyeIndexAsBlendIdx0 : BLENDINDICES0; #define DEFAULT_UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output) output.stereoTargetEyeIndexAsBlendIdx0 = unity_StereoEyeIndex; #define DEFAULT_UNITY_TRANSFER_VERTEX_OUTPUT_STEREO(input, output) output.stereoTargetEyeIndexAsBlendIdx0 = input.stereoTargetEyeIndexAsBlendIdx0; - #define DEFAULT_UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input) unity_StereoEyeIndex = input.stereoTargetEyeIndexAsBlendIdx0; + #define DEFAULT_UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input) unity_StereoEyeIndex = input.stereoTargetEyeIndexAsBlendIdx0; #else #define DEFAULT_UNITY_VERTEX_OUTPUT_STEREO uint stereoTargetEyeIndexAsRTArrayIdx : SV_RenderTargetArrayIndex; uint stereoTargetEyeIndexAsBlendIdx0 : BLENDINDICES0; #define DEFAULT_UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output) output.stereoTargetEyeIndexAsRTArrayIdx = unity_StereoEyeIndex; output.stereoTargetEyeIndexAsBlendIdx0 = unity_StereoEyeIndex; #define DEFAULT_UNITY_TRANSFER_VERTEX_OUTPUT_STEREO(input, output) output.stereoTargetEyeIndexAsBlendIdx0 = input.stereoTargetEyeIndexAsBlendIdx0; - #define DEFAULT_UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input) unity_StereoEyeIndex = input.stereoTargetEyeIndexAsBlendIdx0; + #define DEFAULT_UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input) unity_StereoEyeIndex = input.stereoTargetEyeIndexAsBlendIdx0; #endif #else #define DEFAULT_UNITY_VERTEX_OUTPUT_STEREO uint stereoTargetEyeIndexAsRTArrayIdx : SV_RenderTargetArrayIndex; @@ -158,7 +165,7 @@ // Also procedural function is called to setup instance data. // - UNITY_TRANSFER_INSTANCE_ID Copy instance ID from input struct to output struct. Used in vertex shader. -#if defined(UNITY_INSTANCING_ENABLED) || defined(UNITY_PROCEDURAL_INSTANCING_ENABLED) || defined(UNITY_STEREO_INSTANCING_ENABLED) +#if UNITY_ANY_INSTANCING_ENABLED void UnitySetupInstanceID(uint inputInstanceID) { #ifdef UNITY_STEREO_INSTANCING_ENABLED @@ -209,7 +216,7 @@ //////////////////////////////////////////////////////// // instanced property arrays -#if defined(UNITY_INSTANCING_ENABLED) +#if defined(UNITY_INSTANCING_ENABLED) || defined(UNITY_DOTS_INSTANCING_ENABLED) #ifdef UNITY_FORCE_MAX_INSTANCE_COUNT #define UNITY_INSTANCED_ARRAY_SIZE UNITY_FORCE_MAX_INSTANCE_COUNT @@ -225,11 +232,29 @@ #endif #endif +#if defined(UNITY_DOTS_INSTANCING_ENABLED) + #define UNITY_INSTANCING_BUFFER_START(buf) UNITY_INSTANCING_CBUFFER_SCOPE_BEGIN(UnityInstancing_##buf) + #define UNITY_INSTANCING_BUFFER_END(arr) UNITY_INSTANCING_CBUFFER_SCOPE_END + #define UNITY_DEFINE_INSTANCED_PROP(type, var) type var; + #define UNITY_ACCESS_INSTANCED_PROP(arr, var) var + + #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityDOTSInstancing.hlsl" + +#else #define UNITY_INSTANCING_BUFFER_START(buf) UNITY_INSTANCING_CBUFFER_SCOPE_BEGIN(UnityInstancing_##buf) struct { #define UNITY_INSTANCING_BUFFER_END(arr) } arr##Array[UNITY_INSTANCED_ARRAY_SIZE]; UNITY_INSTANCING_CBUFFER_SCOPE_END #define UNITY_DEFINE_INSTANCED_PROP(type, var) type var; #define UNITY_ACCESS_INSTANCED_PROP(arr, var) arr##Array[unity_InstanceID].var + #define UNITY_DOTS_INSTANCING_START(name) + #define UNITY_DOTS_INSTANCING_END(name) + #define UNITY_DOTS_INSTANCED_PROP(type, name) + + #define UNITY_ACCESS_DOTS_INSTANCED_PROP(type, var) var + #define UNITY_ACCESS_DOTS_INSTANCED_PROP_FROM_MACRO(type, metadata_underscore_var) This_macro_cannot_be_called_without_UNITY_DOTS_INSTANCING_ENABLED + #define UNITY_ACCESS_DOTS_AND_TRADITIONAL_INSTANCED_PROP(type, arr, var) UNITY_ACCESS_INSTANCED_PROP(arr, var) +#endif + // Put worldToObject array to a separate CB if UNITY_ASSUME_UNIFORM_SCALING is defined. Most of the time it will not be used. #ifdef UNITY_ASSUME_UNIFORM_SCALING #define UNITY_WORLDTOOBJECTARRAY_CB 1 @@ -263,6 +288,7 @@ #endif #endif + #if !defined(UNITY_DOTS_INSTANCING_ENABLED) UNITY_INSTANCING_BUFFER_START(PerDraw0) #ifndef UNITY_DONT_INSTANCE_OBJECT_MATRICES UNITY_DEFINE_INSTANCED_PROP(float4x4, unity_ObjectToWorldArray) @@ -278,8 +304,10 @@ UNITY_DEFINE_INSTANCED_PROP(float, unity_RenderingLayerArray) #define unity_RenderingLayer UNITY_ACCESS_INSTANCED_PROP(unity_Builtins0, unity_RenderingLayerArray).xxxx #endif - #if defined(SHADER_GRAPH_GENERATED) - DOTS_CUSTOM_ADDITIONAL_MATERIAL_VARS + + // TODO: Hybrid V1 compatibility, remove once Hybrid V1 is removed + #if defined(UNITY_HYBRID_V1_INSTANCING_ENABLED) && defined(HYBRID_V1_CUSTOM_ADDITIONAL_MATERIAL_VARS) + HYBRID_V1_CUSTOM_ADDITIONAL_MATERIAL_VARS #endif UNITY_INSTANCING_BUFFER_END(unity_Builtins0) @@ -300,6 +328,7 @@ UNITY_INSTANCING_BUFFER_START(PerDraw2) #ifdef UNITY_USE_LIGHTMAPST_ARRAY UNITY_DEFINE_INSTANCED_PROP(float4, unity_LightmapSTArray) + UNITY_DEFINE_INSTANCED_PROP(float4, unity_LightmapIndexArray) #define unity_LightmapST UNITY_ACCESS_INSTANCED_PROP(unity_Builtins2, unity_LightmapSTArray) #endif #ifdef UNITY_USE_DYNAMICLIGHTMAPST_ARRAY @@ -327,21 +356,44 @@ #define unity_ProbesOcclusion UNITY_ACCESS_INSTANCED_PROP(unity_Builtins2, unity_ProbesOcclusionArray) #endif UNITY_INSTANCING_BUFFER_END(unity_Builtins2) + #endif + + // TODO: What about UNITY_DONT_INSTANCE_OBJECT_MATRICES for DOTS? + #if defined(UNITY_DOTS_INSTANCING_ENABLED) + #undef UNITY_MATRIX_M + #undef UNITY_MATRIX_I_M + #ifdef MODIFY_MATRIX_FOR_CAMERA_RELATIVE_RENDERING + #define UNITY_MATRIX_M ApplyCameraTranslationToMatrix(LoadDOTSInstancedData_float4x4_from_float3x4(UNITY_DOTS_INSTANCED_METADATA_NAME_FROM_MACRO(float3x4, Metadata_unity_ObjectToWorld))) + #define UNITY_MATRIX_I_M ApplyCameraTranslationToInverseMatrix(LoadDOTSInstancedData_float4x4_from_float3x4(UNITY_DOTS_INSTANCED_METADATA_NAME_FROM_MACRO(float3x4, Metadata_unity_WorldToObject))) + #else + #define UNITY_MATRIX_M LoadDOTSInstancedData_float4x4_from_float3x4(UNITY_DOTS_INSTANCED_METADATA_NAME_FROM_MACRO(float3x4, Metadata_unity_ObjectToWorld)) + #define UNITY_MATRIX_I_M LoadDOTSInstancedData_float4x4_from_float3x4(UNITY_DOTS_INSTANCED_METADATA_NAME_FROM_MACRO(float3x4, Metadata_unity_WorldToObject)) + #endif + #else #ifndef UNITY_DONT_INSTANCE_OBJECT_MATRICES #undef UNITY_MATRIX_M #undef UNITY_MATRIX_I_M - #define MERGE_UNITY_BUILTINS_INDEX(X) unity_Builtins##X - #define CALL_MERGE_UNITY_BUILTINS_INDEX(X) MERGE_UNITY_BUILTINS_INDEX(X) + + // Use #if instead of preprocessor concatenation to avoid really hard to debug + // preprocessing issues in some cases. + #if UNITY_WORLDTOOBJECTARRAY_CB == 0 + #define UNITY_BUILTINS_WITH_WORLDTOOBJECTARRAY unity_Builtins0 + #else + #define UNITY_BUILTINS_WITH_WORLDTOOBJECTARRAY unity_Builtins1 + #endif + #ifdef MODIFY_MATRIX_FOR_CAMERA_RELATIVE_RENDERING #define UNITY_MATRIX_M ApplyCameraTranslationToMatrix(UNITY_ACCESS_INSTANCED_PROP(unity_Builtins0, unity_ObjectToWorldArray)) - #define UNITY_MATRIX_I_M ApplyCameraTranslationToInverseMatrix(UNITY_ACCESS_INSTANCED_PROP(CALL_MERGE_UNITY_BUILTINS_INDEX(UNITY_WORLDTOOBJECTARRAY_CB), unity_WorldToObjectArray)) + #define UNITY_MATRIX_I_M ApplyCameraTranslationToInverseMatrix(UNITY_ACCESS_INSTANCED_PROP(UNITY_BUILTINS_WITH_WORLDTOOBJECTARRAY, unity_WorldToObjectArray)) #else #define UNITY_MATRIX_M UNITY_ACCESS_INSTANCED_PROP(unity_Builtins0, unity_ObjectToWorldArray) - #define UNITY_MATRIX_I_M UNITY_ACCESS_INSTANCED_PROP(CALL_MERGE_UNITY_BUILTINS_INDEX(UNITY_WORLDTOOBJECTARRAY_CB), unity_WorldToObjectArray) + #define UNITY_MATRIX_I_M UNITY_ACCESS_INSTANCED_PROP(UNITY_BUILTINS_WITH_WORLDTOOBJECTARRAY, unity_WorldToObjectArray) #endif #endif + #endif + #else // UNITY_INSTANCING_ENABLED // in procedural mode we don't need cbuffer, and properties are not uniforms diff --git a/ShaderLibrary/Version.hlsl b/ShaderLibrary/Version.hlsl index 3188d7e..143af9e 100644 --- a/ShaderLibrary/Version.hlsl +++ b/ShaderLibrary/Version.hlsl @@ -1,5 +1,5 @@ -#define SHADER_LIBRARY_VERSION_MAJOR 8 -#define SHADER_LIBRARY_VERSION_MINOR 2 +#define SHADER_LIBRARY_VERSION_MAJOR 10 +#define SHADER_LIBRARY_VERSION_MINOR 1 #define VERSION_GREATER_EQUAL(major, minor) ((SHADER_LIBRARY_VERSION_MAJOR > major) || ((SHADER_LIBRARY_VERSION_MAJOR == major) && (SHADER_LIBRARY_VERSION_MINOR >= minor))) #define VERSION_LOWER(major, minor) ((SHADER_LIBRARY_VERSION_MAJOR < major) || ((SHADER_LIBRARY_VERSION_MAJOR == major) && (SHADER_LIBRARY_VERSION_MINOR < minor))) diff --git a/ShaderLibrary/VirtualTexturing.hlsl b/ShaderLibrary/VirtualTexturing.hlsl new file mode 100644 index 0000000..50b05dd --- /dev/null +++ b/ShaderLibrary/VirtualTexturing.hlsl @@ -0,0 +1,211 @@ +#include "GraniteShaderLibBase.hlsl" + +#define VtAddressMode_Wrap 0 +#define VtAddressMode_Clamp 1 +#define VtAddressMode_Udim 2 + +#define VtFilter_Anisotropic 0 + +#define VtLevel_Automatic 0 +#define VtLevel_Lod 1 +#define VtLevel_Bias 2 +#define VtLevel_Derivatives 3 + +#define VtUvSpace_Regular 0 +#define VtUvSpace_PreTransformed 1 + +#define VtSampleQuality_Low 0 +#define VtSampleQuality_High 1 + +struct VtInputParameters +{ + float2 uv; + float lodOrOffset; + float2 dx; + float2 dy; + int addressMode; + int filterMode; + int levelMode; + int uvMode; + int sampleQuality; +}; + +int VirtualTexturingLookup( + in GraniteConstantBuffers grCB, + in GraniteTranslationTexture translationTable, + in VtInputParameters input, + out GraniteLookupData graniteLookupData, + out float4 resolveResult +) +{ + GraniteStreamingTextureConstantBuffer grSTCB = grCB.streamingTextureBuffer; + GraniteTilesetConstantBuffer tsCB = grCB.tilesetBuffer; + + float2 texCoord = input.uv; + float2 dx; + float2 dy; + float mipLevel; //interger + + if (input.levelMode == VtLevel_Automatic) + { + dx = ddx(texCoord); + dy = ddy(texCoord); + } + else if (input.levelMode == VtLevel_Bias) + { + // We can't simply add the bias after the mip-calculation since the derivatives + // are also used when sampling the cache so make sure we apply bias by scaling derivatives + if ( input.sampleQuality == VtSampleQuality_High ) + { + float offsetPow2 = pow(2.0f, input.lodOrOffset); + dx = ddx(texCoord) * offsetPow2; + dy = ddy(texCoord) * offsetPow2; + } + // In low qauality we don't care about cache derivatives and will add the bias later + else + { + dx = ddx(texCoord); + dy = ddy(texCoord); + } + } + else if (input.levelMode == VtLevel_Derivatives) + { + dx = input.dx; + dy = input.dy; + } + else /*input.levelMode == VtLevel_Lod*/ + { + //gra_TrilinearOffset ensures we do round-nearest for no-trilinear and + //round-floor for trilinear. + float clampedLevel = clamp(input.lodOrOffset + gra_TrilinearOffset, 0.0f, gra_NumLevels); + mipLevel = floor(clampedLevel); + dx = float2(frac(clampedLevel), 0.0f); // trilinear blend ratio + dy = float2(0.0f,0.0f); + } + + // Transform the derivatives to atlas space if needed + if (input.uvMode == VtUvSpace_Regular && input.levelMode != VtLevel_Lod) + { + dx = gra_Transform.zw * dx; + dy = gra_Transform.zw * dy; + } + + if (input.levelMode != VtLevel_Lod) + { + mipLevel = GranitePrivate_CalcMiplevelAnisotropic(grCB.tilesetBuffer, grCB.streamingTextureBuffer, dx, dy); + + // Simply add it here derivatives are wrong from this point onwards but not used anymore + if ( input.sampleQuality == VtSampleQuality_Low && input.levelMode == VtLevel_Bias) + { + mipLevel += input.lodOrOffset; + // GranitePrivate_CalcMiplevelAnisotropic will already clamp between 0 gra_NumLevels + // But we need to do it again here. The alternative is modifying dx,dy before passing to + // GranitePrivate_CalcMiplevelAnisotropic adding a pow2 + 4 fmuls so probably + // the exra clamp is more appropriate here. + mipLevel = clamp(mipLevel, 0.0f, gra_NumLevels); + } + + mipLevel = floor(mipLevel + 0.5f); //round nearest + } + + // Apply clamp/wrap mode if needed and transform into atlas space + // If the user passes in pre-transformed texture coords clamping and wrapping should be handled by the user + if (input.uvMode == VtUvSpace_Regular) + { + if (input.addressMode == VtAddressMode_Wrap) + { + texCoord = frac(input.uv); + } + else if (input.addressMode == VtAddressMode_Clamp) + { + float2 epsilon2 = float2(gra_AssetWidthRcp, gra_AssetHeightRcp); + texCoord = clamp(input.uv, epsilon2, float2(1,1) - epsilon2); + } + else if (input.addressMode == VtAddressMode_Udim) + { + // not modified (i.e outside of the 0-1 range, atlas transform below will take care of it) + texCoord = input.uv; + } + + texCoord = Granite_Transform(gra_StreamingTextureCB, texCoord); + } + + // calculate resolver data + float2 level0NumTiles = float2(gra_Level0NumTilesX, gra_Level0NumTilesX*gra_NumTilesYScale); + float2 virtualTilesUv = floor(texCoord * level0NumTiles * pow(0.5, mipLevel)); + resolveResult = GranitePrivate_MakeResolveOutput(tsCB, virtualTilesUv, mipLevel); + + float4 translationTableData; + if (input.levelMode != VtLevel_Lod) + { + // Look up the physical page indexes and the number of pages on the mipmap + // level of the page in the translation texture + // Note: this is equal for both anisotropic and linear sampling + // We could use a sample bias here for 'auto' mip level detection +#if (GRA_LOAD_INSTR==0) + translationTableData = GranitePrivate_SampleLevel_Translation(translationTable, texCoord, mipLevel); +#else + translationTableData = GranitePrivate_Load(translationTable, gra_Int3(virtualTilesUv, mipLevel)); +#endif + } + else + { + // Look up the physical page indexes and the number of pages on the mipmap + // level of the page in the translation texture + // Note: this is equal for both anisotropic and linear sampling + // We could use a sample bias here for 'auto' mip level detection +#if (GRA_LOAD_INSTR==0) + translationTableData = GranitePrivate_SampleLevel_Translation(translationTable, texCoord, mipLevel); +#else + translationTableData = GranitePrivate_Load(translationTable, gra_Int3(virtualTilesUv, mipLevel)); +#endif + } + + graniteLookupData.translationTableData = translationTableData; + graniteLookupData.textureCoordinates = texCoord; + graniteLookupData.dX = dx; + graniteLookupData.dY = dy; + + return 1; +} + +int VirtualTexturingSample( + in GraniteTilesetConstantBuffer tsCB, + in GraniteLookupData graniteLookupData, + in GraniteCacheTexture cacheTexture, + in int layer, + in int levelMode, + in int quality, + out float4 result) +{ + // Convert from pixels to [0-1] and look up in the physical page texture + float2 deltaScale; + float3 cacheCoord = GranitePrivate_TranslateCoord(tsCB, graniteLookupData.textureCoordinates, graniteLookupData.translationTableData, layer, deltaScale); + + if ( levelMode != VtLevel_Lod ) + { + if ( quality == VtSampleQuality_Low ) + { + // This leads to small artefacts at tile borders but is generally not noticable unless the texture + // is greatly magnified + result = GranitePrivate_SampleArray(cacheTexture, cacheCoord); + } + else /* quality == VtSampleQuality_High */ + { + deltaScale *= gra_LodBiasPow2; + + // Calculate the delta scale this works by first converting the [0-1] texcoord deltas to + // pixel deltas on the current mip level, then dividing by the cache size to convert to [0-1] cache deltas + float2 sampDeltaX = graniteLookupData.dX*deltaScale; + float2 sampDeltaY = graniteLookupData.dY*deltaScale; + + result = GranitePrivate_SampleGradArray(cacheTexture, cacheCoord, sampDeltaX, sampDeltaY); + } + } + else + { + result = GranitePrivate_SampleLevelArray(cacheTexture, cacheCoord, graniteLookupData.dX.x); + } + + return 1; +} diff --git a/ShaderLibrary/VirtualTexturing.hlsl.meta b/ShaderLibrary/VirtualTexturing.hlsl.meta new file mode 100644 index 0000000..53e40b2 --- /dev/null +++ b/ShaderLibrary/VirtualTexturing.hlsl.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 6f22d4e29906d3c47892e0c92874b752 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Editor/RenderGraphTests.cs b/Tests/Editor/RenderGraphTests.cs new file mode 100644 index 0000000..cd6699f --- /dev/null +++ b/Tests/Editor/RenderGraphTests.cs @@ -0,0 +1,553 @@ +using NUnit.Framework; +using UnityEngine.Experimental.Rendering; +using UnityEngine.Experimental.Rendering.RenderGraphModule; + +namespace UnityEngine.Rendering.Tests +{ + class RenderGraphTests + { + RenderGraph m_RenderGraph = new RenderGraph(); + + [SetUp] + public void SetupRenderGraph() + { + m_RenderGraph.ClearCompiledGraph(); + } + + class RenderGraphTestPassData + { + public TextureHandle[] textures = new TextureHandle[8]; + public ComputeBufferHandle[] buffers = new ComputeBufferHandle[8]; + } + + // Final output (back buffer) of render graph needs to be explicitly imported in order to know that the chain of dependency should not be culled. + [Test] + public void WriteToBackBufferNotCulled() + { + using (var builder = m_RenderGraph.AddRenderPass("TestPass0", out var passData)) + { + builder.WriteTexture(m_RenderGraph.ImportBackbuffer(0)); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + m_RenderGraph.CompileRenderGraph(); + + var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + Assert.AreEqual(1, compiledPasses.size); + Assert.AreEqual(false, compiledPasses[0].culled); + } + + // If no back buffer is ever written to, everything should be culled. + [Test] + public void NoWriteToBackBufferCulled() + { + using (var builder = m_RenderGraph.AddRenderPass("TestPass0", out var passData)) + { + builder.WriteTexture(m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm })); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + m_RenderGraph.CompileRenderGraph(); + + var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + Assert.AreEqual(1, compiledPasses.size); + Assert.AreEqual(true, compiledPasses[0].culled); + } + + // Writing to imported resource is considered as a side effect so passes should not be culled. + [Test] + public void WriteToImportedTextureNotCulled() + { + using (var builder = m_RenderGraph.AddRenderPass("TestPass0", out var passData)) + { + builder.WriteTexture(m_RenderGraph.ImportTexture(null)); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + m_RenderGraph.CompileRenderGraph(); + + var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + Assert.AreEqual(1, compiledPasses.size); + Assert.AreEqual(false, compiledPasses[0].culled); + } + + [Test] + public void WriteToImportedComputeBufferNotCulled() + { + using (var builder = m_RenderGraph.AddRenderPass("TestPass0", out var passData)) + { + builder.WriteComputeBuffer(m_RenderGraph.ImportComputeBuffer(null)); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + m_RenderGraph.CompileRenderGraph(); + + var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + Assert.AreEqual(1, compiledPasses.size); + Assert.AreEqual(false, compiledPasses[0].culled); + } + + // TODO RENDERGRAPH : Temporarily removed. See RenderGraph.cs pass culling + //// A pass not writing to anything is useless and should be culled. + //[Test] + //public void CullPassWithNoProduct() + //{ + // // This pass reads an input but does not produce anything (no writes) so it should be culled. + // TextureHandle texture = m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm }); + // using (var builder = m_RenderGraph.AddRenderPass("TestPass0", out var passData)) + // { + // builder.ReadTexture(texture); + // builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + // } + + // m_RenderGraph.CompileRenderGraph(); + + // var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + // Assert.AreEqual(1, compiledPasses.size); + // Assert.AreEqual(true, compiledPasses[0].culled); + //} + + //// A series of passes with no final product should be culled. + //[Test] + //public void CullPassWithTextureDependenciesAndNoProduct() + //{ + // // First pass produces an output that is read by second pass. + // // Second pass does not produce anything so it should be culled as well as all its unused dependencies. + // TextureHandle texture; + // using (var builder = m_RenderGraph.AddRenderPass("TestPass0", out var passData)) + // { + // texture = builder.WriteTexture(m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm })); + // builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + // } + + // using (var builder = m_RenderGraph.AddRenderPass("TestPass1", out var passData)) + // { + // builder.ReadTexture(texture); + // builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + // } + + // m_RenderGraph.CompileRenderGraph(); + + // var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + // Assert.AreEqual(2, compiledPasses.size); + // Assert.AreEqual(true, compiledPasses[0].culled); + // Assert.AreEqual(true, compiledPasses[1].culled); + //} + + //// A series of passes with no final product should be culled. + //// Here first pass is not culled because Compute Buffer is imported. + //// TODO: Add test where compute buffer is created instead of imported once the API exists. + //[Test] + //public void CullPassWithBufferDependenciesAndNoProduct() + //{ + // // First pass produces an output that is read by second pass. + // // Second pass does not produce anything so it should be culled as well as all its unused dependencies. + // ComputeBufferHandle computeBuffer; + // using (var builder = m_RenderGraph.AddRenderPass("TestPass0", out var passData)) + // { + // computeBuffer = builder.WriteComputeBuffer(m_RenderGraph.ImportComputeBuffer(null)); + // builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + // } + + // using (var builder = m_RenderGraph.AddRenderPass("TestPass1", out var passData)) + // { + // builder.ReadComputeBuffer(computeBuffer); + // builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + // } + + // m_RenderGraph.CompileRenderGraph(); + + // var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + // Assert.AreEqual(2, compiledPasses.size); + // Assert.AreEqual(false, compiledPasses[0].culled); // Not culled because writing to an imported resource is a side effect. + // Assert.AreEqual(true, compiledPasses[1].culled); + //} + + [Test] + public void PassWriteResourcePartialNotReadAfterNotCulled() + { + // If a pass writes to a resource that is not unused globally by the graph but not read ever AFTER the pass then the pass should be culled unless it writes to another used resource. + TextureHandle texture0; + using (var builder = m_RenderGraph.AddRenderPass("TestPass0", out var passData)) + { + texture0 = builder.WriteTexture(m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm })); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + TextureHandle texture1; + using (var builder = m_RenderGraph.AddRenderPass("TestPass1", out var passData)) + { + builder.ReadTexture(texture0); + texture1 = builder.WriteTexture(m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm })); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + // This pass writes to texture0 which is used so will not be culled out. + // Since texture0 is never read after this pass, we should decrement refCount for this pass and potentially cull it. + // However, it also writes to texture1 which is used in the last pass so we mustn't cull it. + using (var builder = m_RenderGraph.AddRenderPass("TestPass2", out var passData)) + { + builder.WriteTexture(texture0); + builder.WriteTexture(texture1); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + using (var builder = m_RenderGraph.AddRenderPass("TestPass3", out var passData)) + { + builder.ReadTexture(texture1); + builder.WriteTexture(m_RenderGraph.ImportBackbuffer(0)); // Needed for the passes to not be culled + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + m_RenderGraph.CompileRenderGraph(); + + var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + Assert.AreEqual(4, compiledPasses.size); + Assert.AreEqual(false, compiledPasses[0].culled); + Assert.AreEqual(false, compiledPasses[1].culled); + Assert.AreEqual(false, compiledPasses[2].culled); + Assert.AreEqual(false, compiledPasses[3].culled); + } + + [Test] + public void PassDisallowCullingNotCulled() + { + // This pass does nothing so should be culled but we explicitly disallow it. + using (var builder = m_RenderGraph.AddRenderPass("TestPass0", out var passData)) + { + builder.AllowPassCulling(false); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + m_RenderGraph.CompileRenderGraph(); + + var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + Assert.AreEqual(1, compiledPasses.size); + Assert.AreEqual(false, compiledPasses[0].culled); + } + + // First pass produces two textures and second pass only read one of the two. Pass one should not be culled. + [Test] + public void PartialUnusedProductNotCulled() + { + TextureHandle texture; + using (var builder = m_RenderGraph.AddRenderPass("TestPass0", out var passData)) + { + texture = builder.WriteTexture(m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm })); + builder.WriteTexture(m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm })); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + using (var builder = m_RenderGraph.AddRenderPass("TestPass1", out var passData)) + { + builder.ReadTexture(texture); + builder.WriteTexture(m_RenderGraph.ImportBackbuffer(0)); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + m_RenderGraph.CompileRenderGraph(); + + var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + Assert.AreEqual(2, compiledPasses.size); + Assert.AreEqual(false, compiledPasses[0].culled); + Assert.AreEqual(false, compiledPasses[1].culled); + } + + // Simple cycle of create/release of a texture across multiple passes. + [Test] + public void SimpleCreateReleaseTexture() + { + TextureHandle texture; + using (var builder = m_RenderGraph.AddRenderPass("TestPass0", out var passData)) + { + texture = builder.WriteTexture(m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm })); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + // Add dummy passes + for (int i = 0; i < 2; ++i) + { + using (var builder = m_RenderGraph.AddRenderPass("TestPass1", out var passData)) + { + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + } + + using (var builder = m_RenderGraph.AddRenderPass("TestPass2", out var passData)) + { + builder.ReadTexture(texture); + builder.WriteTexture(m_RenderGraph.ImportBackbuffer(0)); // Needed for the passes to not be culled + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + m_RenderGraph.CompileRenderGraph(); + + var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + Assert.AreEqual(4, compiledPasses.size); + Assert.Contains(texture.handle.index, compiledPasses[0].resourceCreateList[(int)RenderGraphResourceType.Texture]); + Assert.Contains(texture.handle.index, compiledPasses[3].resourceReleaseList[(int)RenderGraphResourceType.Texture]); + } + + [Test] + public void UseTransientOutsidePassRaiseException() + { + Assert.Catch(() => + { + TextureHandle texture; + using (var builder = m_RenderGraph.AddRenderPass("TestPass0", out var passData)) + { + texture = builder.CreateTransientTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm }); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + using (var builder = m_RenderGraph.AddRenderPass("TestPass1", out var passData)) + { + builder.ReadTexture(texture); // This is illegal (transient resource was created in previous pass) + builder.WriteTexture(m_RenderGraph.ImportBackbuffer(0)); // Needed for the passes to not be culled + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + m_RenderGraph.CompileRenderGraph(); + }); + } + + [Test] + public void TransientCreateReleaseInSamePass() + { + TextureHandle texture; + using (var builder = m_RenderGraph.AddRenderPass("TestPass0", out var passData)) + { + texture = builder.CreateTransientTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm }); + builder.WriteTexture(m_RenderGraph.ImportBackbuffer(0)); // Needed for the passes to not be culled + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + m_RenderGraph.CompileRenderGraph(); + + var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + Assert.AreEqual(1, compiledPasses.size); + Assert.Contains(texture.handle.index, compiledPasses[0].resourceCreateList[(int)RenderGraphResourceType.Texture]); + Assert.Contains(texture.handle.index, compiledPasses[0].resourceReleaseList[(int)RenderGraphResourceType.Texture]); + } + + // Texture that should be released during an async pass should have their release delayed until the first pass that syncs with the compute pipe. + // Otherwise they may be reused by the graphics pipe even if the async pipe is not done executing. + [Test] + public void AsyncPassReleaseTextureOnGraphicsPipe() + { + TextureHandle texture0; + TextureHandle texture1; + TextureHandle texture2; + TextureHandle texture3; + // First pass creates and writes two textures. + using (var builder = m_RenderGraph.AddRenderPass("Async_TestPass0", out var passData)) + { + texture0 = builder.WriteTexture(m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm })); + texture1 = builder.WriteTexture(m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm })); + builder.EnableAsyncCompute(true); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + // Second pass creates a transient texture => Create/Release should happen in this pass but we want to delay the release until the first graphics pipe pass that sync with async queue. + using (var builder = m_RenderGraph.AddRenderPass("Async_TestPass1", out var passData)) + { + texture2 = builder.CreateTransientTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm }); + builder.WriteTexture(texture0); + builder.EnableAsyncCompute(true); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + // This pass is the last to read texture0 => Release should happen in this pass but we want to delay the release until the first graphics pipe pass that sync with async queue. + using (var builder = m_RenderGraph.AddRenderPass("Async_TestPass2", out var passData)) + { + texture0 = builder.ReadTexture(texture0); + builder.WriteTexture(texture1); + builder.EnableAsyncCompute(true); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + // Just here to add "padding" to the number of passes to ensure resources are not released right at the first sync pass. + using (var builder = m_RenderGraph.AddRenderPass("TestPass3", out var passData)) + { + texture3 = builder.WriteTexture(m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm })); + builder.EnableAsyncCompute(false); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + // Pass prior to synchronization should be where textures are released. + using (var builder = m_RenderGraph.AddRenderPass("TestPass4", out var passData)) + { + builder.WriteTexture(texture3); + builder.EnableAsyncCompute(false); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + // Graphics pass that reads texture1. This will request a sync with compute pipe. The previous pass should be the one releasing async textures. + using (var builder = m_RenderGraph.AddRenderPass("TestPass5", out var passData)) + { + builder.ReadTexture(texture1); + builder.ReadTexture(texture3); + builder.WriteTexture(m_RenderGraph.ImportBackbuffer(0)); // Needed for the passes to not be culled + builder.EnableAsyncCompute(false); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + m_RenderGraph.CompileRenderGraph(); + + var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + Assert.AreEqual(6, compiledPasses.size); + Assert.Contains(texture0.handle.index, compiledPasses[4].resourceReleaseList[(int)RenderGraphResourceType.Texture]); + Assert.Contains(texture2.handle.index, compiledPasses[4].resourceReleaseList[(int)RenderGraphResourceType.Texture]); + } + + [Test] + public void TransientResourceNotCulled() + { + TextureHandle texture0; + using (var builder = m_RenderGraph.AddRenderPass("TestPass0", out var passData)) + { + texture0 = builder.WriteTexture(m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm })); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + using (var builder = m_RenderGraph.AddRenderPass("TestPass1", out var passData)) + { + builder.CreateTransientTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm }); + builder.WriteTexture(texture0); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + // Graphics pass that reads texture1. This will request a sync with compute pipe. The previous pass should be the one releasing async textures. + using (var builder = m_RenderGraph.AddRenderPass("TestPass5", out var passData)) + { + builder.ReadTexture(texture0); + builder.WriteTexture(m_RenderGraph.ImportBackbuffer(0)); // Needed for the passes to not be culled + builder.EnableAsyncCompute(false); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + m_RenderGraph.CompileRenderGraph(); + + var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + Assert.AreEqual(3, compiledPasses.size); + Assert.AreEqual(false, compiledPasses[1].culled); + } + + [Test] + public void AsyncPassWriteWaitOnGraphcisPipe() + { + TextureHandle texture0; + using (var builder = m_RenderGraph.AddRenderPass("TestPass0", out var passData)) + { + texture0 = builder.WriteTexture(m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm })); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + using (var builder = m_RenderGraph.AddRenderPass("Async_TestPass1", out var passData)) + { + texture0 = builder.WriteTexture(texture0); + builder.EnableAsyncCompute(true); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + using (var builder = m_RenderGraph.AddRenderPass("TestPass2", out var passData)) + { + builder.ReadTexture(texture0); + builder.WriteTexture(m_RenderGraph.ImportBackbuffer(0)); // Needed for the passes to not be culled + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + m_RenderGraph.CompileRenderGraph(); + + var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + Assert.AreEqual(3, compiledPasses.size); + Assert.AreEqual(0, compiledPasses[1].syncToPassIndex); + Assert.AreEqual(1, compiledPasses[2].syncToPassIndex); + } + + [Test] + public void AsyncPassReadWaitOnGraphcisPipe() + { + TextureHandle texture0; + TextureHandle texture1; + using (var builder = m_RenderGraph.AddRenderPass("TestPass0", out var passData)) + { + texture0 = builder.WriteTexture(m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm })); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + using (var builder = m_RenderGraph.AddRenderPass("Async_TestPass1", out var passData)) + { + builder.ReadTexture(texture0); + texture1 = builder.WriteTexture(m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm })); + builder.EnableAsyncCompute(true); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + using (var builder = m_RenderGraph.AddRenderPass("TestPass2", out var passData)) + { + builder.ReadTexture(texture1); + builder.WriteTexture(m_RenderGraph.ImportBackbuffer(0)); // Needed for the passes to not be culled + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + m_RenderGraph.CompileRenderGraph(); + + var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + Assert.AreEqual(3, compiledPasses.size); + Assert.AreEqual(0, compiledPasses[1].syncToPassIndex); + Assert.AreEqual(1, compiledPasses[2].syncToPassIndex); + } + + [Test] + public void GraphicsPassWriteWaitOnAsyncPipe() + { + TextureHandle texture0; + using (var builder = m_RenderGraph.AddRenderPass("Async_TestPass0", out var passData)) + { + texture0 = builder.WriteTexture(m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm })); + builder.EnableAsyncCompute(true); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + using (var builder = m_RenderGraph.AddRenderPass("TestPass1", out var passData)) + { + builder.WriteTexture(texture0); + builder.WriteTexture(m_RenderGraph.ImportBackbuffer(0)); // Needed for the passes to not be culled + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + m_RenderGraph.CompileRenderGraph(); + + var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + Assert.AreEqual(2, compiledPasses.size); + Assert.AreEqual(0, compiledPasses[1].syncToPassIndex); + } + + + [Test] + public void GraphicsPassReadWaitOnAsyncPipe() + { + TextureHandle texture0; + using (var builder = m_RenderGraph.AddRenderPass("Async_TestPass0", out var passData)) + { + texture0 = builder.WriteTexture(m_RenderGraph.CreateTexture(new TextureDesc(Vector2.one) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm })); + builder.EnableAsyncCompute(true); + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + using (var builder = m_RenderGraph.AddRenderPass("TestPass1", out var passData)) + { + builder.ReadTexture(texture0); + builder.WriteTexture(m_RenderGraph.ImportBackbuffer(0)); // Needed for the passes to not be culled + builder.SetRenderFunc((RenderGraphTestPassData data, RenderGraphContext context) => { }); + } + + m_RenderGraph.CompileRenderGraph(); + + var compiledPasses = m_RenderGraph.GetCompiledPassInfos(); + Assert.AreEqual(2, compiledPasses.size); + Assert.AreEqual(0, compiledPasses[1].syncToPassIndex); + } + } +} diff --git a/Tests/Editor/RenderGraphTests.cs.meta b/Tests/Editor/RenderGraphTests.cs.meta new file mode 100644 index 0000000..88dd808 --- /dev/null +++ b/Tests/Editor/RenderGraphTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d6a9714d04bc387489829e17fc14cf3e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json index 2cbd591..a4ff89e 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,19 @@ { "name": "com.unity.render-pipelines.core", "description": "SRP Core makes it easier to create or customize a Scriptable Render Pipeline (SRP). SRP Core contains reusable code, including boilerplate code for working with platform-specific graphics APIs, utility functions for common rendering operations, and shader libraries. The code in SRP Core is use by the High Definition Render Pipeline (HDRP) and Universal Render Pipeline (URP). If you are creating a custom SRP from scratch or customizing a prebuilt SRP, using SRP Core will save you time.", - "version": "8.2.0", - "unity": "2020.1", - "unityRelease": "0b15", + "version": "10.1.0", + "unity": "2020.2", + "unityRelease": "0b8", "displayName": "Core RP Library", "dependencies": { "com.unity.ugui": "1.0.0" }, "upmCi": { - "footprint": "ad44e0437e209566c33fd69d7b5a75c8b7c12bb1" + "footprint": "6e03b4d28bbfdc33b409d8bb4ee658df0215b70a" }, "repository": { "url": "https://github.com/Unity-Technologies/Graphics.git", "type": "git", - "revision": "4ad03e7975af24c8e60c974edb24c696e74e39b3" + "revision": "544de544b923426b18b633c470a31a739532e0c4" } }