diff --git a/.gitignore b/.gitignore index 603f6d435..274105a24 100644 --- a/.gitignore +++ b/.gitignore @@ -148,5 +148,6 @@ bin/ obj/ *.vcxproj.filters .vscode/ +.idea/ *.code-workspace diff --git a/Content/Editor/MaterialTemplates/Particle.shader b/Content/Editor/MaterialTemplates/Particle.shader index a3736a53e..ec73cd072 100644 --- a/Content/Editor/MaterialTemplates/Particle.shader +++ b/Content/Editor/MaterialTemplates/Particle.shader @@ -76,8 +76,8 @@ struct VertexOutput #if USE_CUSTOM_VERTEX_INTERPOLATORS float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; #endif - float3 InstanceOrigin : TEXCOORD6; - float InstanceParams : TEXCOORD7; // x-PerInstanceRandom + nointerpolation float3 InstanceOrigin : TEXCOORD6; + nointerpolation float InstanceParams : TEXCOORD7; // x-PerInstanceRandom }; // Interpolants passed to the pixel shader @@ -94,8 +94,8 @@ struct PixelInput #if USE_CUSTOM_VERTEX_INTERPOLATORS float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; #endif - float3 InstanceOrigin : TEXCOORD6; - float InstanceParams : TEXCOORD7; // x-PerInstanceRandom + nointerpolation float3 InstanceOrigin : TEXCOORD6; + nointerpolation float InstanceParams : TEXCOORD7; // x-PerInstanceRandom bool IsFrontFace : SV_IsFrontFace; }; diff --git a/Content/Editor/MaterialTemplates/Surface.shader b/Content/Editor/MaterialTemplates/Surface.shader index cf6e00e36..ff88f1b40 100644 --- a/Content/Editor/MaterialTemplates/Surface.shader +++ b/Content/Editor/MaterialTemplates/Surface.shader @@ -43,8 +43,8 @@ struct GeometryData #endif float3 WorldNormal : TEXCOORD3; float4 WorldTangent : TEXCOORD4; - float3 InstanceOrigin : TEXCOORD5; - float2 InstanceParams : TEXCOORD6; // x-PerInstanceRandom, y-LODDitherFactor + nointerpolation float3 InstanceOrigin : TEXCOORD5; + nointerpolation float2 InstanceParams : TEXCOORD6; // x-PerInstanceRandom, y-LODDitherFactor float3 PrevWorldPosition : TEXCOORD7; }; diff --git a/Content/Editor/Primitives/Capsule.flax b/Content/Editor/Primitives/Capsule.flax index 10b38bf9d..913a96216 100644 --- a/Content/Editor/Primitives/Capsule.flax +++ b/Content/Editor/Primitives/Capsule.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1d9c920a1931dda8ea9ad3edca0b97935ffe0ceba0bf647ef1611db76c13490 -size 25720 +oid sha256:a65e29fe6fb86f08fa79ad65da87db1f050e60edd3e881837d81a9933a067230 +size 30396 diff --git a/Content/Editor/Primitives/Cone.flax b/Content/Editor/Primitives/Cone.flax index fa0123178..adf83905b 100644 --- a/Content/Editor/Primitives/Cone.flax +++ b/Content/Editor/Primitives/Cone.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ac606bb7dc790affc7b8ca01077597902e7a29e2ae9ea10c60d949adc953839 -size 8815 +oid sha256:5f4e87190b79f532ca04327064cc5bf05312fb77ba267d6a3df213252983f23e +size 11217 diff --git a/Content/Editor/Primitives/Cube.flax b/Content/Editor/Primitives/Cube.flax index a45f860cc..978850c44 100644 --- a/Content/Editor/Primitives/Cube.flax +++ b/Content/Editor/Primitives/Cube.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff43d080b349c955e1b16396018ef0066f3eacd525f8837bb522af15e174bfda -size 2579 +oid sha256:4f62d95192ce88e2871f8efb9ea716aed6954d1fe3628ac5c923f25236d42397 +size 4981 diff --git a/Content/Editor/Primitives/Cylinder.flax b/Content/Editor/Primitives/Cylinder.flax index 310b426aa..224177090 100644 --- a/Content/Editor/Primitives/Cylinder.flax +++ b/Content/Editor/Primitives/Cylinder.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d487edcc44d3d27275f180e00a99753dae244b2ba74f7c1a016775d64d72929 -size 13207 +oid sha256:d744a628567cf3ca1598183a3d7fbf2b23c3b23816ca80103cd8026638f7eddc +size 15609 diff --git a/Content/Editor/Primitives/Plane.flax b/Content/Editor/Primitives/Plane.flax index 1e5eaf76b..57fc0959a 100644 --- a/Content/Editor/Primitives/Plane.flax +++ b/Content/Editor/Primitives/Plane.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5683a761f198d01d7189490d526b55e393eb01fffbd6761fb969d11067b3db73 -size 2277 +oid sha256:9a82456ad825b8bca32b4eec7e33ced41029551183ebad712462e73d006b887c +size 3321 diff --git a/Content/Editor/Primitives/Sphere.flax b/Content/Editor/Primitives/Sphere.flax index d2d26788f..651bc7afc 100644 --- a/Content/Editor/Primitives/Sphere.flax +++ b/Content/Editor/Primitives/Sphere.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2f6b4fd9f83c269d54f716edb09071d00d71b09a89d829ce936038677648f22 -size 22219 +oid sha256:0bd78fb9c7b970d7661cff626568cd5fada5a5071740b7771241288d9bcb7995 +size 40605 diff --git a/Content/Engine/Models/Quad.flax b/Content/Engine/Models/Quad.flax index 0bec10f36..3e1ac4692 100644 --- a/Content/Engine/Models/Quad.flax +++ b/Content/Engine/Models/Quad.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0ccf8e4082695391576e65cbaaf4a685d77a75cad668cc99a71149e852390c9 -size 865 +oid sha256:a7f1082e461386e0b2d89ce3cec3ddd7b670bce2a867e9503c841681721236bf +size 965 diff --git a/Content/Engine/Models/Sphere.flax b/Content/Engine/Models/Sphere.flax index 803b9ab40..182cb2e6f 100644 --- a/Content/Engine/Models/Sphere.flax +++ b/Content/Engine/Models/Sphere.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec6177abb0235b6da27b0aef9f06f9865b15548afcdc34e71448cae6b51b8db6 -size 109567 +oid sha256:6287b7eb7d31e1a8a910b9601c376a2bc870f514b6a569d84f112264fb853099 +size 111969 diff --git a/Content/Shaders/GI/GlobalSurfaceAtlas.flax b/Content/Shaders/GI/GlobalSurfaceAtlas.flax new file mode 100644 index 000000000..2376e451e --- /dev/null +++ b/Content/Shaders/GI/GlobalSurfaceAtlas.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ec7fc26caf2d2c9c216cc47684ac2adc6b872a5a61cdd7028a59e9230eae0f4 +size 10668 diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax new file mode 100644 index 000000000..f30c01874 --- /dev/null +++ b/Content/Shaders/GlobalSignDistanceField.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce0651af73a6bc605cb88d6a747f0d241d15062c19c8359c783138e476c9b0d3 +size 10943 diff --git a/Flax.flaxproj b/Flax.flaxproj index 7f8224511..d3c343532 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -2,8 +2,8 @@ "Name": "Flax", "Version": { "Major": 1, - "Minor": 3, - "Build": 6229 + "Minor": 4, + "Build": 6332 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.", diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index 4c0ea9564..ec2e8d359 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -254,6 +254,7 @@ True True True + True True True True @@ -360,6 +361,7 @@ True True True + True True True True diff --git a/Source/.editorconfig b/Source/.editorconfig index 06e4f1454..8f9d7f7b0 100644 --- a/Source/.editorconfig +++ b/Source/.editorconfig @@ -18,6 +18,11 @@ indent_size = 4 indent_style = space indent_size = 4 +# Shader files +[*.{hlsl,shader,glsl}] +indent_style = space +indent_size = 4 + # XAML files [*.xaml] indent_style = space diff --git a/Source/Editor/Content/Import/ModelImportEntry.cs b/Source/Editor/Content/Import/ModelImportEntry.cs index 23a15157a..ebd7cf951 100644 --- a/Source/Editor/Content/Import/ModelImportEntry.cs +++ b/Source/Editor/Content/Import/ModelImportEntry.cs @@ -90,219 +90,270 @@ namespace FlaxEditor.Content.Import public class ModelImportSettings { /// - /// Gets or sets the type of the imported asset. + /// Type of the imported asset. /// - [EditorOrder(0), Tooltip("Type of the imported asset")] + [EditorOrder(0)] public ModelType Type { get; set; } = ModelType.Model; /// - /// True if calculate model normals, otherwise will import them. + /// Enable model normal vectors recalculating. /// - [EditorOrder(20), DefaultValue(false), EditorDisplay("Geometry"), Tooltip("Enable model normal vectors recalculating")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(20), DefaultValue(false)] public bool CalculateNormals { get; set; } = false; /// - /// Calculated normals smoothing angle. + /// Specifies the maximum angle (in degrees) that may be between two face normals at the same vertex position that their are smoothed together. The default value is 175. /// - [VisibleIf("CalculateNormals")] - [EditorOrder(30), DefaultValue(175.0f), Limit(0, 175, 0.1f), EditorDisplay("Geometry"), Tooltip("Specifies the maximum angle (in degrees) that may be between two face normals at the same vertex position that their are smoothed together. The default value is 175.")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowSmoothingNormalsAngle))] + [EditorOrder(30), DefaultValue(175.0f), Limit(0, 175, 0.1f)] public float SmoothingNormalsAngle { get; set; } = 175.0f; + private bool ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals; + /// /// If checked, the imported normal vectors of the mesh will be flipped (scaled by -1). /// - [EditorOrder(35), DefaultValue(false), EditorDisplay("Geometry"), Tooltip("If checked, the imported normal vectors of the mesh will be flipped (scaled by -1).")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(35), DefaultValue(false)] public bool FlipNormals { get; set; } = false; /// - /// True if calculate model tangents, otherwise will import them. + /// Enable model tangent vectors recalculating. /// - [EditorOrder(40), DefaultValue(false), EditorDisplay("Geometry"), Tooltip("Enable model tangent vectors recalculating")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(40), DefaultValue(false)] public bool CalculateTangents { get; set; } = false; /// - /// Calculated normals smoothing angle. + /// Specifies the maximum angle (in degrees) that may be between two vertex tangents that their tangents and bi-tangents are smoothed. The default value is 45. /// - [VisibleIf("CalculateTangents")] - [EditorOrder(45), DefaultValue(45.0f), Limit(0, 45, 0.1f), EditorDisplay("Geometry"), Tooltip("Specifies the maximum angle (in degrees) that may be between two vertex tangents that their tangents and bi-tangents are smoothed. The default value is 45.")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowSmoothingTangentsAngle))] + [EditorOrder(45), DefaultValue(45.0f), Limit(0, 45, 0.1f)] public float SmoothingTangentsAngle { get; set; } = 45.0f; + private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents; + /// /// Enable/disable meshes geometry optimization. /// - [EditorOrder(50), DefaultValue(true), EditorDisplay("Geometry"), Tooltip("Enable/disable meshes geometry optimization")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(50), DefaultValue(true)] public bool OptimizeMeshes { get; set; } = true; /// /// Enable/disable geometry merge for meshes with the same materials. /// - [EditorOrder(60), DefaultValue(true), EditorDisplay("Geometry"), Tooltip("Enable/disable geometry merge for meshes with the same materials")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(60), DefaultValue(true)] public bool MergeMeshes { get; set; } = true; /// /// Enable/disable importing meshes Level of Details. /// - [EditorOrder(70), DefaultValue(true), EditorDisplay("Geometry", "Import LODs"), Tooltip("Enable/disable importing meshes Level of Details")] + [EditorDisplay("Geometry", "Import LODs"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(70), DefaultValue(true)] public bool ImportLODs { get; set; } = true; /// /// Enable/disable importing vertex colors (channel 0 only). /// - [EditorOrder(80), DefaultValue(true), EditorDisplay("Geometry"), Tooltip("Enable/disable importing vertex colors (channel 0 only)")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowModel))] + [EditorOrder(80), DefaultValue(true)] public bool ImportVertexColors { get; set; } = true; /// /// Enable/disable importing blend shapes (morph targets). /// - [EditorOrder(85), DefaultValue(false), EditorDisplay("Geometry"), Tooltip("Enable/disable importing blend shapes (morph targets).")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowSkinnedModel))] + [EditorOrder(85), DefaultValue(false)] public bool ImportBlendShapes { get; set; } = false; /// /// The lightmap UVs source. /// - [EditorOrder(90), DefaultValue(ModelLightmapUVsSource.Disable), EditorDisplay("Geometry", "Lightmap UVs Source"), Tooltip("Model lightmap UVs source")] + [EditorDisplay("Geometry", "Lightmap UVs Source"), VisibleIf(nameof(ShowModel))] + [EditorOrder(90), DefaultValue(ModelLightmapUVsSource.Disable)] public ModelLightmapUVsSource LightmapUVsSource { get; set; } = ModelLightmapUVsSource.Disable; /// /// If specified, all meshes which name starts with this prefix will be imported as a separate collision data (excluded used for rendering). /// - [EditorOrder(100), DefaultValue(""), EditorDisplay("Geometry")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(100), DefaultValue("")] public string CollisionMeshesPrefix { get; set; } /// /// Custom uniform import scale. /// - [EditorOrder(500), DefaultValue(1.0f), EditorDisplay("Transform"), Tooltip("Custom uniform import scale")] + [EditorOrder(500), DefaultValue(1.0f), EditorDisplay("Transform")] public float Scale { get; set; } = 1.0f; /// /// Custom import geometry rotation. /// [DefaultValue(typeof(Quaternion), "0,0,0,1")] - [EditorOrder(510), EditorDisplay("Transform"), Tooltip("Custom import geometry rotation")] + [EditorOrder(510), EditorDisplay("Transform")] public Quaternion Rotation { get; set; } = Quaternion.Identity; /// /// Custom import geometry offset. /// [DefaultValue(typeof(Vector3), "0,0,0")] - [EditorOrder(520), EditorDisplay("Transform"), Tooltip("Custom import geometry offset")] + [EditorOrder(520), EditorDisplay("Transform")] public Vector3 Translation { get; set; } = Vector3.Zero; /// /// If checked, the imported geometry will be shifted to the center of mass. /// - [EditorOrder(530), DefaultValue(false), EditorDisplay("Transform"), Tooltip("If checked, the imported geometry will be shifted to the center of mass.")] + [EditorOrder(530), DefaultValue(false), EditorDisplay("Transform")] public bool CenterGeometry { get; set; } = false; /// - /// The imported animation duration mode. + /// Imported animation duration mode. Can use the original value or overriden by settings. /// - [EditorOrder(1000), DefaultValue(AnimationDuration.Imported), EditorDisplay("Animation"), Tooltip("Imported animation duration mode. Can use the original value or overriden by settings.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] + [EditorOrder(1000), DefaultValue(AnimationDuration.Imported)] public AnimationDuration Duration { get; set; } = AnimationDuration.Imported; /// - /// The imported animation first frame index. Used only if Duration mode is set to Custom. + /// Imported animation first frame index. Used only if Duration mode is set to Custom. /// - [EditorOrder(1010), DefaultValue(0.0f), Limit(0), EditorDisplay("Animation"), Tooltip("Imported animation first frame index. Used only if Duration mode is set to Custom.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowFramesRange))] + [EditorOrder(1010), DefaultValue(0.0f), Limit(0)] public float FramesRangeStart { get; set; } = 0; /// - /// The imported animation end frame index. Used only if Duration mode is set to Custom. + /// Imported animation last frame index. Used only if Duration mode is set to Custom. /// - [EditorOrder(1020), DefaultValue(0.0f), Limit(0), EditorDisplay("Animation"), Tooltip("Imported animation last frame index. Used only if Duration mode is set to Custom.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowFramesRange))] + [EditorOrder(1020), DefaultValue(0.0f), Limit(0)] public float FramesRangeEnd { get; set; } = 0; + private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom; + /// /// The imported animation default frame rate. Can specify the default frames per second amount for imported animation. If value is 0 then the original animation frame rate will be used. /// - [EditorOrder(1025), DefaultValue(0.0f), Limit(0, 1000, 0.01f), EditorDisplay("Animation"), Tooltip("The imported animation default frame rate. Can specify the default frames per second amount for imported animation. If value is 0 then the original animation frame rate will be used.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] + [EditorOrder(1025), DefaultValue(0.0f), Limit(0, 1000, 0.01f)] public float DefaultFrameRate { get; set; } = 0.0f; /// /// The imported animation sampling rate. If value is 0 then the original animation speed will be used. /// - [EditorOrder(1030), DefaultValue(0.0f), Limit(0, 1000, 0.01f), EditorDisplay("Animation"), Tooltip("The imported animation sampling rate. If value is 0 then the original animation speed will be used.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] + [EditorOrder(1030), DefaultValue(0.0f), Limit(0, 1000, 0.01f)] public float SamplingRate { get; set; } = 0.0f; /// /// The imported animation will have removed tracks with no keyframes or unspecified data. /// - [EditorOrder(1040), DefaultValue(true), EditorDisplay("Animation"), Tooltip("The imported animation will have removed tracks with no keyframes or unspecified data.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] + [EditorOrder(1040), DefaultValue(true)] public bool SkipEmptyCurves { get; set; } = true; /// /// The imported animation channels will be optimized to remove redundant keyframes. /// - [EditorOrder(1050), DefaultValue(true), EditorDisplay("Animation"), Tooltip("The imported animation channels will be optimized to remove redundant keyframes.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] + [EditorOrder(1050), DefaultValue(true)] public bool OptimizeKeyframes { get; set; } = true; /// /// Enables root motion extraction support from this animation. /// - [EditorOrder(1060), DefaultValue(false), EditorDisplay("Animation"), Tooltip("Enables root motion extraction support from this animation.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] + [EditorOrder(1060), DefaultValue(false)] public bool EnableRootMotion { get; set; } = false; /// /// The custom node name to be used as a root motion source. If not specified the actual root node will be used. /// - [EditorOrder(1070), DefaultValue(typeof(string), ""), EditorDisplay("Animation"), Tooltip("The custom node name to be used as a root motion source. If not specified the actual root node will be used.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] + [EditorOrder(1070), DefaultValue(typeof(string), "")] public string RootNodeName { get; set; } /// /// If checked, the importer will generate a sequence of LODs based on the base LOD index. /// - [EditorOrder(1100), DefaultValue(false), EditorDisplay("Level Of Detail", "Generate LODs"), Tooltip("If checked, the importer will generate a sequence of LODs based on the base LOD index.")] + [EditorDisplay("Level Of Detail", "Generate LODs"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(1100), DefaultValue(false)] public bool GenerateLODs { get; set; } = false; /// /// The index of the LOD from the source model data to use as a reference for following LODs generation. /// - [EditorOrder(1110), DefaultValue(0), Limit(0, Model.MaxLODs - 1), EditorDisplay("Level Of Detail", "Base LOD"), Tooltip("The index of the LOD from the source model data to use as a reference for following LODs generation.")] + [EditorDisplay("Level Of Detail", "Base LOD"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(1110), DefaultValue(0), Limit(0, Model.MaxLODs - 1)] public int BaseLOD { get; set; } = 0; /// /// The amount of LODs to include in the model (all remaining ones starting from Base LOD will be generated). /// - [EditorOrder(1120), DefaultValue(4), Limit(1, Model.MaxLODs), EditorDisplay("Level Of Detail", "LOD Count"), Tooltip("The amount of LODs to include in the model (all remaining ones starting from Base LOD will be generated).")] + [EditorDisplay("Level Of Detail", "LOD Count"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(1120), DefaultValue(4), Limit(1, Model.MaxLODs)] public int LODCount { get; set; } = 4; /// /// The target amount of triangles for the generated LOD (based on the higher LOD). Normalized to range 0-1. For instance 0.4 cuts the triangle count to 40%. /// - [EditorOrder(1130), DefaultValue(0.5f), Limit(0, 1, 0.001f), EditorDisplay("Level Of Detail"), Tooltip("The target amount of triangles for the generated LOD (based on the higher LOD). Normalized to range 0-1. For instance 0.4 cuts the triangle count to 40%.")] + [EditorDisplay("Level Of Detail"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(1130), DefaultValue(0.5f), Limit(0, 1, 0.001f)] public float TriangleReduction { get; set; } = 0.5f; /// /// If checked, the importer will create materials for model meshes as specified in the file. /// - [EditorOrder(400), DefaultValue(true), EditorDisplay("Materials"), Tooltip("If checked, the importer will create materials for model meshes as specified in the file.")] + [EditorDisplay("Materials"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(400), DefaultValue(true)] public bool ImportMaterials { get; set; } = true; /// /// If checked, the importer will import texture files used by the model and any embedded texture resources. /// - [EditorOrder(410), DefaultValue(true), EditorDisplay("Materials"), Tooltip("If checked, the importer will import texture files used by the model and any embedded texture resources.")] + [EditorDisplay("Materials"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(410), DefaultValue(true)] public bool ImportTextures { get; set; } = true; /// /// If checked, the importer will try to restore the model material slots. /// - [EditorOrder(420), DefaultValue(true), EditorDisplay("Materials", "Restore Materials On Reimport"), Tooltip("If checked, the importer will try to restore the assigned materials to the model slots.")] + [EditorDisplay("Materials", "Restore Materials On Reimport"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(420), DefaultValue(true)] public bool RestoreMaterialsOnReimport { get; set; } = true; + /// + /// If checked, enables generation of Signed Distance Field (SDF). + /// + [EditorDisplay("SDF"), VisibleIf(nameof(ShowModel))] + [EditorOrder(1500), DefaultValue(false)] + public bool GenerateSDF { get; set; } = false; + + /// + /// Resolution scale for generated Signed Distance Field (SDF) texture. Higher values improve accuracy but increase memory usage and reduce performance. + /// + [EditorDisplay("SDF"), VisibleIf(nameof(ShowModel))] + [EditorOrder(1510), DefaultValue(1.0f), Limit(0.0001f, 100.0f)] + public float SDFResolution { get; set; } = 1.0f; + /// /// If checked, the imported mesh/animations are splitted into separate assets. Used if ObjectIndex is set to -1. /// - [EditorOrder(2000), DefaultValue(false), EditorDisplay("Splitting"), Tooltip("If checked, the imported mesh/animations are splitted into separate assets. Used if ObjectIndex is set to -1.")] + [EditorOrder(2000), DefaultValue(false), EditorDisplay("Splitting")] public bool SplitObjects { get; set; } = false; /// /// The zero-based index for the mesh/animation clip to import. If the source file has more than one mesh/animation it can be used to pick a desire object. Default -1 imports all objects. /// - [EditorOrder(2010), DefaultValue(-1), EditorDisplay("Splitting"), Tooltip("The zero-based index for the mesh/animation clip to import. If the source file has more than one mesh/animation it can be used to pick a desire object. Default -1 imports all objects.")] + [EditorOrder(2010), DefaultValue(-1), EditorDisplay("Splitting")] public int ObjectIndex { get; set; } = -1; + private bool ShowGeometry => Type == ModelType.Model || Type == ModelType.SkinnedModel; + private bool ShowModel => Type == ModelType.Model; + private bool ShowSkinnedModel => Type == ModelType.SkinnedModel; + private bool ShowAnimation => Type == ModelType.Animation; + [StructLayout(LayoutKind.Sequential)] internal struct InternalOptions { @@ -350,6 +401,10 @@ namespace FlaxEditor.Content.Import public byte ImportTextures; public byte RestoreMaterialsOnReimport; + // SDF + public byte GenerateSDF; + public float SDFResolution; + // Splitting public byte SplitObjects; public int ObjectIndex; @@ -392,6 +447,8 @@ namespace FlaxEditor.Content.Import ImportMaterials = (byte)(ImportMaterials ? 1 : 0), ImportTextures = (byte)(ImportTextures ? 1 : 0), RestoreMaterialsOnReimport = (byte)(RestoreMaterialsOnReimport ? 1 : 0), + GenerateSDF = (byte)(GenerateSDF ? 1 : 0), + SDFResolution = SDFResolution, SplitObjects = (byte)(SplitObjects ? 1 : 0), ObjectIndex = ObjectIndex, }; @@ -431,25 +488,22 @@ namespace FlaxEditor.Content.Import ImportMaterials = options.ImportMaterials != 0; ImportTextures = options.ImportTextures != 0; RestoreMaterialsOnReimport = options.RestoreMaterialsOnReimport != 0; + GenerateSDF = options.GenerateSDF != 0; + SDFResolution = options.SDFResolution; SplitObjects = options.SplitObjects != 0; ObjectIndex = options.ObjectIndex; } /// - /// Tries the restore the asset import options from the target resource file. + /// Tries the restore the asset import options from the target resource file. Applies the project default options too. /// /// The options. /// The asset path. /// True settings has been restored, otherwise false. - public static bool TryRestore(ref ModelImportSettings options, string assetPath) + public static void TryRestore(ref ModelImportSettings options, string assetPath) { - if (ModelImportEntry.Internal_GetModelImportOptions(assetPath, out var internalOptions)) - { - // Restore settings - options.FromInternal(ref internalOptions); - return true; - } - return false; + ModelImportEntry.Internal_GetModelImportOptions(assetPath, out var internalOptions); + options.FromInternal(ref internalOptions); } } @@ -495,7 +549,7 @@ namespace FlaxEditor.Content.Import #region Internal Calls [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_GetModelImportOptions(string path, out ModelImportSettings.InternalOptions result); + internal static extern void Internal_GetModelImportOptions(string path, out ModelImportSettings.InternalOptions result); #endregion } diff --git a/Source/Editor/Content/Proxy/AssetProxy.cs b/Source/Editor/Content/Proxy/AssetProxy.cs index 6f081c050..6516dbc37 100644 --- a/Source/Editor/Content/Proxy/AssetProxy.cs +++ b/Source/Editor/Content/Proxy/AssetProxy.cs @@ -93,5 +93,29 @@ namespace FlaxEditor.Content public virtual void OnThumbnailDrawCleanup(ThumbnailRequest request) { } + + /// + /// Initializes rendering settings for asset preview drawing for a thumbnail. + /// + /// The asset preview. + protected void InitAssetPreview(Viewport.Previews.AssetPreview preview) + { + preview.RenderOnlyWithWindow = false; + preview.UseAutomaticTaskManagement = false; + preview.AnchorPreset = AnchorPresets.StretchAll; + preview.Offsets = Margin.Zero; + + var task = preview.Task; + task.Enabled = false; + + var view = task.View; + view.IsSingleFrame = true; // Disable LOD transitions + task.View = view; + + var eyeAdaptation = preview.PostFxVolume.EyeAdaptation; + eyeAdaptation.Mode = EyeAdaptationMode.None; + eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; + preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + } } } diff --git a/Source/Editor/Content/Proxy/CubeTextureProxy.cs b/Source/Editor/Content/Proxy/CubeTextureProxy.cs index fbf75d9f5..d89771b13 100644 --- a/Source/Editor/Content/Proxy/CubeTextureProxy.cs +++ b/Source/Editor/Content/Proxy/CubeTextureProxy.cs @@ -44,19 +44,8 @@ namespace FlaxEditor.Content { if (_preview == null) { - _preview = new CubeTexturePreview(false) - { - RenderOnlyWithWindow = false, - UseAutomaticTaskManagement = false, - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - }; - _preview.Task.Enabled = false; - - var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation; - eyeAdaptation.Mode = EyeAdaptationMode.None; - eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; - _preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + _preview = new CubeTexturePreview(false); + InitAssetPreview(_preview); } // TODO: disable streaming for asset during thumbnail rendering (and restore it after) diff --git a/Source/Editor/Content/Proxy/JsonAssetProxy.cs b/Source/Editor/Content/Proxy/JsonAssetProxy.cs index 15d5e89f1..3f2863f74 100644 --- a/Source/Editor/Content/Proxy/JsonAssetProxy.cs +++ b/Source/Editor/Content/Proxy/JsonAssetProxy.cs @@ -163,7 +163,7 @@ namespace FlaxEditor.Content public sealed class SpawnableJsonAssetProxy : JsonAssetProxy where T : new() { /// - public override string Name { get; } = CustomEditors.CustomEditorsUtil.GetPropertyNameUI(typeof(T).Name); + public override string Name { get; } = Utilities.Utils.GetPropertyNameUI(typeof(T).Name); /// public override bool CanCreate(ContentFolder targetLocation) diff --git a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs index 38faf5b71..663c191ef 100644 --- a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs @@ -51,19 +51,8 @@ namespace FlaxEditor.Content { if (_preview == null) { - _preview = new MaterialPreview(false) - { - RenderOnlyWithWindow = false, - UseAutomaticTaskManagement = false, - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - }; - _preview.Task.Enabled = false; - - var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation; - eyeAdaptation.Mode = EyeAdaptationMode.None; - eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; - _preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + _preview = new MaterialPreview(false); + InitAssetPreview(_preview); } // TODO: disable streaming for dependant assets during thumbnail rendering (and restore it after) diff --git a/Source/Editor/Content/Proxy/MaterialProxy.cs b/Source/Editor/Content/Proxy/MaterialProxy.cs index 8c31bd4c8..e2e74eb14 100644 --- a/Source/Editor/Content/Proxy/MaterialProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialProxy.cs @@ -97,19 +97,8 @@ namespace FlaxEditor.Content { if (_preview == null) { - _preview = new MaterialPreview(false) - { - RenderOnlyWithWindow = false, - UseAutomaticTaskManagement = false, - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - }; - _preview.Task.Enabled = false; - - var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation; - eyeAdaptation.Mode = EyeAdaptationMode.None; - eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; - _preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + _preview = new MaterialPreview(false); + InitAssetPreview(_preview); } // TODO: disable streaming for dependant assets during thumbnail rendering (and restore it after) diff --git a/Source/Editor/Content/Proxy/ModelProxy.cs b/Source/Editor/Content/Proxy/ModelProxy.cs index 7122bee77..b99d15134 100644 --- a/Source/Editor/Content/Proxy/ModelProxy.cs +++ b/Source/Editor/Content/Proxy/ModelProxy.cs @@ -58,19 +58,8 @@ namespace FlaxEditor.Content { if (_preview == null) { - _preview = new ModelPreview(false) - { - RenderOnlyWithWindow = false, - UseAutomaticTaskManagement = false, - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - }; - _preview.Task.Enabled = false; - - var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation; - eyeAdaptation.Mode = EyeAdaptationMode.None; - eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; - _preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + _preview = new ModelPreview(false); + InitAssetPreview(_preview); } // TODO: disable streaming for asset during thumbnail rendering (and restore it after) diff --git a/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs b/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs index f10c6cfe1..089c614da 100644 --- a/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs +++ b/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs @@ -52,19 +52,8 @@ namespace FlaxEditor.Content { if (_preview == null) { - _preview = new ParticleEmitterPreview(false) - { - RenderOnlyWithWindow = false, - UseAutomaticTaskManagement = false, - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - }; - _preview.Task.Enabled = false; - - var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation; - eyeAdaptation.Mode = EyeAdaptationMode.None; - eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; - _preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + _preview = new ParticleEmitterPreview(false); + InitAssetPreview(_preview); } // Mark for initial warmup diff --git a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs index 4d1261232..c19e84a78 100644 --- a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs +++ b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs @@ -83,19 +83,8 @@ namespace FlaxEditor.Content { if (_preview == null) { - _preview = new ParticleEmitterPreview(false) - { - RenderOnlyWithWindow = false, - UseAutomaticTaskManagement = false, - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - }; - _preview.Task.Enabled = false; - - var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation; - eyeAdaptation.Mode = EyeAdaptationMode.None; - eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; - _preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + _preview = new ParticleEmitterPreview(false); + InitAssetPreview(_preview); } // Mark for initial warmup diff --git a/Source/Editor/Content/Proxy/PrefabProxy.cs b/Source/Editor/Content/Proxy/PrefabProxy.cs index edc4ebba0..9ec143368 100644 --- a/Source/Editor/Content/Proxy/PrefabProxy.cs +++ b/Source/Editor/Content/Proxy/PrefabProxy.cs @@ -94,19 +94,8 @@ namespace FlaxEditor.Content { if (_preview == null) { - _preview = new PrefabPreview(false) - { - RenderOnlyWithWindow = false, - UseAutomaticTaskManagement = false, - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - }; - _preview.Task.Enabled = false; - - var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation; - eyeAdaptation.Mode = EyeAdaptationMode.None; - eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; - _preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + _preview = new PrefabPreview(false); + InitAssetPreview(_preview); } // TODO: disable streaming for asset during thumbnail rendering (and restore it after) diff --git a/Source/Editor/Content/Proxy/SettingsProxy.cs b/Source/Editor/Content/Proxy/SettingsProxy.cs index 6b1b5a6cb..0bbd2830f 100644 --- a/Source/Editor/Content/Proxy/SettingsProxy.cs +++ b/Source/Editor/Content/Proxy/SettingsProxy.cs @@ -35,7 +35,7 @@ namespace FlaxEditor.Content /// public override string Name => "Settings"; - //public override string Name { get; } = CustomEditors.CustomEditorsUtil.GetPropertyNameUI(_type.Name); + //public override string Name { get; } = Utilities.Utils.GetPropertyNameUI(_type.Name); /// public override bool CanCreate(ContentFolder targetLocation) diff --git a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs index 2666b836c..597c69a1d 100644 --- a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs +++ b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs @@ -44,19 +44,8 @@ namespace FlaxEditor.Content { if (_preview == null) { - _preview = new AnimatedModelPreview(false) - { - RenderOnlyWithWindow = false, - UseAutomaticTaskManagement = false, - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - }; - _preview.Task.Enabled = false; - - var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation; - eyeAdaptation.Mode = EyeAdaptationMode.None; - eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; - _preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + _preview = new AnimatedModelPreview(false); + InitAssetPreview(_preview); } // TODO: disable streaming for asset during thumbnail rendering (and restore it after) diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index 4e511b0ac..b41b28e43 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -68,6 +68,8 @@ bool DeployDataStep::Perform(CookingData& data) data.AddRootEngineAsset(TEXT("Shaders/MotionBlur")); data.AddRootEngineAsset(TEXT("Shaders/BitonicSort")); data.AddRootEngineAsset(TEXT("Shaders/GPUParticlesSorting")); + data.AddRootEngineAsset(TEXT("Shaders/GlobalSignDistanceField")); + data.AddRootEngineAsset(TEXT("Shaders/GI/GlobalSurfaceAtlas")); data.AddRootEngineAsset(TEXT("Shaders/Quad")); data.AddRootEngineAsset(TEXT("Shaders/Reflections")); data.AddRootEngineAsset(TEXT("Shaders/Shadows")); diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cs b/Source/Editor/CustomEditors/CustomEditorsUtil.cs index 6c20b4ed4..a40b223b9 100644 --- a/Source/Editor/CustomEditors/CustomEditorsUtil.cs +++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Text; using FlaxEditor.CustomEditors.Editors; using FlaxEditor.Scripting; using FlaxEngine; @@ -13,8 +12,6 @@ namespace FlaxEditor.CustomEditors { internal static class CustomEditorsUtil { - private static readonly StringBuilder CachedSb = new StringBuilder(256); - internal static readonly Dictionary InBuildTypeNames = new Dictionary() { { typeof(bool), "bool" }, @@ -46,51 +43,6 @@ namespace FlaxEditor.CustomEditors return result; } - /// - /// Gets the property name for UI. Removes unnecessary characters and filters text. Makes it more user-friendly. - /// - /// The name. - /// The result. - public static string GetPropertyNameUI(string name) - { - int length = name.Length; - StringBuilder sb = CachedSb; - sb.Clear(); - sb.EnsureCapacity(length + 8); - int startIndex = 0; - - // Skip some prefixes - if (name.StartsWith("g_") || name.StartsWith("m_")) - startIndex = 2; - - // Filter text - for (int i = startIndex; i < length; i++) - { - var c = name[i]; - - // Space before word starting with uppercase letter - if (char.IsUpper(c) && i > 0) - { - if (i + 1 < length && !char.IsUpper(name[i + 1])) - sb.Append(' '); - } - // Space instead of underscore - else if (c == '_') - { - if (sb.Length > 0) - sb.Append(' '); - continue; - } - // Space before digits sequence - else if (i > 1 && char.IsDigit(c) && !char.IsDigit(name[i - 1])) - sb.Append(' '); - - sb.Append(c); - } - - return sb.ToString(); - } - internal static CustomEditor CreateEditor(ValueContainer values, CustomEditor overrideEditor, bool canUseRefPicker = true) { // Check if use provided editor diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs index 4a46c32c7..49cc67868 100644 --- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs @@ -209,13 +209,13 @@ namespace FlaxEditor.CustomEditors.Dedicated if (editor is RemovedScriptDummy removed) { node.TextColor = Color.OrangeRed; - node.Text = CustomEditorsUtil.GetPropertyNameUI(removed.PrefabObject.GetType().Name); + node.Text = Utilities.Utils.GetPropertyNameUI(removed.PrefabObject.GetType().Name); } // Actor or Script else if (editor.Values[0] is SceneObject sceneObject) { node.TextColor = sceneObject.HasPrefabLink ? FlaxEngine.GUI.Style.Current.ProgressNormal : FlaxEngine.GUI.Style.Current.BackgroundSelected; - node.Text = CustomEditorsUtil.GetPropertyNameUI(sceneObject.GetType().Name); + node.Text = Utilities.Utils.GetPropertyNameUI(sceneObject.GetType().Name); } // Array Item else if (editor.ParentEditor?.Values?.Type.IsArray ?? false) @@ -225,7 +225,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Common type else if (editor.Values.Info != ScriptMemberInfo.Null) { - node.Text = CustomEditorsUtil.GetPropertyNameUI(editor.Values.Info.Name); + node.Text = Utilities.Utils.GetPropertyNameUI(editor.Values.Info.Name); } // Custom type else if (editor.Values[0] != null) diff --git a/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs b/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs index bfdcc57f6..664721258 100644 --- a/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs @@ -26,6 +26,26 @@ namespace FlaxEditor.CustomEditors.Dedicated } } + private object ParameterGet(object instance, GraphParameter parameter, object tag) + { + if (instance is ParticleEffect particleEffect && particleEffect && parameter && tag is ParticleEffectParameter effectParameter) + return particleEffect.GetParameterValue(effectParameter.TrackName, parameter.Name); + return null; + } + + private void ParameterSet(object instance, object value, GraphParameter parameter, object tag) + { + if (instance is ParticleEffect particleEffect && particleEffect && parameter && tag is ParticleEffectParameter effectParameter) + particleEffect.SetParameterValue(effectParameter.TrackName, parameter.Name, value); + } + + private object ParameterDefaultValue(object instance, GraphParameter parameter, object tag) + { + if (tag is ParticleEffectParameter effectParameter) + return effectParameter.DefaultValue; + return null; + } + /// public override void Initialize(LayoutElementsContainer layout) { @@ -48,11 +68,7 @@ namespace FlaxEditor.CustomEditors.Dedicated group.Panel.Open(false); var data = SurfaceUtils.InitGraphParameters(parametersGroup); - SurfaceUtils.DisplayGraphParameters(group, data, - (instance, parameter, tag) => ((ParticleEffect)instance).GetParameterValue(trackName, parameter.Name), - (instance, value, parameter, tag) => ((ParticleEffect)instance).SetParameterValue(trackName, parameter.Name, value), - Values, - (instance, parameter, tag) => ((ParticleEffectParameter)tag).DefaultValue); + SurfaceUtils.DisplayGraphParameters(group, data, ParameterGet, ParameterSet, Values, ParameterDefaultValue); } } diff --git a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs index fa72e06c0..2c3aebcc1 100644 --- a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs @@ -273,7 +273,7 @@ namespace FlaxEditor.CustomEditors.Dedicated cm.AddItem(item); } cm.ItemClicked += item => action((string)item.Tag); - cm.SortChildren(); + cm.SortItems(); cm.Show(button.Parent, button.BottomLeft); } diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index 9c3a3f645..c165824eb 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -71,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Dedicated cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i])); } cm.ItemClicked += item => AddScript((ScriptType)item.Tag); - cm.SortChildren(); + cm.SortItems(); cm.Show(this, button.BottomLeft); } @@ -611,7 +611,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var editor = CustomEditorsUtil.CreateEditor(scriptType, false); // Create group - var title = CustomEditorsUtil.GetPropertyNameUI(scriptType.Name); + var title = Utilities.Utils.GetPropertyNameUI(scriptType.Name); var group = layout.Group(title, editor); if ((Presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) { diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index ac26de204..60915ff8e 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -32,7 +32,7 @@ namespace FlaxEditor.CustomEditors.Dedicated if (_presets != value) { _presets = value; - TooltipText = CustomEditorsUtil.GetPropertyNameUI(_presets.ToString()); + TooltipText = Utilities.Utils.GetPropertyNameUI(_presets.ToString()); } } } @@ -642,7 +642,7 @@ namespace FlaxEditor.CustomEditors.Dedicated cm.AddItem(new TypeSearchPopup.TypeItemView(controlTypes[i])); } cm.ItemClicked += controlType => SetType((ScriptType)controlType.Tag); - cm.SortChildren(); + cm.SortItems(); cm.Show(button.Parent, button.BottomLeft); } diff --git a/Source/Editor/CustomEditors/Editors/DoubleEditor.cs b/Source/Editor/CustomEditors/Editors/DoubleEditor.cs index 06dee3d1b..859b70f4e 100644 --- a/Source/Editor/CustomEditors/Editors/DoubleEditor.cs +++ b/Source/Editor/CustomEditors/Editors/DoubleEditor.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. +using System; using System.Linq; using FlaxEditor.CustomEditors.Elements; using FlaxEngine; @@ -66,7 +67,13 @@ namespace FlaxEditor.CustomEditors.Editors } else { - _element.Value = (double)Values[0]; + var value = Values[0]; + if (value is double asDouble) + _element.Value = (float)asDouble; + else if (value is float asFloat) + _element.Value = asFloat; + else + throw new Exception(string.Format("Invalid value type {0}.", value?.GetType().ToString() ?? "")); } } } diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index a7ea235f2..a69ae7ba4 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -137,7 +137,7 @@ namespace FlaxEditor.CustomEditors.Editors ExpandGroups = attributes.FirstOrDefault(x => x is ExpandGroupsAttribute) != null; IsReadOnly |= !info.HasSet; - DisplayName = Display?.Name ?? CustomEditorsUtil.GetPropertyNameUI(info.Name); + DisplayName = Display?.Name ?? Utilities.Utils.GetPropertyNameUI(info.Name); var editor = Editor.Instance; TooltipText = editor.CodeDocs.GetTooltip(info, attributes); _membersOrder = editor.Options.Options.General.ScriptMembersOrder; @@ -229,6 +229,7 @@ namespace FlaxEditor.CustomEditors.Editors } } + private static HashSet _visibleIfPropertiesListsCache; private VisibleIfCache[] _visibleIfCaches; private bool _isNull; @@ -761,8 +762,13 @@ namespace FlaxEditor.CustomEditors.Editors if (_visibleIfCaches != null) { + if (_visibleIfPropertiesListsCache == null) + _visibleIfPropertiesListsCache = new HashSet(); + else + _visibleIfPropertiesListsCache.Clear(); try { + // Update VisibleIf rules for (int i = 0; i < _visibleIfCaches.Length; i++) { ref var c = ref _visibleIfCaches[i]; @@ -798,6 +804,21 @@ namespace FlaxEditor.CustomEditors.Editors { c.Group.Panel.Visible = visible; } + if (c.PropertiesList != null) + _visibleIfPropertiesListsCache.Add(c.PropertiesList.Properties); + } + + // Hide properties lists with all labels being hidden + foreach (var propertiesList in _visibleIfPropertiesListsCache) + { + propertiesList.Visible = propertiesList.Children.Any(c => c.Visible); + } + + // Hide group panels with all properties lists hidden + foreach (var propertiesList in _visibleIfPropertiesListsCache) + { + if (propertiesList.Parent is DropPanel dropPanel) + dropPanel.Visible = propertiesList.Visible || !dropPanel.Children.All(c => c is PropertiesList && !c.Visible); } } catch (Exception ex) diff --git a/Source/Editor/CustomEditors/Elements/ButtonElement.cs b/Source/Editor/CustomEditors/Elements/ButtonElement.cs index 69340238d..f7485e542 100644 --- a/Source/Editor/CustomEditors/Elements/ButtonElement.cs +++ b/Source/Editor/CustomEditors/Elements/ButtonElement.cs @@ -16,26 +16,6 @@ namespace FlaxEditor.CustomEditors.Elements /// public readonly Button Button = new Button(); - /// - /// Initializes the element. - /// - /// The text. - public void Init(string text) - { - Button.Text = text; - } - - /// - /// Initializes the element. - /// - /// The text. - /// The color. - public void Init(string text, Color color) - { - Button.Text = text; - Button.SetColors(color); - } - /// public override Control Control => Button; } diff --git a/Source/Editor/CustomEditors/Elements/LabelElement.cs b/Source/Editor/CustomEditors/Elements/LabelElement.cs index 3c1ee8ff7..5fab106d5 100644 --- a/Source/Editor/CustomEditors/Elements/LabelElement.cs +++ b/Source/Editor/CustomEditors/Elements/LabelElement.cs @@ -1,5 +1,8 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. +using System; +using FlaxEditor.GUI; +using FlaxEditor.GUI.ContextMenu; using FlaxEngine; using FlaxEngine.GUI; @@ -11,23 +14,49 @@ namespace FlaxEditor.CustomEditors.Elements /// public class LabelElement : LayoutElement { + private Action _customContextualOptions; + /// /// The label. /// - public readonly Label Label; + public readonly ClickableLabel Label; /// /// Initializes a new instance of the class. /// public LabelElement() { - Label = new Label(0, 0, 100, 18) + Label = new ClickableLabel { - HorizontalAlignment = TextAlignment.Near + Size = new Vector2(100, 18), + HorizontalAlignment = TextAlignment.Near, }; // TODO: auto height for label } + /// + /// Adds a simple context menu with utility to copy label text. Can be extended with more options. + /// + public LabelElement AddCopyContextMenu(Action customOptions = null) + { + Label.RightClick += OnRightClick; + _customContextualOptions = customOptions; + return this; + } + + private void OnRightClick() + { + var menu = new ContextMenu(); + menu.AddButton("Copy text").Clicked += OnCopyText; + _customContextualOptions?.Invoke(menu); + menu.Show(Label, Label.PointFromScreen(Input.MouseScreenPosition)); + } + + private void OnCopyText() + { + Clipboard.Text = Label.Text; + } + /// public override Control Control => Label; } diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 29d7cf359..e96fee420 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -133,11 +133,13 @@ namespace FlaxEditor.CustomEditors /// Adds new button element. /// /// The text. + /// The tooltip text. /// The created element. - public ButtonElement Button(string text) + public ButtonElement Button(string text, string tooltip = null) { var element = new ButtonElement(); - element.Init(text); + element.Button.Text = text; + element.Button.TooltipText = tooltip; OnAddElement(element); return element; } @@ -147,11 +149,14 @@ namespace FlaxEditor.CustomEditors /// /// The text. /// The color. + /// The tooltip text. /// The created element. - public ButtonElement Button(string text, Color color) + public ButtonElement Button(string text, Color color, string tooltip = null) { ButtonElement element = new ButtonElement(); - element.Init(text, color); + element.Button.Text = text; + element.Button.TooltipText = tooltip; + element.Button.SetColors(color); OnAddElement(element); return element; } diff --git a/Source/Editor/GUI/EnumComboBox.cs b/Source/Editor/GUI/EnumComboBox.cs index 27563bd7a..24f818d0d 100644 --- a/Source/Editor/GUI/EnumComboBox.cs +++ b/Source/Editor/GUI/EnumComboBox.cs @@ -261,7 +261,7 @@ namespace FlaxEditor.GUI switch (formatMode) { case EnumDisplayAttribute.FormatMode.Default: - name = CustomEditorsUtil.GetPropertyNameUI(field.Name); + name = Utilities.Utils.GetPropertyNameUI(field.Name); break; case EnumDisplayAttribute.FormatMode.None: name = field.Name; diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 1c62a6556..3bfe912c4 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -263,6 +263,19 @@ namespace FlaxEditor.GUI PerformLayout(true); _searchBox.Focus(); } + + /// + /// Sorts the items list (by item name by default). + /// + public void SortItems() + { + ItemsPanel.SortChildren(); + if (_categoryPanels != null) + { + for (int i = 0; i < _categoryPanels.Count; i++) + _categoryPanels[i].SortChildren(); + } + } /// /// Adds the item to the view and registers for the click event. diff --git a/Source/Editor/GUI/Popups/ActorSearchPopup.cs b/Source/Editor/GUI/Popups/ActorSearchPopup.cs index b5859a52b..92482c18e 100644 --- a/Source/Editor/GUI/Popups/ActorSearchPopup.cs +++ b/Source/Editor/GUI/Popups/ActorSearchPopup.cs @@ -67,7 +67,7 @@ namespace FlaxEditor.GUI { Find(Level.GetScene(i)); } - SortChildren(); + SortItems(); } private void OnItemClicked(Item item) diff --git a/Source/Editor/GUI/Popups/AssetSearchPopup.cs b/Source/Editor/GUI/Popups/AssetSearchPopup.cs index 3f81c2e2a..8f5e007a0 100644 --- a/Source/Editor/GUI/Popups/AssetSearchPopup.cs +++ b/Source/Editor/GUI/Popups/AssetSearchPopup.cs @@ -122,7 +122,7 @@ namespace FlaxEditor.GUI if (project.Content != null) FindAssets(project.Content.Folder); } - SortChildren(); + SortItems(); } private void OnItemClicked(Item item) diff --git a/Source/Editor/GUI/Popups/ScriptSearchPopup.cs b/Source/Editor/GUI/Popups/ScriptSearchPopup.cs index d8c1e9ee1..ece346895 100644 --- a/Source/Editor/GUI/Popups/ScriptSearchPopup.cs +++ b/Source/Editor/GUI/Popups/ScriptSearchPopup.cs @@ -78,7 +78,7 @@ namespace FlaxEditor.GUI { Find(Level.GetScene(i)); } - SortChildren(); + SortItems(); } private void OnItemClicked(Item item) diff --git a/Source/Editor/GUI/Popups/TypeSearchPopup.cs b/Source/Editor/GUI/Popups/TypeSearchPopup.cs index 9ab0d5b54..40396652b 100644 --- a/Source/Editor/GUI/Popups/TypeSearchPopup.cs +++ b/Source/Editor/GUI/Popups/TypeSearchPopup.cs @@ -95,7 +95,7 @@ namespace FlaxEditor.GUI } } } - SortChildren(); + SortItems(); } private void OnItemClicked(Item item) diff --git a/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs index 8758c7622..9a7f4dfed 100644 --- a/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs @@ -177,7 +177,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks if (SubTracks.Any(x => x is IObjectTrack y && y.Object == script)) continue; - var name = CustomEditorsUtil.GetPropertyNameUI(script.GetType().Name); + var name = Utilities.Utils.GetPropertyNameUI(script.GetType().Name); menu.AddButton(name, OnAddScriptTrack).Tag = script; } } diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 248aa757f..acfe71ad6 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -29,6 +29,7 @@ #include "Engine/Level/Actor.h" #include "Engine/Level/Prefabs/Prefab.h" #include "Engine/Core/Config/GameSettings.h" +#include "Engine/Core/Config/GraphicsSettings.h" #include "Engine/Core/Cache.h" #include "Engine/CSG/CSGBuilder.h" #include "Engine/Debug/DebugLog.h" @@ -196,6 +197,10 @@ struct InternalModelOptions byte ImportTextures; byte RestoreMaterialsOnReimport; + // SDF + byte GenerateSDF; + float SDFResolution; + // Splitting byte SplitObjects; int32 ObjectIndex; @@ -235,6 +240,8 @@ struct InternalModelOptions to->ImportMaterials = from->ImportMaterials; to->ImportTextures = from->ImportTextures; to->RestoreMaterialsOnReimport = from->RestoreMaterialsOnReimport; + to->GenerateSDF = from->GenerateSDF; + to->SDFResolution = from->SDFResolution; to->SplitObjects = from->SplitObjects; to->ObjectIndex = from->ObjectIndex; } @@ -274,6 +281,8 @@ struct InternalModelOptions to->ImportMaterials = from->ImportMaterials; to->ImportTextures = from->ImportTextures; to->RestoreMaterialsOnReimport = from->RestoreMaterialsOnReimport; + to->GenerateSDF = from->GenerateSDF; + to->SDFResolution = from->SDFResolution; to->SplitObjects = from->SplitObjects; to->ObjectIndex = from->ObjectIndex; } @@ -626,22 +635,23 @@ public: return false; } - static bool GetModelImportOptions(MonoString* pathObj, InternalModelOptions* result) + static void GetModelImportOptions(MonoString* pathObj, InternalModelOptions* result) { + // Initialize defaults + ImportModelFile::Options options; + if (const auto* graphicsSettings = GraphicsSettings::Get()) + { + options.GenerateSDF = graphicsSettings->GenerateSDFOnModelImport; + } + + // Get options from model String path; MUtils::ToString(pathObj, path); FileSystem::NormalizePath(path); + ImportModelFile::TryGetImportOptions(path, options); - ImportModelFile::Options options; - if (ImportModelFile::TryGetImportOptions(path, options)) - { - // Convert into managed storage - InternalModelOptions::Convert(&options, result); - - return true; - } - - return false; + // Convert into managed storage + InternalModelOptions::Convert(&options, result); } static bool GetAudioImportOptions(MonoString* pathObj, InternalAudioOptions* result) @@ -852,7 +862,7 @@ public: continue; switch (e.Type) { - // Keyboard events + // Keyboard events case InputDevice::EventType::Char: window->OnCharInput(e.CharData.Char); break; @@ -862,7 +872,7 @@ public: case InputDevice::EventType::KeyUp: window->OnKeyUp(e.KeyData.Key); break; - // Mouse events + // Mouse events case InputDevice::EventType::MouseDown: window->OnMouseDown(window->ScreenToClient(e.MouseData.Position), e.MouseData.Button); break; diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs index 3f8d8012f..7042dfb92 100644 --- a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs +++ b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs @@ -27,6 +27,17 @@ namespace FlaxEditor.Modules.SourceCodeEditing { } + /// + /// Gets the tooltip text for the type. + /// + /// The type. + /// The type attributes. Optional, if null type attributes will be used. + /// The documentation tooltip. + public string GetTooltip(Type type, object[] attributes = null) + { + return GetTooltip(new ScriptType(type), attributes); + } + /// /// Gets the tooltip text for the type. /// @@ -62,6 +73,19 @@ namespace FlaxEditor.Modules.SourceCodeEditing return text; } + /// + /// Gets the tooltip text for the type member. + /// + /// The type. + /// The member name. + /// The member attributes. Optional, if null member attributes will be used. + /// The documentation tooltip. + public string GetTooltip(Type type, string memberName, object[] attributes = null) + { + var member = new ScriptType(type).GetMember(memberName, MemberTypes.All, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); + return GetTooltip(member, attributes); + } + /// /// Gets the tooltip text for the type member. /// diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 3d1cc129e..50ffad223 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -58,6 +58,7 @@ namespace FlaxEditor.Modules private ContextMenuButton _menuToolsBakeAllEnvProbes; private ContextMenuButton _menuToolsBuildCSGMesh; private ContextMenuButton _menuToolsBuildNavMesh; + private ContextMenuButton _menuToolsBuildAllMesgesSDF; private ContextMenuButton _menuToolsCancelBuilding; private ContextMenuButton _menuToolsSetTheCurrentSceneViewAsDefault; private ContextMenuChildMenu _menuWindowApplyWindowLayout; @@ -484,6 +485,7 @@ namespace FlaxEditor.Modules _menuToolsBakeAllEnvProbes = cm.AddButton("Bake all env probes", BakeAllEnvProbes); _menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", BuildCSG); _menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", BuildNavMesh); + _menuToolsBuildAllMesgesSDF = cm.AddButton("Build all meshes SDF", BuildAllMeshesSDF); cm.AddSeparator(); cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow); _menuToolsCancelBuilding = cm.AddButton("Cancel building game", () => GameCooker.Cancel()); @@ -709,6 +711,7 @@ namespace FlaxEditor.Modules _menuToolsBakeLightmaps.Text = isBakingLightmaps ? "Cancel baking lightmaps" : "Bake lightmaps"; _menuToolsClearLightmaps.Enabled = canEdit; _menuToolsBakeAllEnvProbes.Enabled = canEdit; + _menuToolsBuildAllMesgesSDF.Enabled = canEdit && !isBakingLightmaps; _menuToolsBuildCSGMesh.Enabled = canEdit; _menuToolsBuildNavMesh.Enabled = canEdit; _menuToolsCancelBuilding.Enabled = GameCooker.IsRunning; @@ -836,6 +839,24 @@ namespace FlaxEditor.Modules Editor.Scene.MarkSceneEdited(scenes); } + private void BuildAllMeshesSDF() + { + // TODO: async maybe with progress reporting? + Editor.Scene.ExecuteOnGraph(node => + { + if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel) + { + if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && staticModel.Model != null && !staticModel.Model.IsVirtual && staticModel.Model.SDF.Texture == null) + { + Editor.Log("Generating SDF for " + staticModel.Model); + if (!staticModel.Model.GenerateSDF()) + staticModel.Model.Save(); + } + } + return true; + }); + } + private void SetTheCurrentSceneViewAsDefault() { var projectInfo = Editor.GameProject; diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs index d492f8be2..9cf719122 100644 --- a/Source/Editor/Surface/Archetypes/Material.cs +++ b/Source/Editor/Surface/Archetypes/Material.cs @@ -236,6 +236,39 @@ namespace FlaxEditor.Surface.Archetypes } } + internal enum MaterialTemplateInputsMapping + { + /// + /// Constant buffers. + /// + Constants = 1, + + /// + /// Shader resources such as textures and buffers. + /// + ShaderResources = 2, + + /// + /// Pre-processor definitions. + /// + Defines = 3, + + /// + /// Included files. + /// + Includes = 7, + + /// + /// Default location after all shader resources and methods but before actual material code. + /// + Utilities = 8, + + /// + /// Shader functions location after all material shaders. + /// + Shaders = 9, + } + /// /// The nodes for that group. /// @@ -814,17 +847,20 @@ namespace FlaxEditor.Surface.Archetypes Title = "Custom Global Code", Description = "Custom global HLSL shader code expression (placed before material shader code). Can contain includes to shader utilities or declare functions to reuse later.", Flags = NodeFlags.MaterialGraph, - Size = new Vector2(300, 220), + Size = new Vector2(300, 240), DefaultValues = new object[] { "// Here you can add HLSL code\nfloat4 GetCustomColor()\n{\n\treturn float4(1, 0, 0, 1);\n}", true, + (int)MaterialTemplateInputsMapping.Utilities, }, Elements = new[] { NodeElementArchetype.Factory.Bool(0, 0, 1), NodeElementArchetype.Factory.Text(20, 0, "Enabled"), - NodeElementArchetype.Factory.TextBox(0, 20, 300, 200, 0), + NodeElementArchetype.Factory.Text(0, 20, "Location"), + NodeElementArchetype.Factory.Enum(50, 20, 120, 2, typeof(MaterialTemplateInputsMapping)), + NodeElementArchetype.Factory.TextBox(0, 40, 300, 200, 0), } }, }; diff --git a/Source/Editor/Surface/Archetypes/Math.cs b/Source/Editor/Surface/Archetypes/Math.cs index 2bfb3e816..b9d4098bc 100644 --- a/Source/Editor/Surface/Archetypes/Math.cs +++ b/Source/Editor/Surface/Archetypes/Math.cs @@ -427,6 +427,20 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 4), } }, + new NodeArchetype + { + TypeID = 49, + Title = "Rotate Vector", + Description = "Rotates given vector using the Quaternion", + Flags = NodeFlags.AllGraphs, + Size = new Vector2(200, 40), + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Quaternion", true, typeof(Quaternion), 0), + NodeElementArchetype.Factory.Input(1, "Vector", true, typeof(Vector3), 1), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Vector3), 2), + } + }, }; } } diff --git a/Source/Editor/Surface/Archetypes/ParticleModules.cs b/Source/Editor/Surface/Archetypes/ParticleModules.cs index 9c4da4b8e..0cb9d9466 100644 --- a/Source/Editor/Surface/Archetypes/ParticleModules.cs +++ b/Source/Editor/Surface/Archetypes/ParticleModules.cs @@ -888,7 +888,6 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.ComboBox(0, -10.0f, 160, 2, typeof(ParticleModelFacingMode)), }, }, - new NodeArchetype { TypeID = 214, @@ -912,6 +911,20 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(-0.5f + 2.0f, "Velocity Scale", true, typeof(float), 2, 4), }, }, + new NodeArchetype + { + TypeID = 215, + Create = CreateParticleModuleNode, + Title = "Position (Global SDF)", + Description = "Places the particles on Global SDF surface (uses current particle position to snap it to SDF)", + Flags = DefaultModuleFlags, + Size = new Vector2(200, 0 * Surface.Constants.LayoutOffsetY), + DefaultValues = new object[] + { + true, + (int)ModuleType.Initialize, + }, + }, GetParticleAttribute(ModuleType.Initialize, 250, "Set Position", "Sets the particle position", typeof(Vector3), Vector3.Zero), GetParticleAttribute(ModuleType.Initialize, 251, "Set Lifetime", "Sets the particle lifetime (in seconds)", typeof(float), 10.0f), GetParticleAttribute(ModuleType.Initialize, 252, "Set Age", "Sets the particle age (in seconds)", typeof(float), 0.0f), @@ -1344,7 +1357,7 @@ namespace FlaxEditor.Surface.Archetypes true, (int)ModuleType.Update, false, // Invert - 0.0f, // Radius + 5.0f, // Radius 0.0f, // Roughness 0.1f, // Elasticity 0.0f, // Friction @@ -1362,6 +1375,59 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(-0.5f + 0, "Surface Thickness", true, typeof(float), 5, 8), }, }, + new NodeArchetype + { + TypeID = 335, + Create = CreateParticleModuleNode, + Title = "Conform to Global SDF", + Description = "Applies the force vector to particles to conform around Global SDF", + Flags = DefaultModuleFlags, + Size = new Vector2(200, 4 * Surface.Constants.LayoutOffsetY), + DefaultValues = new object[] + { + true, + (int)ModuleType.Update, + 5.0f, + 2000.0f, + 1.0f, + 5000.0f, + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(-0.5f, "Attraction Speed", true, typeof(float), 0, 2), + NodeElementArchetype.Factory.Input(-0.5f + 1.0f, "Attraction Force", true, typeof(float), 1, 3), + NodeElementArchetype.Factory.Input(-0.5f + 2.0f, "Stick Distance", true, typeof(float), 2, 4), + NodeElementArchetype.Factory.Input(-0.5f + 3.0f, "Stick Force", true, typeof(float), 3, 5), + }, + }, + new NodeArchetype + { + TypeID = 336, + Create = CreateParticleModuleNode, + Title = "Collision (Global SDF)", + Description = "Collides particles with the scene Global SDF", + Flags = DefaultModuleFlags, + Size = new Vector2(200, 5 * Surface.Constants.LayoutOffsetY), + DefaultValues = new object[] + { + true, + (int)ModuleType.Update, + false, // Invert + 5.0f, // Radius + 0.4f, // Roughness + 0.1f, // Elasticity + 0.0f, // Friction + 0.0f, // Lifetime Loss + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(-0.5f + 0, "Radius", true, typeof(float), 0, 3), + NodeElementArchetype.Factory.Input(-0.5f + 1, "Roughness", true, typeof(float), 1, 4), + NodeElementArchetype.Factory.Input(-0.5f + 2, "Elasticity", true, typeof(float), 2, 5), + NodeElementArchetype.Factory.Input(-0.5f + 3, "Friction", true, typeof(float), 3, 6), + NodeElementArchetype.Factory.Input(-0.5f + 4, "Lifetime Loss", true, typeof(float), 4, 7), + }, + }, GetParticleAttribute(ModuleType.Update, 350, "Set Position", "Sets the particle position", typeof(Vector3), Vector3.Zero), GetParticleAttribute(ModuleType.Update, 351, "Set Lifetime", "Sets the particle lifetime (in seconds)", typeof(float), 10.0f), GetParticleAttribute(ModuleType.Update, 352, "Set Age", "Sets the particle age (in seconds)", typeof(float), 0.0f), diff --git a/Source/Editor/Surface/Archetypes/Particles.cs b/Source/Editor/Surface/Archetypes/Particles.cs index a0714f15e..1ff45308d 100644 --- a/Source/Editor/Surface/Archetypes/Particles.cs +++ b/Source/Editor/Surface/Archetypes/Particles.cs @@ -126,7 +126,7 @@ namespace FlaxEditor.Surface.Archetypes }); } cm.ItemClicked += item => AddModule((ushort)item.Tag); - cm.SortChildren(); + cm.SortItems(); cm.Show(this, button.BottomLeft); } diff --git a/Source/Editor/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs index a3974c740..044ee6430 100644 --- a/Source/Editor/Surface/Archetypes/Textures.cs +++ b/Source/Editor/Surface/Archetypes/Textures.cs @@ -358,6 +358,33 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(1, "Location", true, null, 2), } }, + new NodeArchetype + { + TypeID = 14, + Title = "Sample Global SDF", + Description = "Samples the Global SDF to get the distance to the closest surface (in world-space). Requires models SDF to be generated and checking `Enable Global SDF` in Graphics Settings.", + Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph, + Size = new Vector2(200, 20), + Elements = new[] + { + NodeElementArchetype.Factory.Output(0, "Distance", typeof(float), 0), + NodeElementArchetype.Factory.Input(0, "World Position", true, typeof(Vector3), 1), + } + }, + new NodeArchetype + { + TypeID = 15, + Title = "Sample Global SDF Gradient", + Description = "Samples the Global SDF to get the gradient and distance to the closest surface (in world-space). Normalize gradient to get SDF surface normal vector. Requires models SDF to be generated and checking `Enable Global SDF` in Graphics Settings.", + Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph, + Size = new Vector2(260, 40), + Elements = new[] + { + NodeElementArchetype.Factory.Output(0, "Gradient", typeof(Vector3), 0), + NodeElementArchetype.Factory.Output(1, "Distance", typeof(float), 2), + NodeElementArchetype.Factory.Input(0, "World Position", true, typeof(Vector3), 1), + } + }, }; } } diff --git a/Source/Editor/Surface/AttributesEditor.cs b/Source/Editor/Surface/AttributesEditor.cs index 74bfe2e25..6de6d60da 100644 --- a/Source/Editor/Surface/AttributesEditor.cs +++ b/Source/Editor/Surface/AttributesEditor.cs @@ -41,7 +41,7 @@ namespace FlaxEditor.Surface for (int i = 0; i < options.Count; i++) { var type = options[i]; - _options[i] = new OptionType(CustomEditorsUtil.GetPropertyNameUI(type.Name), type, Creator); + _options[i] = new OptionType(Utilities.Utils.GetPropertyNameUI(type.Name), type, Creator); } base.Initialize(layout); diff --git a/Source/Editor/Surface/NodeElementArchetype.cs b/Source/Editor/Surface/NodeElementArchetype.cs index d5a8ea122..d365668ea 100644 --- a/Source/Editor/Surface/NodeElementArchetype.cs +++ b/Source/Editor/Surface/NodeElementArchetype.cs @@ -457,7 +457,7 @@ namespace FlaxEditor.Surface if (field.Name.Equals("value__")) continue; - var name = CustomEditorsUtil.GetPropertyNameUI(field.Name); + var name = Utilities.Utils.GetPropertyNameUI(field.Name); values.Add(name); } return ComboBox(x, y, width, valueIndex, values.ToArray()); diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs index 583a12833..9664e890e 100644 --- a/Source/Editor/Surface/VisjectSurfaceWindow.cs +++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs @@ -466,7 +466,7 @@ namespace FlaxEditor.Surface cm.AddItem(item); } cm.ItemClicked += OnAddParameterItemClicked; - cm.SortChildren(); + cm.SortItems(); cm.Show(button.Parent, button.BottomLeft); } diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 1960951d5..28de0c415 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Reflection; using System.Runtime.InteropServices; +using System.Text; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Tree; using FlaxEditor.SceneGraph; @@ -28,6 +29,8 @@ namespace FlaxEditor.Utilities /// public static class Utils { + private static readonly StringBuilder CachedSb = new StringBuilder(256); + private static readonly string[] MemorySizePostfixes = { "B", @@ -863,6 +866,62 @@ namespace FlaxEditor.Utilities } } + /// + /// Gets the property name for UI. Removes unnecessary characters and filters text. Makes it more user-friendly. + /// + /// The name. + /// The result. + public static string GetPropertyNameUI(string name) + { + int length = name.Length; + StringBuilder sb = CachedSb; + sb.Clear(); + sb.EnsureCapacity(length + 8); + int startIndex = 0; + + // Skip some prefixes + if (name.StartsWith("g_") || name.StartsWith("m_")) + startIndex = 2; + + // Filter text + var lastChar = '\0'; + for (int i = startIndex; i < length; i++) + { + var c = name[i]; + + // Space before word starting with uppercase letter + if (char.IsUpper(c) && i > 0) + { + if (lastChar != ' ' && (!char.IsUpper(name[i - 1]) || (i + 1 != length && !char.IsUpper(name[i + 1])))) + { + lastChar = ' '; + sb.Append(lastChar); + } + } + // Space instead of underscore + else if (c == '_') + { + if (sb.Length > 0 && lastChar != ' ') + { + lastChar = ' '; + sb.Append(lastChar); + } + continue; + } + // Space before digits sequence + else if (i > 1 && char.IsDigit(c) && !char.IsDigit(name[i - 1]) && lastChar != ' ') + { + lastChar = ' '; + sb.Append(lastChar); + } + + lastChar = c; + sb.Append(lastChar); + } + + return sb.ToString(); + } + /// /// Creates the search popup with a tree. /// diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 548da2e56..bf78dea5b 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -1407,6 +1407,8 @@ namespace FlaxEditor.Viewport new ViewModeOptions(ViewMode.LODPreview, "LOD Preview"), new ViewModeOptions(ViewMode.MaterialComplexity, "Material Complexity"), new ViewModeOptions(ViewMode.QuadOverdraw, "Quad Overdraw"), + new ViewModeOptions(ViewMode.GlobalSDF, "Global SDF"), + new ViewModeOptions(ViewMode.GlobalSurfaceAtlas, "Global Surface Atlas"), }; private void WidgetCamSpeedShowHide(Control cm) diff --git a/Source/Editor/Windows/Assets/CubeTextureWindow.cs b/Source/Editor/Windows/Assets/CubeTextureWindow.cs index 278dd6f3a..845b40ee1 100644 --- a/Source/Editor/Windows/Assets/CubeTextureWindow.cs +++ b/Source/Editor/Windows/Assets/CubeTextureWindow.cs @@ -46,7 +46,7 @@ namespace FlaxEditor.Windows.Assets var group = layout.Group("General"); group.Label("Format: " + texture.Format); - group.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height)); + group.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height)).AddCopyContextMenu(); group.Label("Mip levels: " + texture.MipLevels); group.Label("Memory usage: " + Utilities.Utils.FormatBytesCount(texture.TotalMemoryUsage)); } diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index 28fbe9e98..f3197660c 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Windows.Assets PreviewLight.ShadowsDistance = 2000.0f; Task.ViewFlags |= ViewFlags.Shadows; } - + public override void Draw() { base.Draw(); @@ -75,10 +75,7 @@ namespace FlaxEditor.Windows.Assets base.OnClean(); } - /// - /// Updates the highlight/isolate effects on UI. - /// - public void UpdateEffectsOnUI() + private void UpdateEffectsOnUI() { Window._skipEffectsGuiEvents = true; @@ -97,10 +94,7 @@ namespace FlaxEditor.Windows.Assets Window._skipEffectsGuiEvents = false; } - /// - /// Updates the material slots UI parts. Should be called after material slot rename. - /// - public void UpdateMaterialSlotsUI() + private void UpdateMaterialSlotsUI() { Window._skipEffectsGuiEvents = true; @@ -123,12 +117,7 @@ namespace FlaxEditor.Windows.Assets Window._skipEffectsGuiEvents = false; } - /// - /// Sets the material slot index to the mesh. - /// - /// The mesh. - /// New index of the material slot to use. - public void SetMaterialSlot(Mesh mesh, int newSlotIndex) + private void SetMaterialSlot(Mesh mesh, int newSlotIndex) { if (Window._skipEffectsGuiEvents) return; @@ -139,11 +128,7 @@ namespace FlaxEditor.Windows.Assets Window.MarkAsEdited(); } - /// - /// Sets the material slot to isolate. - /// - /// The mesh. - public void SetIsolate(Mesh mesh) + private void SetIsolate(Mesh mesh) { if (Window._skipEffectsGuiEvents) return; @@ -153,11 +138,7 @@ namespace FlaxEditor.Windows.Assets UpdateEffectsOnUI(); } - /// - /// Sets the material slot index to highlight. - /// - /// The mesh. - public void SetHighlight(Mesh mesh) + private void SetHighlight(Mesh mesh) { if (Window._skipEffectsGuiEvents) return; @@ -169,6 +150,8 @@ namespace FlaxEditor.Windows.Assets private class ProxyEditor : ProxyEditorBase { + private CustomEditors.Elements.IntegerValueElement _sdfModelLodIndex; + public override void Initialize(LayoutElementsContainer layout) { var proxy = (MeshesPropertiesProxy)Values[0]; @@ -191,13 +174,60 @@ namespace FlaxEditor.Windows.Assets minScreenSize.FloatValue.MinValue = 0.0f; minScreenSize.FloatValue.MaxValue = 1.0f; minScreenSize.FloatValue.Value = proxy.Asset.MinScreenSize; - minScreenSize.FloatValue.ValueChanged += () => + minScreenSize.FloatValue.BoxValueChanged += b => { - proxy.Asset.MinScreenSize = minScreenSize.FloatValue.Value; + proxy.Asset.MinScreenSize = b.Value; proxy.Window.MarkAsEdited(); }; } + // SDF + { + var group = layout.Group("SDF"); + + var sdf = proxy.Asset.SDF; + if (sdf.Texture != null) + { + var size = sdf.Texture.Size3; + group.Label($"SDF Texture {size.X}x{size.Y}x{size.Z} ({Utilities.Utils.FormatBytesCount(sdf.Texture.MemoryUsage)})").AddCopyContextMenu(); + } + else + { + group.Label("No SDF"); + } + + var resolution = group.FloatValue("Resolution Scale", proxy.Window.Editor.CodeDocs.GetTooltip(typeof(ModelImportSettings), nameof(ModelImportSettings.SDFResolution))); + resolution.FloatValue.MinValue = 0.0001f; + resolution.FloatValue.MaxValue = 100.0f; + resolution.FloatValue.Value = sdf.Texture != null ? sdf.ResolutionScale : 1.0f; + resolution.FloatValue.BoxValueChanged += b => { proxy.Window._importSettings.SDFResolution = b.Value; }; + proxy.Window._importSettings.SDFResolution = sdf.ResolutionScale; + + var backfacesThreshold = group.FloatValue("Backfaces Threshold", "Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh."); + backfacesThreshold.FloatValue.MinValue = 0.001f; + backfacesThreshold.FloatValue.MaxValue = 1.0f; + backfacesThreshold.FloatValue.Value = proxy.Window._backfacesThreshold; + backfacesThreshold.FloatValue.BoxValueChanged += b => { proxy.Window._backfacesThreshold = b.Value; }; + + var lodIndex = group.IntegerValue("LOD Index", "Index of the model Level of Detail to use for SDF data building. By default uses the lowest quality LOD for fast building."); + lodIndex.IntValue.MinValue = 0; + lodIndex.IntValue.MaxValue = lods.Length - 1; + lodIndex.IntValue.Value = sdf.Texture != null ? sdf.LOD : 6; + _sdfModelLodIndex = lodIndex; + + var buttons = group.CustomContainer(); + var gridControl = buttons.CustomControl; + gridControl.ClipChildren = false; + gridControl.Height = Button.DefaultHeight; + gridControl.SlotsHorizontally = 2; + gridControl.SlotsVertically = 1; + var rebuildButton = buttons.Button("Rebuild", "Rebuilds the model SDF.").Button; + rebuildButton.Clicked += OnRebuildSDF; + var removeButton = buttons.Button("Remove", "Removes the model SDF data from the asset.").Button; + removeButton.Enabled = sdf.Texture != null; + removeButton.Clicked += OnRemoveSDF; + } + // Group per LOD for (int lodIndex = 0; lodIndex < lods.Length; lodIndex++) { @@ -218,15 +248,15 @@ namespace FlaxEditor.Windows.Assets vertexCount += mesh.VertexCount; } - group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)); - group.Label("Size: " + lod.Box.Size); + group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)).AddCopyContextMenu(); + group.Label("Size: " + lod.Box.Size).AddCopyContextMenu(); var screenSize = group.FloatValue("Screen Size", "The screen size to switch LODs. Bottom limit of the model screen size to render this LOD."); screenSize.FloatValue.MinValue = 0.0f; screenSize.FloatValue.MaxValue = 10.0f; screenSize.FloatValue.Value = lod.ScreenSize; - screenSize.FloatValue.ValueChanged += () => + screenSize.FloatValue.BoxValueChanged += b => { - lod.ScreenSize = screenSize.FloatValue.Value; + lod.ScreenSize = b.Value; proxy.Window.MarkAsEdited(); }; @@ -234,7 +264,7 @@ namespace FlaxEditor.Windows.Assets for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++) { var mesh = meshes[meshIndex]; - group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, verts: {mesh.VertexCount:N0})"); + group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, verts: {mesh.VertexCount:N0})").AddCopyContextMenu(); // Material Slot var materialSlot = group.ComboBox("Material Slot", "Material slot used by this mesh during rendering"); @@ -260,6 +290,22 @@ namespace FlaxEditor.Windows.Assets proxy.UpdateMaterialSlotsUI(); } + private void OnRebuildSDF() + { + var proxy = (MeshesPropertiesProxy)Values[0]; + proxy.Asset.GenerateSDF(proxy.Window._importSettings.SDFResolution, _sdfModelLodIndex.Value, true, proxy.Window._backfacesThreshold); + proxy.Window.MarkAsEdited(); + Presenter.BuildLayoutOnUpdate(); + } + + private void OnRemoveSDF() + { + var proxy = (MeshesPropertiesProxy)Values[0]; + proxy.Asset.SetSDF(new ModelBase.SDFData()); + proxy.Window.MarkAsEdited(); + Presenter.BuildLayoutOnUpdate(); + } + internal override void RefreshInternal() { // Skip updates when model is not loaded @@ -645,14 +691,14 @@ namespace FlaxEditor.Windows.Assets [CustomEditor(typeof(ProxyEditor))] private sealed class ImportPropertiesProxy : PropertiesProxyBase { - private ModelImportSettings ImportSettings = new ModelImportSettings(); + private ModelImportSettings ImportSettings; /// public override void OnLoad(ModelWindow window) { base.OnLoad(window); - ModelImportSettings.TryRestore(ref ImportSettings, window.Item.Path); + ImportSettings = window._importSettings; } public void Reimport() @@ -675,7 +721,7 @@ namespace FlaxEditor.Windows.Assets { var group = layout.Group("Import Settings"); - var importSettingsField = typeof(ImportPropertiesProxy).GetField("ImportSettings", BindingFlags.NonPublic | BindingFlags.Instance); + var importSettingsField = typeof(ImportPropertiesProxy).GetField(nameof(ImportSettings), BindingFlags.NonPublic | BindingFlags.Instance); var importSettingsValues = new ValueContainer(new ScriptMemberInfo(importSettingsField)) { proxy.ImportSettings }; group.Object(importSettingsValues); @@ -730,6 +776,8 @@ namespace FlaxEditor.Windows.Assets private readonly ModelPreview _preview; private StaticModel _highlightActor; private MeshDataCache _meshData; + private ModelImportSettings _importSettings = new ModelImportSettings(); + private float _backfacesThreshold = 0.6f; /// public ModelWindow(Editor editor, AssetItem item) @@ -868,6 +916,7 @@ namespace FlaxEditor.Windows.Assets { _refreshOnLODsLoaded = true; _preview.ViewportCamera.SetArcBallView(Asset.GetBox()); + ModelImportSettings.TryRestore(ref _importSettings, Item.Path); UpdateEffectsOnAsset(); // TODO: disable streaming for this model diff --git a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs index 1df505bb3..45e2f305d 100644 --- a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs @@ -262,6 +262,9 @@ namespace FlaxEditor.Windows.Assets base.OnSurfaceEditingStart(); } + /// + protected override bool CanEditSurfaceOnAssetLoadError => true; + /// protected override bool SaveToOriginal() { diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index 4927de680..069e6aece 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -219,7 +219,7 @@ namespace FlaxEditor.Windows.Assets vertexCount += mesh.VertexCount; } - group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)); + group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)).AddCopyContextMenu(); group.Label("Size: " + lod.Box.Size); var screenSize = group.FloatValue("Screen Size", "The screen size to switch LODs. Bottom limit of the model screen size to render this LOD."); screenSize.FloatValue.MinValue = 0.0f; @@ -235,7 +235,7 @@ namespace FlaxEditor.Windows.Assets for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++) { var mesh = meshes[meshIndex]; - group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, verts: {mesh.VertexCount:N0})"); + group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, verts: {mesh.VertexCount:N0})").AddCopyContextMenu(); // Material Slot var materialSlot = group.ComboBox("Material Slot", "Material slot used by this mesh during rendering"); diff --git a/Source/Editor/Windows/Assets/TextureWindow.cs b/Source/Editor/Windows/Assets/TextureWindow.cs index 5918de2a9..84c734365 100644 --- a/Source/Editor/Windows/Assets/TextureWindow.cs +++ b/Source/Editor/Windows/Assets/TextureWindow.cs @@ -36,9 +36,9 @@ namespace FlaxEditor.Windows.Assets // Texture info var general = layout.Group("General"); general.Label("Format: " + texture.Format); - general.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height)); + general.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height)).AddCopyContextMenu(); general.Label("Mip levels: " + texture.MipLevels); - general.Label("Memory usage: " + Utilities.Utils.FormatBytesCount(texture.TotalMemoryUsage)); + general.Label("Memory usage: " + Utilities.Utils.FormatBytesCount(texture.TotalMemoryUsage)).AddCopyContextMenu(); // Texture properties var properties = layout.Group("Properties"); diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index 1e17afd81..86908add9 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -258,7 +258,7 @@ namespace FlaxEditor.Windows.Assets cm.AddItem(item); } cm.ItemClicked += itemClicked; - cm.SortChildren(); + cm.SortItems(); cm.Show(window, window.PointFromScreen(Input.MouseScreenPosition)); } diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index 1c95addad..c67c90644 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -280,7 +280,7 @@ namespace FlaxEditor.Windows name = "Mac"; break; default: - name = CustomEditorsUtil.GetPropertyNameUI(_platform.ToString()); + name = Utilities.Utils.GetPropertyNameUI(_platform.ToString()); break; } var group = layout.Group(name); diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 0d47a92b3..2925df386 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -223,7 +223,7 @@ namespace FlaxEditor.Windows if (!QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges)) continue; - var item = _groupSearch.AddChild(CreateActorItem(CustomEditors.CustomEditorsUtil.GetPropertyNameUI(text), actorType)); + var item = _groupSearch.AddChild(CreateActorItem(Utilities.Utils.GetPropertyNameUI(text), actorType)); var highlights = new List(ranges.Length); var style = Style.Current; diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index 05498460a..aa6305aa2 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -324,15 +324,14 @@ void Asset::Reload() Content::AssetReloading(this); OnReloading(this); - // Fire unload event - // TODO: maybe just call release storage ref or sth? we cannot call onUnload because managed asset objects gets invalidated - //onUnload_MainThread(); - ScopeLock lock(Locker); - // Unload current data - unload(true); - _isLoaded = false; + if (IsLoaded()) + { + // Unload current data + unload(true); + _isLoaded = false; + } // Start reloading process startLoading(); diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index b2de85bee..226074a72 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -83,7 +83,7 @@ void Animation::ClearCache() // Free memory MappingCache.Clear(); - MappingCache.Cleanup(); + MappingCache.SetCapacity(0); } const Animation::NodeToChannel* Animation::GetMapping(SkinnedModel* obj) diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index fb6f9dcc7..97679193f 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -48,6 +48,11 @@ const MaterialInfo& Material::GetInfo() const return EmptyInfo; } +GPUShader* Material::GetShader() const +{ + return _materialShader ? _materialShader->GetShader() : nullptr; +} + bool Material::IsReady() const { return _materialShader && _materialShader->IsReady(); diff --git a/Source/Engine/Content/Assets/Material.h b/Source/Engine/Content/Assets/Material.h index a8bf9a629..676680968 100644 --- a/Source/Engine/Content/Assets/Material.h +++ b/Source/Engine/Content/Assets/Material.h @@ -45,6 +45,7 @@ public: // [IMaterial] const MaterialInfo& GetInfo() const override; + GPUShader* GetShader() const override; bool IsReady() const override; DrawPass GetDrawModes() const override; bool CanUseLightmap() const override; diff --git a/Source/Engine/Content/Assets/MaterialInstance.cpp b/Source/Engine/Content/Assets/MaterialInstance.cpp index e0e57c6cb..9d94aeed4 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.cpp +++ b/Source/Engine/Content/Assets/MaterialInstance.cpp @@ -143,6 +143,11 @@ const MaterialInfo& MaterialInstance::GetInfo() const return EmptyInfo; } +GPUShader* MaterialInstance::GetShader() const +{ + return _baseMaterial ? _baseMaterial->GetShader() : nullptr; +} + bool MaterialInstance::IsReady() const { return IsLoaded() && _baseMaterial && _baseMaterial->IsReady(); diff --git a/Source/Engine/Content/Assets/MaterialInstance.h b/Source/Engine/Content/Assets/MaterialInstance.h index f5143aadd..d5df025be 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.h +++ b/Source/Engine/Content/Assets/MaterialInstance.h @@ -59,6 +59,7 @@ public: // [IMaterial] const MaterialInfo& GetInfo() const override; + GPUShader* GetShader() const override; bool IsReady() const override; DrawPass GetDrawModes() const override; bool CanUseLightmap() const override; diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index d41d92f16..a0e994702 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -7,13 +7,22 @@ #include "Engine/Content/WeakAssetReference.h" #include "Engine/Content/Upgraders/ModelAssetUpgrader.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" +#include "Engine/Debug/DebugDraw.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/Models/ModelInstanceEntry.h" #include "Engine/Streaming/StreamingGroup.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/Async/GPUTask.h" +#include "Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h" +#include "Engine/Graphics/Textures/GPUTexture.h" +#include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/DrawCall.h" #include "Engine/Threading/Threading.h" +#include "Engine/Tools/ModelTool/ModelTool.h" +#include "Engine/Tools/ModelTool/MeshAccelerationStructure.h" #if GPU_ENABLE_ASYNC_RESOURCES_CREATION #include "Engine/Threading/ThreadPoolTask.h" #define STREAM_TASK_BASE ThreadPoolTask @@ -37,18 +46,11 @@ REGISTER_BINARY_ASSET_ABSTRACT(ModelBase, "FlaxEngine.ModelBase"); class StreamModelLODTask : public STREAM_TASK_BASE { private: - WeakAssetReference _asset; int32 _lodIndex; FlaxStorage::LockData _dataLock; public: - - /// - /// Init - /// - /// Parent model - /// LOD to stream index StreamModelLODTask(Model* model, int32 lodIndex) : _asset(model) , _lodIndex(lodIndex) @@ -57,23 +59,16 @@ public: } public: - - // [ThreadPoolTask] bool HasReference(Object* resource) const override { return _asset == resource; } -protected: - - // [ThreadPoolTask] bool Run() override { AssetReference model = _asset.Get(); if (model == nullptr) - { return true; - } // Get data BytesContainer data; @@ -96,6 +91,7 @@ protected: // Update residency level model->_loadedLODs++; + model->ResidencyChanged(); return false; } @@ -116,11 +112,54 @@ protected: } }; +class StreamModelSDFTask : public GPUUploadTextureMipTask +{ +private: + WeakAssetReference _asset; + FlaxStorage::LockData _dataLock; + +public: + StreamModelSDFTask(Model* model, GPUTexture* texture, const Span& data, int32 mipIndex, int32 rowPitch, int32 slicePitch) + : GPUUploadTextureMipTask(texture, mipIndex, data, rowPitch, slicePitch, false) + , _asset(model) + , _dataLock(model->Storage->Lock()) + { + } + + bool HasReference(Object* resource) const override + { + return _asset == resource; + } + + Result run(GPUTasksContext* context) override + { + AssetReference model = _asset.Get(); + if (model == nullptr) + return Result::MissingResources; + return GPUUploadTextureMipTask::run(context); + } + + void OnEnd() override + { + _dataLock.Release(); + + // Base + GPUUploadTextureMipTask::OnEnd(); + } +}; + REGISTER_BINARY_ASSET_WITH_UPGRADER(Model, "FlaxEngine.Model", ModelAssetUpgrader, true); +static byte EnableModelSDF = 0; + Model::Model(const SpawnParams& params, const AssetInfo* info) : ModelBase(params, info, StreamingGroups::Instance()->Models()) { + if (EnableModelSDF == 0 && GPUDevice::Instance) + { + const bool enable = GPUDevice::Instance->GetFeatureLevel() >= FeatureLevel::SM5; + EnableModelSDF = enable ? 1 : 2; + } } Model::~Model() @@ -183,7 +222,7 @@ void Model::Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info) if (lodIndex == -1) { // Handling model fade-out transition - if (modelFrame == frame && info.DrawState->PrevLOD != -1) + if (modelFrame == frame && info.DrawState->PrevLOD != -1 && !renderContext.View.IsSingleFrame) { // Check if start transition if (info.DrawState->LODTransition == 255) @@ -212,8 +251,11 @@ void Model::Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info) lodIndex += info.LODBias + renderContext.View.ModelLODBias; lodIndex = ClampLODIndex(lodIndex); + if (renderContext.View.IsSingleFrame) + { + } // Check if it's the new frame and could update the drawing state (note: model instance could be rendered many times per frame to different viewports) - if (modelFrame == frame) + else if (modelFrame == frame) { // Check if start transition if (info.DrawState->PrevLOD != lodIndex && info.DrawState->LODTransition == 255) @@ -229,7 +271,7 @@ void Model::Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info) info.DrawState->PrevLOD = lodIndex; } } - // Check if there was a gap between frames in drawing this model instance + // Check if there was a gap between frames in drawing this model instance else if (modelFrame < frame || info.DrawState->PrevLOD == -1) { // Reset state @@ -238,7 +280,7 @@ void Model::Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info) } // Draw - if (info.DrawState->PrevLOD == lodIndex) + if (info.DrawState->PrevLOD == lodIndex || renderContext.View.IsSingleFrame) { LODs[lodIndex].Draw(renderContext, info, 0.0f); } @@ -521,17 +563,50 @@ bool Model::Save(bool withMeshDataFromGpu, const StringView& path) lodChunk->Data.Copy(meshesStream.GetHandle(), meshesStream.GetPosition()); } } + + // Download SDF data + if (SDF.Texture) + { + auto sdfChunk = GET_CHUNK(15); + if (sdfChunk == nullptr) + return true; + MemoryWriteStream sdfStream; + sdfStream.WriteInt32(1); // Version + ModelSDFHeader data(SDF, SDF.Texture->GetDescription()); + sdfStream.Write(&data); + TextureData sdfTextureData; + if (SDF.Texture->DownloadData(sdfTextureData)) + return true; + for (int32 mipLevel = 0; mipLevel < sdfTextureData.Items[0].Mips.Count(); mipLevel++) + { + auto& mip = sdfTextureData.Items[0].Mips[mipLevel]; + ModelSDFMip mipData(mipLevel, mip); + sdfStream.Write(&mipData); + sdfStream.Write(mip.Data.Get(), mip.Data.Length()); + } + sdfChunk->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition()); + } } else { - ASSERT(!IsVirtual()); - // Load all chunks with a mesh data for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++) { if (LoadChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex))) return true; } + + if (SDF.Texture) + { + // SDF data from file (only if has no cached texture data) + if (LoadChunk(15)) + return true; + } + else + { + // No SDF texture + ReleaseChunk(15); + } } // Set mesh header data @@ -560,6 +635,51 @@ bool Model::Save(bool withMeshDataFromGpu, const StringView& path) #endif +bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData, float backfacesThreshold) +{ + if (EnableModelSDF == 2) + return true; // Not supported + ScopeLock lock(Locker); + if (!HasAnyLODInitialized()) + return true; + if (IsInMainThread() && IsVirtual()) + { + // TODO: could be supported if algorithm could run on a GPU and called during rendering + LOG(Warning, "Cannot generate SDF for virtual models on a main thread."); + return true; + } + lodIndex = Math::Clamp(lodIndex, HighestResidentLODIndex(), LODs.Count() - 1); + + // Generate SDF +#if USE_EDITOR + cacheData &= Storage != nullptr; // Cache only if has storage linked + MemoryWriteStream sdfStream; + MemoryWriteStream* outputStream = cacheData ? &sdfStream : nullptr; +#else + class MemoryWriteStream* outputStream = nullptr; +#endif + if (ModelTool::GenerateModelSDF(this, nullptr, resolutionScale, lodIndex, &SDF, outputStream, GetPath(), backfacesThreshold)) + return true; + +#if USE_EDITOR + // Set asset data + if (cacheData) + GetOrCreateChunk(15)->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition()); +#endif + + return false; +} + +void Model::SetSDF(const SDFData& sdf) +{ + ScopeLock lock(Locker); + if (SDF.Texture == sdf.Texture) + return; + SAFE_DELETE_GPU_RESOURCE(SDF.Texture); + SDF = sdf; + ReleaseChunk(15); +} + bool Model::Init(const Span& meshesCountPerLod) { if (meshesCountPerLod.IsInvalid() || meshesCountPerLod.Length() > MODEL_MAX_LODS) @@ -574,14 +694,12 @@ bool Model::Init(const Span& meshesCountPerLod) // Setup MaterialSlots.Resize(1); MinScreenSize = 0.0f; + SAFE_DELETE_GPU_RESOURCE(SDF.Texture); // Setup LODs for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++) - { LODs[lodIndex].Dispose(); - } LODs.Resize(meshesCountPerLod.Length()); - _loadedLODs = meshesCountPerLod.Length(); // Setup meshes for (int32 lodIndex = 0; lodIndex < meshesCountPerLod.Length(); lodIndex++) @@ -600,6 +718,10 @@ bool Model::Init(const Span& meshesCountPerLod) } } + // Update resource residency + _loadedLODs = meshesCountPerLod.Length(); + ResidencyChanged(); + return false; } @@ -716,6 +838,7 @@ Task* Model::CreateStreamingTask(int32 residency) for (int32 i = HighestResidentLODIndex(); i < LODs.Count() - residency; i++) LODs[i].Unload(); _loadedLODs = residency; + ResidencyChanged(); } return result; @@ -810,6 +933,47 @@ Asset::LoadResult Model::load() } } + // Load SDF + auto chunk15 = GetChunk(15); + if (chunk15 && chunk15->IsLoaded() && EnableModelSDF == 1) + { + MemoryReadStream sdfStream(chunk15->Get(), chunk15->Size()); + int32 version; + sdfStream.ReadInt32(&version); + switch (version) + { + case 1: + { + ModelSDFHeader data; + sdfStream.Read(&data); + if (!SDF.Texture) + SDF.Texture = GPUTexture::New(); + if (SDF.Texture->Init(GPUTextureDescription::New3D(data.Width, data.Height, data.Depth, data.Format, GPUTextureFlags::ShaderResource, data.MipLevels))) + return LoadResult::Failed; + SDF.LocalToUVWMul = data.LocalToUVWMul; + SDF.LocalToUVWAdd = data.LocalToUVWAdd; + SDF.WorldUnitsPerVoxel = data.WorldUnitsPerVoxel; + SDF.MaxDistance = data.MaxDistance; + SDF.LocalBoundsMin = data.LocalBoundsMin; + SDF.LocalBoundsMax = data.LocalBoundsMax; + SDF.ResolutionScale = data.ResolutionScale; + SDF.LOD = data.LOD; + for (int32 mipLevel = 0; mipLevel < data.MipLevels; mipLevel++) + { + ModelSDFMip mipData; + sdfStream.Read(&mipData); + void* mipBytes = sdfStream.Read(mipData.SlicePitch); + auto task = ::New(this, SDF.Texture, Span((byte*)mipBytes, mipData.SlicePitch), mipData.MipIndex, mipData.RowPitch, mipData.SlicePitch); + task->Start(); + } + break; + } + default: + LOG(Warning, "Unknown SDF data version {0} in {1}", version, ToString()); + break; + } + } + #if BUILD_DEBUG || BUILD_DEVELOPMENT // Validate LODs for (int32 lodIndex = 1; lodIndex < LODs.Count(); lodIndex++) @@ -840,6 +1004,7 @@ void Model::unload(bool isReloading) } // Cleanup + SAFE_DELETE_GPU_RESOURCE(SDF.Texture); MaterialSlots.Resize(0); for (int32 i = 0; i < LODs.Count(); i++) LODs[i].Dispose(); @@ -862,7 +1027,7 @@ bool Model::init(AssetInitData& initData) AssetChunksFlag Model::getChunksToPreload() const { // Note: we don't preload any LODs here because it's done by the Streaming Manager - return GET_CHUNK_FLAG(0); + return GET_CHUNK_FLAG(0) | GET_CHUNK_FLAG(15); } void ModelBase::SetupMaterialSlots(int32 slotsCount) diff --git a/Source/Engine/Content/Assets/Model.h b/Source/Engine/Content/Assets/Model.h index 6232147ba..6fde4bab3 100644 --- a/Source/Engine/Content/Assets/Model.h +++ b/Source/Engine/Content/Assets/Model.h @@ -28,6 +28,11 @@ public: /// API_FIELD(ReadOnly) Array> LODs; + /// + /// The generated Sign Distant Field (SDF) for this model (merged all meshes). Use GenerateSDF to update it. + /// + API_FIELD(ReadOnly) SDFData SDF; + public: /// @@ -200,6 +205,22 @@ public: API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty); #endif + + /// + /// Generates the Sign Distant Field for this model. + /// + /// Can be called in async in case of SDF generation on a CPU (assuming model is not during rendering). + /// The SDF texture resolution scale. Use higher values for more precise data but with significant performance and memory overhead. + /// The index of the LOD to use for the SDF building. + /// If true, the generated SDF texture data will be cached on CPU (in asset chunk storage) to allow saving it later, otherwise it will be runtime for GPU-only. Ignored for virtual assets or in build. + /// Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh. + /// True if failed, otherwise false. + API_FUNCTION() bool GenerateSDF(float resolutionScale = 1.0f, int32 lodIndex = 6, bool cacheData = true, float backfacesThreshold = 0.6f); + + /// + /// Sets set SDF data (releases the current one). + /// + API_FUNCTION() void SetSDF(const SDFData& sdf); private: diff --git a/Source/Engine/Content/Assets/ModelBase.h b/Source/Engine/Content/Assets/ModelBase.h index c194fd38e..44e8e8242 100644 --- a/Source/Engine/Content/Assets/ModelBase.h +++ b/Source/Engine/Content/Assets/ModelBase.h @@ -14,7 +14,7 @@ // Chunk 1: LOD0 // Chunk 2: LOD1 // .. -// +// Chunk 15: SDF #define MODEL_LOD_TO_CHUNK_INDEX(lod) (lod + 1) class MeshBase; @@ -24,9 +24,62 @@ class MeshBase; /// API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API ModelBase : public BinaryAsset, public StreamableResource { -DECLARE_ASSET_HEADER(ModelBase); -protected: + DECLARE_ASSET_HEADER(ModelBase); +public: + /// + /// The Sign Distant Field (SDF) data for the model. + /// + API_STRUCT() struct SDFData + { + DECLARE_SCRIPTING_TYPE_MINIMAL(SDFData); + /// + /// The SDF volume texture (merged all meshes). + /// + API_FIELD() GPUTexture* Texture = nullptr; + + /// + /// The transformation scale from model local-space to the generated SDF texture space (local-space -> uv). + /// + API_FIELD() Vector3 LocalToUVWMul; + + /// + /// Amount of world-units per SDF texture voxel. + /// + API_FIELD() float WorldUnitsPerVoxel; + + /// + /// The transformation offset from model local-space to the generated SDF texture space (local-space -> uv). + /// + API_FIELD() Vector3 LocalToUVWAdd; + + /// + /// The maximum distance stored in the SDF texture. Used to rescale normalized SDF into world-units (in model local space). + /// + API_FIELD() float MaxDistance; + + /// + /// The bounding box of the SDF texture in the model local-space. + /// + API_FIELD() Vector3 LocalBoundsMin; + + /// + /// The SDF texture resolution scale used for building texture. + /// + API_FIELD() float ResolutionScale = 1.0f; + + /// + /// The bounding box of the SDF texture in the model local-space. + /// + API_FIELD() Vector3 LocalBoundsMax; + + /// + /// The model LOD index used for the building. + /// + API_FIELD() int32 LOD = 6; + }; + +protected: explicit ModelBase(const SpawnParams& params, const AssetInfo* info, StreamingGroup* group) : BinaryAsset(params, info) , StreamableResource(group) @@ -34,7 +87,6 @@ protected: } public: - /// /// The minimum screen size to draw this model (the bottom limit). Used to cull small models. Set to 0 to disable this feature. /// diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 12e940382..f0cff64aa 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -30,13 +30,11 @@ class StreamSkinnedModelLODTask : public ThreadPoolTask { private: - WeakAssetReference _asset; int32 _lodIndex; FlaxStorage::LockData _dataLock; public: - /// /// Init /// @@ -50,7 +48,6 @@ public: } public: - // [ThreadPoolTask] bool HasReference(Object* resource) const override { @@ -58,7 +55,6 @@ public: } protected: - // [ThreadPoolTask] bool Run() override { @@ -89,6 +85,7 @@ protected: // Update residency level model->_loadedLODs++; + model->ResidencyChanged(); return false; } @@ -191,7 +188,7 @@ void SkinnedModel::Draw(RenderContext& renderContext, const SkinnedMesh::DrawInf if (lodIndex == -1) { // Handling model fade-out transition - if (modelFrame == frame && info.DrawState->PrevLOD != -1) + if (modelFrame == frame && info.DrawState->PrevLOD != -1 && !renderContext.View.IsSingleFrame) { // Check if start transition if (info.DrawState->LODTransition == 255) @@ -220,8 +217,11 @@ void SkinnedModel::Draw(RenderContext& renderContext, const SkinnedMesh::DrawInf lodIndex += info.LODBias + renderContext.View.ModelLODBias; lodIndex = ClampLODIndex(lodIndex); + if (renderContext.View.IsSingleFrame) + { + } // Check if it's the new frame and could update the drawing state (note: model instance could be rendered many times per frame to different viewports) - if (modelFrame == frame) + else if (modelFrame == frame) { // Check if start transition if (info.DrawState->PrevLOD != lodIndex && info.DrawState->LODTransition == 255) @@ -237,7 +237,7 @@ void SkinnedModel::Draw(RenderContext& renderContext, const SkinnedMesh::DrawInf info.DrawState->PrevLOD = lodIndex; } } - // Check if there was a gap between frames in drawing this model instance + // Check if there was a gap between frames in drawing this model instance else if (modelFrame < frame || info.DrawState->PrevLOD == -1) { // Reset state @@ -246,7 +246,7 @@ void SkinnedModel::Draw(RenderContext& renderContext, const SkinnedMesh::DrawInf } // Draw - if (info.DrawState->PrevLOD == lodIndex) + if (info.DrawState->PrevLOD == lodIndex || renderContext.View.IsSingleFrame) { LODs[lodIndex].Draw(renderContext, info, 0.0f); } @@ -677,11 +677,8 @@ bool SkinnedModel::Init(const Span& meshesCountPerLod) // Setup LODs for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++) - { LODs[lodIndex].Dispose(); - } LODs.Resize(meshesCountPerLod.Length()); - _loadedLODs = meshesCountPerLod.Length(); // Setup meshes for (int32 lodIndex = 0; lodIndex < meshesCountPerLod.Length(); lodIndex++) @@ -700,6 +697,10 @@ bool SkinnedModel::Init(const Span& meshesCountPerLod) } } + // Update resource residency + _loadedLODs = meshesCountPerLod.Length(); + ResidencyChanged(); + return false; } @@ -828,6 +829,7 @@ Task* SkinnedModel::CreateStreamingTask(int32 residency) for (int32 i = HighestResidentLODIndex(); i < LODs.Count() - residency; i++) LODs[i].Unload(); _loadedLODs = residency; + ResidencyChanged(); } return result; diff --git a/Source/Engine/Content/Content.Build.cs b/Source/Engine/Content/Content.Build.cs index 36608e3a8..501856f02 100644 --- a/Source/Engine/Content/Content.Build.cs +++ b/Source/Engine/Content/Content.Build.cs @@ -18,6 +18,7 @@ public class Content : EngineModule options.PrivateDependencies.Add("lz4"); options.PrivateDependencies.Add("AudioTool"); options.PrivateDependencies.Add("TextureTool"); + options.PrivateDependencies.Add("ModelTool"); options.PrivateDependencies.Add("Particles"); if (options.Target.IsEditor) diff --git a/Source/Engine/ContentImporters/CreateMaterial.cpp b/Source/Engine/ContentImporters/CreateMaterial.cpp index 36caacb23..1dd5e7f05 100644 --- a/Source/Engine/ContentImporters/CreateMaterial.cpp +++ b/Source/Engine/ContentImporters/CreateMaterial.cpp @@ -61,13 +61,13 @@ namespace return &node; } - ShaderGraphNode<>* AddTextureNode(MaterialLayer* layer, const Guid& textureId) + ShaderGraphNode<>* AddTextureNode(MaterialLayer* layer, const Guid& textureId, bool normalMap = false) { if (!textureId.IsValid()) return nullptr; auto& node = layer->Graph.Nodes.AddOne(); node.ID = layer->Graph.Nodes.Count(); - node.Type = GRAPH_NODE_MAKE_TYPE(5, 1); + node.Type = GRAPH_NODE_MAKE_TYPE(5, normalMap ? 4 : 1); node.Boxes.Resize(7); node.Boxes[0] = MaterialGraphBox(&node, 0, VariantType::Vector2); // UVs node.Boxes[6] = MaterialGraphBox(&node, 6, VariantType::Object); // Texture Reference @@ -178,7 +178,7 @@ CreateAssetResult CreateMaterial::Create(CreateAssetContext& context) CONNECT(layer->Root->Boxes[static_cast(MaterialGraphBoxes::Emissive)], emissiveColor->Boxes[0]); SET_POS(emissiveColor, Vector2(-493.5272f, -2.926111f)); } - auto normalMap = AddTextureNode(layer, options.Normals.Texture); + auto normalMap = AddTextureNode(layer, options.Normals.Texture, true); if (normalMap) { CONNECT(layer->Root->Boxes[static_cast(MaterialGraphBoxes::Normal)], normalMap->Boxes[1]); diff --git a/Source/Engine/ContentImporters/ImportModel.h b/Source/Engine/ContentImporters/ImportModel.h index 9e4b521a6..6d49ca826 100644 --- a/Source/Engine/ContentImporters/ImportModel.h +++ b/Source/Engine/ContentImporters/ImportModel.h @@ -48,9 +48,9 @@ public: private: - static CreateAssetResult ImportModel(CreateAssetContext& context, ModelData& modelData); - static CreateAssetResult ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData); - static CreateAssetResult ImportAnimation(CreateAssetContext& context, ModelData& modelData); + static CreateAssetResult ImportModel(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr); + static CreateAssetResult ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr); + static CreateAssetResult ImportAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr); }; #endif diff --git a/Source/Engine/ContentImporters/ImportModelFile.cpp b/Source/Engine/ContentImporters/ImportModelFile.cpp index 3c85084db..cd56f0025 100644 --- a/Source/Engine/ContentImporters/ImportModelFile.cpp +++ b/Source/Engine/ContentImporters/ImportModelFile.cpp @@ -150,13 +150,13 @@ CreateAssetResult ImportModelFile::Import(CreateAssetContext& context) switch (options.Type) { case ModelTool::ModelType::Model: - result = ImportModel(context, modelData); + result = ImportModel(context, modelData, &options); break; case ModelTool::ModelType::SkinnedModel: - result = ImportSkinnedModel(context, modelData); + result = ImportSkinnedModel(context, modelData, &options); break; case ModelTool::ModelType::Animation: - result = ImportAnimation(context, modelData); + result = ImportAnimation(context, modelData, &options); break; } if (result != CreateAssetResult::Ok) @@ -199,7 +199,7 @@ CreateAssetResult ImportModelFile::Create(CreateAssetContext& context) return ImportModel(context, modelData); } -CreateAssetResult ImportModelFile::ImportModel(CreateAssetContext& context, ModelData& modelData) +CreateAssetResult ImportModelFile::ImportModel(CreateAssetContext& context, ModelData& modelData, const Options* options) { // Base IMPORT_SETUP(Model, Model::SerializedVersion); @@ -235,10 +235,22 @@ CreateAssetResult ImportModelFile::ImportModel(CreateAssetContext& context, Mode context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition()); } + // Generate SDF + if (options && options->GenerateSDF) + { + stream.SetPosition(0); + if (!ModelTool::GenerateModelSDF(nullptr, &modelData, options->SDFResolution, lodCount - 1, nullptr, &stream, context.TargetAssetPath)) + { + if (context.AllocateChunk(15)) + return CreateAssetResult::CannotAllocateChunk; + context.Data.Header.Chunks[15]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + } + } + return CreateAssetResult::Ok; } -CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData) +CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options) { // Base IMPORT_SETUP(SkinnedModel, SkinnedModel::SerializedVersion); @@ -277,7 +289,7 @@ CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& contex return CreateAssetResult::Ok; } -CreateAssetResult ImportModelFile::ImportAnimation(CreateAssetContext& context, ModelData& modelData) +CreateAssetResult ImportModelFile::ImportAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options) { // Base IMPORT_SETUP(Animation, Animation::SerializedVersion); diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index ce146fd2b..1979c4e74 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -381,10 +381,10 @@ public: // Insert ASSERT(pos.FreeSlotIndex != -1); - auto bucket = &_allocation.Get()[pos.FreeSlotIndex]; - bucket->Occupy(key); + Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex]; + bucket.Occupy(key); _elementsCount++; - return bucket->Value; + return bucket.Value; } /// @@ -484,7 +484,7 @@ public: #endif void ClearDelete() { - for (auto i = Begin(); i.IsNotEnd(); ++i) + for (Iterator i = Begin(); i.IsNotEnd(); ++i) { if (i->Value) Delete(i->Value); @@ -547,22 +547,15 @@ public: /// Ensures that collection has given capacity. /// /// The minimum required capacity. - void EnsureCapacity(int32 minCapacity) + /// True if preserve collection data when changing its size, otherwise collection after resize will be empty. + void EnsureCapacity(int32 minCapacity, bool preserveContents = true) { if (_size >= minCapacity) return; if (minCapacity < DICTIONARY_DEFAULT_CAPACITY) minCapacity = DICTIONARY_DEFAULT_CAPACITY; const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity); - SetCapacity(capacity); - } - - /// - /// Cleanup collection data (changes size to 0 without data preserving). - /// - FORCE_INLINE void Cleanup() - { - SetCapacity(0, false); + SetCapacity(capacity, preserveContents); } /// @@ -642,7 +635,7 @@ public: void Add(const Iterator& i) { ASSERT(&i._collection != this && i); - Bucket& bucket = *i; + const Bucket& bucket = *i; Add(bucket.Key, bucket.Value); } @@ -655,11 +648,9 @@ public: bool Remove(const KeyComparableType& key) { if (IsEmpty()) - return true; - + return false; FindPositionResult pos; FindPosition(key, pos); - if (pos.ObjectIndex != -1) { _allocation.Get()[pos.ObjectIndex].Delete(); @@ -697,7 +688,7 @@ public: int32 RemoveValue(const ValueType& value) { int32 result = 0; - for (auto i = Begin(); i.IsNotEnd(); ++i) + for (Iterator i = Begin(); i.IsNotEnd(); ++i) { if (i->Value == value) { @@ -718,16 +709,11 @@ public: template Iterator Find(const KeyComparableType& key) const { - if (HasItems()) - { - const Bucket* data = _allocation.Get(); - for (int32 i = 0; i < _size; i++) - { - if (data[i].IsOccupied() && data[i].Key == key) - return Iterator(*this, i); - } - } - return End(); + if (IsEmpty()) + return End(); + FindPositionResult pos; + FindPosition(key, pos); + return pos.ObjectIndex != -1 ? Iterator(*this, pos.ObjectIndex) : End(); } /// @@ -798,7 +784,7 @@ public: { Clear(); SetCapacity(other.Capacity(), false); - for (auto i = other.Begin(); i != other.End(); ++i) + for (Iterator i = other.Begin(); i != other.End(); ++i) Add(i); ASSERT(Count() == other.Count()); ASSERT(Capacity() == other.Capacity()); @@ -811,7 +797,7 @@ public: template void GetKeys(Array& result) const { - for (auto i = Begin(); i.IsNotEnd(); ++i) + for (Iterator i = Begin(); i.IsNotEnd(); ++i) result.Add(i->Key); } @@ -822,7 +808,7 @@ public: template void GetValues(Array& result) const { - for (auto i = Begin(); i.IsNotEnd(); ++i) + for (Iterator i = Begin(); i.IsNotEnd(); ++i) result.Add(i->Value); } @@ -897,7 +883,7 @@ protected: while (checksCount < _size) { // Empty bucket - auto& bucket = data[bucketIndex]; + const Bucket& bucket = data[bucketIndex]; if (bucket.IsEmpty()) { // Found place to insert diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h index 5d105bcae..22bdfe65a 100644 --- a/Source/Engine/Core/Collections/HashSet.h +++ b/Source/Engine/Core/Collections/HashSet.h @@ -2,78 +2,62 @@ #pragma once -#include "Engine/Core/Core.h" -#include "Engine/Core/Math/Math.h" -#include "Engine/Platform/Platform.h" -#include "HashFunctions.h" -#include "Config.h" +#include "Engine/Core/Memory/Memory.h" +#include "Engine/Core/Memory/Allocation.h" +#include "Engine/Core/Collections/HashFunctions.h" +#include "Engine/Core/Collections/Config.h" /// /// Template for unordered set of values (without duplicates with O(1) lookup access). /// /// The type of elements in the set. -template +/// The type of memory allocator. +template API_CLASS(InBuild) class HashSet { friend HashSet; - public: /// - /// Describes single portion of space for the item in a hash map + /// Describes single portion of space for the item in a hash map. /// struct Bucket { friend HashSet; - public: - enum State : byte { Empty, Deleted, Occupied, }; - - public: - + + /// The item. T Item; private: - State _state; - public: - - Bucket() - : _state(Empty) - { - } - - ~Bucket() - { - } - - public: - void Free() { + if (_state == Occupied) + Memory::DestructItem(&Item); _state = Empty; } void Delete() { _state = Deleted; + Memory::DestructItem(&Item); } - void Occupy(const T& item) + template + void Occupy(const ItemType& item) { - Item = item; + Memory::ConstructItems(&Item, &item, 1); _state = Occupied; } - public: - FORCE_INLINE bool IsEmpty() const { return _state == Empty; @@ -94,13 +78,15 @@ public: return _state != Occupied; } }; + + typedef typename AllocationType::template Data AllocationData; private: int32 _elementsCount = 0; int32 _deletedCount = 0; - int32 _tableSize = 0; - Bucket* _table = nullptr; + int32 _size = 0; + AllocationData _allocation; public: @@ -117,10 +103,27 @@ public: /// The initial capacity. HashSet(int32 capacity) { - ASSERT(capacity >= 0); SetCapacity(capacity); } + /// + /// Initializes a new instance of the class. + /// + /// The other collection to move. + HashSet(HashSet&& other) noexcept + : _elementsCount(other._elementsCount) + , _deletedCount(other._deletedCount) + , _size(other._size) + { + _elementsCount = other._elementsCount; + _deletedCount = other._deletedCount; + _size = other._size; + other._elementsCount = 0; + other._deletedCount = 0; + other._size = 0; + _allocation.Swap(other._allocation); + } + /// /// Initializes a new instance of the class. /// @@ -137,18 +140,39 @@ public: /// The reference to this. HashSet& operator=(const HashSet& other) { - // Ensure we're not trying to set to itself if (this != &other) Clone(other); return *this; } + /// + /// Moves the data from the other collection. + /// + /// The other collection to move. + /// The reference to this. + HashSet& operator=(HashSet&& other) noexcept + { + if (this != &other) + { + Clear(); + _allocation.Free(); + _elementsCount = other._elementsCount; + _deletedCount = other._deletedCount; + _size = other._size; + other._elementsCount = 0; + other._deletedCount = 0; + other._size = 0; + _allocation.Swap(other._allocation); + } + return *this; + } + /// /// Finalizes an instance of the class. /// ~HashSet() { - Cleanup(); + SetCapacity(0, false); } public: @@ -156,7 +180,6 @@ public: /// /// Gets the amount of the elements in the collection. /// - /// The amount of elements in the collection. FORCE_INLINE int32 Count() const { return _elementsCount; @@ -165,16 +188,14 @@ public: /// /// Gets the amount of the elements that can be contained by the collection. /// - /// The capacity of the collection. FORCE_INLINE int32 Capacity() const { - return _tableSize; + return _size; } /// /// Returns true if collection is empty. /// - /// True if is empty, otherwise false. FORCE_INLINE bool IsEmpty() const { return _elementsCount == 0; @@ -183,7 +204,6 @@ public: /// /// Returns true if collection has one or more elements. /// - /// True if isn't empty, otherwise false. FORCE_INLINE bool HasItems() const { return _elementsCount != 0; @@ -197,9 +217,7 @@ public: struct Iterator { friend HashSet; - private: - HashSet& _collection; int32 _index; @@ -223,41 +241,37 @@ public: { } + Iterator(Iterator&& i) + : _collection(i._collection) + , _index(i._index) + { + } + public: - /// - /// Checks if iterator is in the end of the collection - /// - /// True if is in the end, otherwise false FORCE_INLINE bool IsEnd() const { return _index == _collection.Capacity(); } - /// - /// Checks if iterator is not in the end of the collection - /// - /// True if is not in the end, otherwise false FORCE_INLINE bool IsNotEnd() const { return _index != _collection.Capacity(); } - public: - FORCE_INLINE Bucket& operator*() const { - return _collection._table[_index]; + return _collection._allocation.Get()[_index]; } FORCE_INLINE Bucket* operator->() const { - return &_collection._table[_index]; + return &_collection._allocation.Get()[_index]; } FORCE_INLINE explicit operator bool() const { - return _index >= 0 && _index < _collection._tableSize; + return _index >= 0 && _index < _collection._size; } FORCE_INLINE bool operator !() const @@ -275,17 +289,16 @@ public: return _index != v._index || &_collection != &v._collection; } - public: - Iterator& operator++() { const int32 capacity = _collection.Capacity(); if (_index != capacity) { + const Bucket* data = _collection._allocation.Get(); do { _index++; - } while (_index != capacity && _collection._table[_index].IsNotOccupied()); + } while (_index != capacity && data[_index].IsNotOccupied()); } return *this; } @@ -301,10 +314,11 @@ public: { if (_index > 0) { + const Bucket* data = _collection._allocation.Get(); do { _index--; - } while (_index > 0 && _collection._table[_index].IsNotOccupied()); + } while (_index > 0 && data[_index].IsNotOccupied()); } return *this; } @@ -324,22 +338,25 @@ public: /// void Clear() { - if (_table) + if (_elementsCount + _deletedCount != 0) { - // Free all buckets - // Note: this will not clear allocated objects space! - for (int32 i = 0; i < _tableSize; i++) - _table[i].Free(); + Bucket* data = _allocation.Get(); + for (int32 i = 0; i < _size; i++) + data[i].Free(); _elementsCount = _deletedCount = 0; } } /// - /// Clear the collection and delete value objects. + /// Clears the collection and delete value objects. + /// Note: collection must contain pointers to the objects that have public destructor and be allocated using New method. /// +#if defined(_MSC_VER) + template::Value>::Type> +#endif void ClearDelete() { - for (auto i = Begin(); i.IsNotEnd(); ++i) + for (Iterator i = Begin(); i.IsNotEnd(); ++i) { if (i->Value) ::Delete(i->Value); @@ -354,86 +371,63 @@ public: /// Enable/disable preserving collection contents during resizing void SetCapacity(int32 capacity, bool preserveContents = true) { - // Validate input - ASSERT(capacity >= 0); - - // Check if capacity won't change if (capacity == Capacity()) return; - - // Cache previous state - auto oldTable = _table; - auto oldTableSize = _tableSize; - - // Clear elements counters - const auto oldElementsCount = _elementsCount; + ASSERT(capacity >= 0); + AllocationData oldAllocation; + oldAllocation.Swap(_allocation); + const int32 oldSize = _size; + const int32 oldElementsCount = _elementsCount; _deletedCount = _elementsCount = 0; - - // Check if need to create a new table - if (capacity > 0) + if (capacity != 0 && (capacity & (capacity - 1)) != 0) { - // Align capacity value - if (Math::IsPowerOfTwo(capacity) == false) - capacity = Math::RoundUpToPowerOf2(capacity); - - // Allocate new table - _table = NewArray(capacity); - _tableSize = capacity; - - // Check if preserve content - if (oldElementsCount != 0 && preserveContents) + // Align capacity value to the next power of two (if it's not) + capacity++; + capacity |= capacity >> 1; + capacity |= capacity >> 2; + capacity |= capacity >> 4; + capacity |= capacity >> 8; + capacity |= capacity >> 16; + capacity = capacity + 1; + } + if (capacity) + { + _allocation.Allocate(capacity); + Bucket* data = _allocation.Get(); + for (int32 i = 0; i < capacity; i++) + data[i]._state = Bucket::Empty; + } + _size = capacity; + Bucket* oldData = oldAllocation.Get(); + if (oldElementsCount != 0 && preserveContents) + { + // TODO; move keys and values on realloc + for (int32 i = 0; i < oldSize; i++) { - // Try to preserve all values in the collection - for (int32 i = 0; i < oldTableSize; i++) - { - if (oldTable[i].IsOccupied()) - Add(oldTable[i].Item); - } + if (oldData[i].IsOccupied()) + Add(oldData[i].Item); } } - else + if (oldElementsCount != 0) { - // Clear data - _table = nullptr; - _tableSize = 0; - } - ASSERT(preserveContents == false || _elementsCount == oldElementsCount); - - // Delete old table - if (oldTable) - { - DeleteArray(oldTable, oldTableSize); + for (int32 i = 0; i < oldSize; i++) + oldData[i].Free(); } } /// - /// Increases collection capacity by given extra size (content will be preserved) + /// Ensures that collection has given capacity. /// - /// Extra size to enlarge collection - FORCE_INLINE void IncreaseCapacity(int32 extraSize) - { - ASSERT(extraSize >= 0); - SetCapacity(Capacity() + extraSize); - } - - /// - /// Ensures that collection has given capacity - /// - /// Minimum required capacity - void EnsureCapacity(int32 minCapacity) + /// The minimum required capacity. + /// True if preserve collection data when changing its size, otherwise collection after resize will be empty. + void EnsureCapacity(int32 minCapacity, bool preserveContents = true) { if (Capacity() >= minCapacity) return; - int32 num = Capacity() == 0 ? DICTIONARY_DEFAULT_CAPACITY : Capacity() * 2; - SetCapacity(Math::Clamp(num, minCapacity, MAX_int32 - 1410)); - } - - /// - /// Cleanup collection data (changes size to 0 without data preserving) - /// - FORCE_INLINE void Cleanup() - { - SetCapacity(0, false); + if (minCapacity < DICTIONARY_DEFAULT_CAPACITY) + minCapacity = DICTIONARY_DEFAULT_CAPACITY; + const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity); + SetCapacity(capacity, preserveContents); } public: @@ -443,7 +437,8 @@ public: /// /// The element to add to the set. /// True if element has been added to the collection, otherwise false if the element is already present. - bool Add(const T& item) + template + bool Add(const ItemType& item) { // Ensure to have enough memory for the next item (in case of new element insertion) EnsureCapacity(_elementsCount + _deletedCount + 1); @@ -453,12 +448,12 @@ public: FindPosition(item, pos); // Check if object has been already added - if (pos.ObjectIndex != INVALID_INDEX) + if (pos.ObjectIndex != -1) return false; // Insert - ASSERT(pos.FreeSlotIndex != INVALID_INDEX); - auto bucket = &_table[pos.FreeSlotIndex]; + ASSERT(pos.FreeSlotIndex != -1); + Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; bucket->Occupy(item); _elementsCount++; @@ -472,7 +467,7 @@ public: void Add(const Iterator& i) { ASSERT(&i._collection != this && i); - Bucket& bucket = *i; + const Bucket& bucket = *i; Add(bucket.Item); } @@ -481,22 +476,20 @@ public: /// /// The element to remove. /// True if cannot remove item from the collection because cannot find it, otherwise false. - bool Remove(const T& item) + template + bool Remove(const ItemType& item) { if (IsEmpty()) - return true; - + return false; FindPositionResult pos; FindPosition(item, pos); - - if (pos.ObjectIndex != INVALID_INDEX) + if (pos.ObjectIndex != -1) { - _table[pos.ObjectIndex].Delete(); + _allocation.Get()[pos.ObjectIndex].Delete(); _elementsCount--; _deletedCount++; return true; } - return false; } @@ -510,8 +503,8 @@ public: ASSERT(&i._collection == this); if (i) { - ASSERT(_table[i._index].IsOccupied()); - _table[i._index].Delete(); + ASSERT(_allocation.Get()[i._index].IsOccupied()); + _allocation.Get()[i._index].Delete(); _elementsCount--; _deletedCount++; return true; @@ -526,15 +519,14 @@ public: /// /// Item to find /// Iterator for the found element or End if cannot find it - Iterator Find(const T& item) const + template + Iterator Find(const ItemType& item) const { if (IsEmpty()) return End(); - FindPositionResult pos; FindPosition(item, pos); - - return pos.ObjectIndex != INVALID_INDEX ? Iterator(*this, pos.ObjectIndex) : End(); + return pos.ObjectIndex != -1 ? Iterator(*this, pos.ObjectIndex) : End(); } /// @@ -542,15 +534,14 @@ public: /// /// The item to locate. /// True if value has been found in a collection, otherwise false - bool Contains(const T& item) const + template + bool Contains(const ItemType& item) const { if (IsEmpty()) return false; - FindPositionResult pos; FindPosition(item, pos); - - return pos.ObjectIndex != INVALID_INDEX; + return pos.ObjectIndex != -1; } public: @@ -561,41 +552,26 @@ public: /// Other collection to clone void Clone(const HashSet& other) { - // Clear previous data Clear(); - - // Update capacity SetCapacity(other.Capacity(), false); - - // Clone items - for (auto i = other.Begin(); i != other.End(); ++i) + for (Iterator i = other.Begin(); i != other.End(); ++i) Add(i); - - // Check ASSERT(Count() == other.Count()); ASSERT(Capacity() == other.Capacity()); } public: - /// - /// Gets iterator for beginning of the collection. - /// - /// Iterator for beginning of the collection. Iterator Begin() const { - Iterator i(*this, INVALID_INDEX); + Iterator i(*this, -1); ++i; return i; } - /// - /// Gets iterator for ending of the collection. - /// - /// Iterator for ending of the collection. Iterator End() const { - return Iterator(*this, _tableSize); + return Iterator(*this, _size); } Iterator begin() @@ -607,7 +583,7 @@ public: FORCE_INLINE Iterator end() { - return Iterator(*this, _tableSize); + return Iterator(*this, _size); } const Iterator begin() const @@ -619,11 +595,14 @@ public: FORCE_INLINE const Iterator end() const { - return Iterator(*this, _tableSize); + return Iterator(*this, _size); } protected: + /// + /// The result container of the set item lookup searching. + /// struct FindPositionResult { int32 ObjectIndex; @@ -632,42 +611,43 @@ protected: /// /// Returns a pair of positions: 1st where the object is, 2nd where - /// it would go if you wanted to insert it. 1st is INVALID_INDEX - /// if object is not found; 2nd is INVALID_INDEX if it is. + /// it would go if you wanted to insert it. 1st is -1 + /// if object is not found; 2nd is -1 if it is. /// Note: because of deletions where-to-insert is not trivial: it's the /// first deleted bucket we see, as long as we don't find the item later /// /// The item to find /// Pair of values: where the object is and where it would go if you wanted to insert it - void FindPosition(const T& item, FindPositionResult& result) const + template + void FindPosition(const ItemType& item, FindPositionResult& result) const { - ASSERT(_table); - - const int32 tableSizeMinusOne = _tableSize - 1; + ASSERT(_size); + const int32 tableSizeMinusOne = _size - 1; int32 bucketIndex = GetHash(item) & tableSizeMinusOne; - int32 insertPos = INVALID_INDEX; + int32 insertPos = -1; int32 numChecks = 0; - result.FreeSlotIndex = INVALID_INDEX; - - while (numChecks < _tableSize) + const Bucket* data = _allocation.Get(); + result.FreeSlotIndex = -1; + while (numChecks < _size) { // Empty bucket - if (_table[bucketIndex].IsEmpty()) + const Bucket& bucket = data[bucketIndex]; + if (bucket.IsEmpty()) { // Found place to insert - result.ObjectIndex = INVALID_INDEX; - result.FreeSlotIndex = insertPos == INVALID_INDEX ? bucketIndex : insertPos; + result.ObjectIndex = -1; + result.FreeSlotIndex = insertPos == -1 ? bucketIndex : insertPos; return; } // Deleted bucket - if (_table[bucketIndex].IsDeleted()) + if (bucket.IsDeleted()) { // Keep searching but mark to insert - if (insertPos == INVALID_INDEX) + if (insertPos == -1) insertPos = bucketIndex; } // Occupied bucket by target item - else if (_table[bucketIndex].Item == item) + else if (bucket.Item == item) { // Found item result.ObjectIndex = bucketIndex; @@ -675,10 +655,9 @@ protected: } numChecks++; - bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_tableSize, numChecks)) & tableSizeMinusOne; + bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_size, numChecks)) & tableSizeMinusOne; } - - result.ObjectIndex = INVALID_INDEX; + result.ObjectIndex = -1; result.FreeSlotIndex = insertPos; } }; diff --git a/Source/Engine/Core/Config/GameSettings.cs b/Source/Engine/Core/Config/GameSettings.cs index a8519fe4c..7e70f29bd 100644 --- a/Source/Engine/Core/Config/GameSettings.cs +++ b/Source/Engine/Core/Config/GameSettings.cs @@ -334,7 +334,7 @@ namespace FlaxEditor.Content.Settings } // Create new settings asset and link it to the game settings - var path = StringUtils.CombinePaths(Globals.ProjectContentFolder, "Settings", CustomEditors.CustomEditorsUtil.GetPropertyNameUI(typeof(T).Name) + ".json"); + var path = StringUtils.CombinePaths(Globals.ProjectContentFolder, "Settings", Utilities.Utils.GetPropertyNameUI(typeof(T).Name) + ".json"); if (Editor.SaveJsonAsset(path, obj)) return true; asset = FlaxEngine.Content.LoadAsync(path); diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index 6411d78e6..53bf89a61 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -11,9 +11,9 @@ /// API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API GraphicsSettings : public SettingsBase { -DECLARE_SCRIPTING_TYPE_MINIMAL(GraphicsSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_MINIMAL(GraphicsSettings); public: - /// /// Enables rendering synchronization with the refresh rate of the display device to avoid "tearing" artifacts. /// @@ -62,8 +62,21 @@ public: API_FIELD(Attributes="EditorOrder(1320), DefaultValue(false), EditorDisplay(\"Quality\", \"Allow CSM Blending\")") bool AllowCSMBlending = false; -public: + /// + /// If checked, enables Global SDF rendering. This can be used in materials, shaders, and particles. + /// + API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Global SDF\")") + bool EnableGlobalSDF = false; +#if USE_EDITOR + /// + /// If checked, the 'Generate SDF' option will be checked on model import options by default. Use it if your project uses Global SDF (eg. for Global Illumination or particles). + /// + API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Global SDF\")") + bool GenerateSDFOnModelImport = false; +#endif + +public: /// /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. /// @@ -71,15 +84,4 @@ public: // [SettingsBase] void Apply() override; - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override - { - DESERIALIZE(UseVSync); - DESERIALIZE(AAQuality); - DESERIALIZE(SSRQuality); - DESERIALIZE(SSAOQuality); - DESERIALIZE(VolumetricFogQuality); - DESERIALIZE(ShadowsQuality); - DESERIALIZE(ShadowMapsQuality); - DESERIALIZE(AllowCSMBlending); - } }; diff --git a/Source/Engine/Core/Math/BoundingBox.cpp b/Source/Engine/Core/Math/BoundingBox.cpp index df6150cf5..04b404622 100644 --- a/Source/Engine/Core/Math/BoundingBox.cpp +++ b/Source/Engine/Core/Math/BoundingBox.cpp @@ -13,6 +13,26 @@ String BoundingBox::ToString() const return String::Format(TEXT("{}"), *this); } +void BoundingBox::GetCorners(Vector3 corners[8]) const +{ + corners[0] = Vector3(Minimum.X, Maximum.Y, Maximum.Z); + corners[1] = Vector3(Maximum.X, Maximum.Y, Maximum.Z); + corners[2] = Vector3(Maximum.X, Minimum.Y, Maximum.Z); + corners[3] = Vector3(Minimum.X, Minimum.Y, Maximum.Z); + corners[4] = Vector3(Minimum.X, Maximum.Y, Minimum.Z); + corners[5] = Vector3(Maximum.X, Maximum.Y, Minimum.Z); + corners[6] = Vector3(Maximum.X, Minimum.Y, Minimum.Z); + corners[7] = Vector3(Minimum.X, Minimum.Y, Minimum.Z); +} + +BoundingBox BoundingBox::MakeOffsetted(const Vector3& offset) const +{ + BoundingBox result; + result.Minimum = Minimum + offset; + result.Maximum = Maximum + offset; + return result; +} + void BoundingBox::FromPoints(const Vector3* points, int32 pointsCount, BoundingBox& result) { ASSERT(points && pointsCount > 0); diff --git a/Source/Engine/Core/Math/BoundingBox.h b/Source/Engine/Core/Math/BoundingBox.h index 4652bb3f6..575f484fa 100644 --- a/Source/Engine/Core/Math/BoundingBox.h +++ b/Source/Engine/Core/Math/BoundingBox.h @@ -76,17 +76,7 @@ public: /// Gets the eight corners of the bounding box. /// /// An array of points representing the eight corners of the bounding box. - void GetCorners(Vector3 corners[8]) const - { - corners[0] = Vector3(Minimum.X, Maximum.Y, Maximum.Z); - corners[1] = Vector3(Maximum.X, Maximum.Y, Maximum.Z); - corners[2] = Vector3(Maximum.X, Minimum.Y, Maximum.Z); - corners[3] = Vector3(Minimum.X, Minimum.Y, Maximum.Z); - corners[4] = Vector3(Minimum.X, Maximum.Y, Minimum.Z); - corners[5] = Vector3(Maximum.X, Maximum.Y, Minimum.Z); - corners[6] = Vector3(Maximum.X, Minimum.Y, Minimum.Z); - corners[7] = Vector3(Minimum.X, Minimum.Y, Minimum.Z); - } + void GetCorners(Vector3 corners[8]) const; /// /// Calculates volume of the box. @@ -200,13 +190,7 @@ public: /// /// The offset. /// The result. - BoundingBox MakeOffsetted(const Vector3& offset) const - { - BoundingBox result; - result.Minimum = Minimum + offset; - result.Maximum = Maximum + offset; - return result; - } + BoundingBox MakeOffsetted(const Vector3& offset) const; public: @@ -421,6 +405,26 @@ public: { return CollisionsHelper::BoxContainsSphere(*this, sphere); } + + /// + /// Determines the distance between a Bounding Box and a point. + /// + /// The point to test. + /// The distance between bounding box and a point. + FORCE_INLINE float Distance(const Vector3& point) const + { + return CollisionsHelper::DistanceBoxPoint(*this, point); + } + + /// + /// Determines the distance between two Bounding Boxed. + /// + /// The bounding box to test. + /// The distance between bounding boxes. + FORCE_INLINE float Distance(const BoundingBox& box) const + { + return CollisionsHelper::DistanceBoxBox(*this, box); + } }; template<> diff --git a/Source/Engine/Core/Math/CollisionsHelper.cpp b/Source/Engine/Core/Math/CollisionsHelper.cpp index b966523ec..48e262286 100644 --- a/Source/Engine/Core/Math/CollisionsHelper.cpp +++ b/Source/Engine/Core/Math/CollisionsHelper.cpp @@ -1124,7 +1124,6 @@ bool CollisionsHelper::BoxIntersectsSphere(const BoundingBox& box, const Boundin Vector3 vector; Vector3::Clamp(sphere.Center, box.Minimum, box.Maximum, vector); const float distance = Vector3::DistanceSquared(sphere.Center, vector); - return distance <= sphere.Radius * sphere.Radius; } @@ -1438,6 +1437,16 @@ bool CollisionsHelper::LineIntersectsRect(const Vector2& p1, const Vector2& p2, return (topoverlap < botoverlap) && (!((botoverlap < t) || (topoverlap > b)));*/ } +Vector2 CollisionsHelper::LineHitsBox(const Vector3& lineStart, const Vector3& lineEnd, const Vector3& boxMin, const Vector3& boxMax) +{ + const Vector3 invDirection = 1.0f / (lineEnd - lineStart); + const Vector3 enterIntersection = (boxMin - lineStart) * invDirection; + const Vector3 exitIntersection = (boxMax - lineStart) * invDirection; + const Vector3 minIntersections = Vector3::Min(enterIntersection, exitIntersection); + const Vector3 maxIntersections = Vector3::Max(enterIntersection, exitIntersection); + return Vector2(Math::Saturate(minIntersections.MaxValue()), Math::Saturate(maxIntersections.MinValue())); +} + bool CollisionsHelper::IsPointInTriangle(const Vector2& point, const Vector2& a, const Vector2& b, const Vector2& c) { const Vector2 an = a - point; diff --git a/Source/Engine/Core/Math/CollisionsHelper.h b/Source/Engine/Core/Math/CollisionsHelper.h index 9720369c1..ef1791ec1 100644 --- a/Source/Engine/Core/Math/CollisionsHelper.h +++ b/Source/Engine/Core/Math/CollisionsHelper.h @@ -569,6 +569,12 @@ public: /// True if line intersects with the rectangle static bool LineIntersectsRect(const Vector2& p1, const Vector2& p2, const Rectangle& rect); + // Hits axis-aligned box (boxMin, boxMax) with a line (lineStart, lineEnd). + // Returns the intersections on the line (x - closest, y - furthest). + // Line hits the box if: intersections.x < intersections.y. + // Hit point is: hitPoint = lineStart + (lineEnd - lineStart) * intersections.x/y. + static Vector2 LineHitsBox(const Vector3& lineStart, const Vector3& lineEnd, const Vector3& boxMin, const Vector3& boxMax); + /// /// Determines whether the given 2D point is inside the specified triangle. /// diff --git a/Source/Engine/Core/Math/Half.cpp b/Source/Engine/Core/Math/Half.cpp index 40ab1144c..b17721cd6 100644 --- a/Source/Engine/Core/Math/Half.cpp +++ b/Source/Engine/Core/Math/Half.cpp @@ -12,9 +12,9 @@ static_assert(sizeof(Half2) == 4, "Invalid Half2 type size."); static_assert(sizeof(Half3) == 6, "Invalid Half3 type size."); static_assert(sizeof(Half4) == 8, "Invalid Half4 type size."); -Half2 Half2::Zero(0, 0); -Half3 Half3::Zero(0, 0, 0); -Half4 Half4::Zero(0, 0, 0, 0); +Half2 Half2::Zero(0.0f, 0.0f); +Half3 Half3::Zero(0.0f, 0.0f, 0.0f); +Half4 Half4::Zero(0.0f, 0.0f, 0.0f, 0.0f); Half2::Half2(const Vector2& v) { diff --git a/Source/Engine/Core/Math/Half.h b/Source/Engine/Core/Math/Half.h index dcdd6beee..f6fa3d4c5 100644 --- a/Source/Engine/Core/Math/Half.h +++ b/Source/Engine/Core/Math/Half.h @@ -45,7 +45,6 @@ class FLAXENGINE_API Float16Compressor static const int32 minD = minC - subC - 1; public: - static Half Compress(const float value) { #if USE_SSE_HALF_CONVERSION @@ -102,14 +101,12 @@ public: struct FLAXENGINE_API Half2 { public: - /// /// Zero vector /// static Half2 Zero; public: - /// /// Gets or sets the X component of the vector. /// @@ -121,7 +118,6 @@ public: Half Y; public: - /// /// Default constructor /// @@ -129,6 +125,17 @@ public: { } + /// + /// Init + /// + /// X component + /// Y component + Half2(Half x, Half y) + : X(x) + , Y(y) + { + } + /// /// Init /// @@ -147,7 +154,6 @@ public: Half2(const Vector2& v); public: - /// /// Convert to Vector2 /// @@ -161,14 +167,12 @@ public: struct FLAXENGINE_API Half3 { public: - /// /// Zero vector /// static Half3 Zero; public: - /// /// Gets or sets the X component of the vector. /// @@ -185,11 +189,17 @@ public: Half Z; public: - Half3() { } + Half3(Half x, Half y, Half z) + : X(x) + , Y(y) + , Z(z) + { + } + Half3(const float x, const float y, const float z) { X = Float16Compressor::Compress(x); @@ -200,7 +210,6 @@ public: Half3(const Vector3& v); public: - Vector3 ToVector3() const; }; @@ -210,14 +219,12 @@ public: struct FLAXENGINE_API Half4 { public: - /// /// Zero vector /// static Half4 Zero; public: - /// /// Gets or sets the X component of the vector. /// @@ -239,11 +246,18 @@ public: Half W; public: - Half4() { } + Half4(Half x, Half y, Half z, Half w) + : X(x) + , Y(y) + , Z(z) + , W(w) + { + } + Half4(const float x, const float y, const float z) { X = Float16Compressor::Compress(x); @@ -265,7 +279,6 @@ public: explicit Half4(const Rectangle& rect); public: - Vector2 ToVector2() const; Vector3 ToVector3() const; Vector4 ToVector4() const; diff --git a/Source/Engine/Core/Math/Matrix.cpp b/Source/Engine/Core/Math/Matrix.cpp index d2e87ead2..039ab201b 100644 --- a/Source/Engine/Core/Math/Matrix.cpp +++ b/Source/Engine/Core/Math/Matrix.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "Matrix.h" +#include "Matrix3x3.h" #include "Vector2.h" #include "Quaternion.h" #include "Transform.h" @@ -15,6 +16,17 @@ const Matrix Matrix::Identity( 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f); +Matrix::Matrix(const Matrix3x3& matrix) +{ + Platform::MemoryCopy(&M11, &matrix.M11, sizeof(Vector3)); + Platform::MemoryCopy(&M21, &matrix.M21, sizeof(Vector3)); + Platform::MemoryCopy(&M31, &matrix.M31, sizeof(Vector3)); + M14 = 0.0f; + M24 = 0.0f; + M34 = 0.0f; + M44 = 1.0f; +} + String Matrix::ToString() const { return String::Format(TEXT("{}"), *this); @@ -284,8 +296,6 @@ void Matrix::LookAt(const Vector3& eye, const Vector3& target, const Vector3& up xaxis.Normalize(); Vector3::Cross(zaxis, xaxis, yaxis); - result = Identity; - result.M11 = xaxis.X; result.M21 = xaxis.Y; result.M31 = xaxis.Z; @@ -298,9 +308,14 @@ void Matrix::LookAt(const Vector3& eye, const Vector3& target, const Vector3& up result.M23 = zaxis.Y; result.M33 = zaxis.Z; + result.M14 = 0.0f; + result.M24 = 0.0f; + result.M34 = 0.0f; + result.M41 = -Vector3::Dot(xaxis, eye); result.M42 = -Vector3::Dot(yaxis, eye); result.M43 = -Vector3::Dot(zaxis, eye); + result.M44 = 1.0f; } void Matrix::OrthoOffCenter(float left, float right, float bottom, float top, float zNear, float zFar, Matrix& result) @@ -587,33 +602,7 @@ void Matrix::Transformation2D(Vector2& scalingCenter, float scalingRotation, con Matrix Matrix::CreateWorld(const Vector3& position, const Vector3& forward, const Vector3& up) { Matrix result; - Vector3 vector3, vector31, vector32; - - Vector3::Normalize(forward, vector3); - vector3.Negate(); - Vector3::Normalize(Vector3::Cross(up, vector3), vector31); - Vector3::Cross(vector3, vector31, vector32); - - result.M11 = vector31.X; - result.M12 = vector31.Y; - result.M13 = vector31.Z; - result.M14 = 0.0f; - - result.M21 = vector32.X; - result.M22 = vector32.Y; - result.M23 = vector32.Z; - result.M24 = 0.0f; - - result.M31 = vector3.X; - result.M32 = vector3.Y; - result.M33 = vector3.Z; - result.M34 = 0.0f; - - result.M41 = position.X; - result.M42 = position.Y; - result.M43 = position.Z; - result.M44 = 1.0f; - + CreateWorld(position, forward, up, result); return result; } @@ -649,41 +638,9 @@ void Matrix::CreateWorld(const Vector3& position, const Vector3& forward, const Matrix Matrix::CreateFromAxisAngle(const Vector3& axis, float angle) { - Matrix matrix; - - const float x = axis.X; - const float y = axis.Y; - const float z = axis.Z; - const float single = Math::Sin(angle); - const float single1 = Math::Cos(angle); - const float single2 = x * x; - const float single3 = y * y; - const float single4 = z * z; - const float single5 = x * y; - const float single6 = x * z; - const float single7 = y * z; - - matrix.M11 = single2 + single1 * (1.0f - single2); - matrix.M12 = single5 - single1 * single5 + single * z; - matrix.M13 = single6 - single1 * single6 - single * y; - matrix.M14 = 0.0f; - - matrix.M21 = single5 - single1 * single5 - single * z; - matrix.M22 = single3 + single1 * (1.0f - single3); - matrix.M23 = single7 - single1 * single7 + single * x; - matrix.M24 = 0.0f; - - matrix.M31 = single6 - single1 * single6 + single * y; - matrix.M32 = single7 - single1 * single7 - single * x; - matrix.M33 = single4 + single1 * (1.0f - single4); - matrix.M34 = 0.0f; - - matrix.M41 = 0.0f; - matrix.M42 = 0.0f; - matrix.M43 = 0.0f; - matrix.M44 = 1.0f; - - return matrix; + Matrix result; + CreateFromAxisAngle(axis, angle, result); + return result; } void Matrix::CreateFromAxisAngle(const Vector3& axis, float angle, Matrix& result) diff --git a/Source/Engine/Core/Math/Matrix.h b/Source/Engine/Core/Math/Matrix.h index 6d680e292..feb7c8160 100644 --- a/Source/Engine/Core/Math/Matrix.h +++ b/Source/Engine/Core/Math/Matrix.h @@ -150,6 +150,8 @@ public: Platform::MemoryCopy(Raw, values, sizeof(float) * 16); } + explicit Matrix(const Matrix3x3& matrix); + public: String ToString() const; diff --git a/Source/Engine/Core/Math/Matrix3x3.cpp b/Source/Engine/Core/Math/Matrix3x3.cpp index c443582a2..c208b4f98 100644 --- a/Source/Engine/Core/Math/Matrix3x3.cpp +++ b/Source/Engine/Core/Math/Matrix3x3.cpp @@ -1,8 +1,9 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "Matrix3x3.h" -#include "../Types/String.h" +#include "Matrix.h" #include "Quaternion.h" +#include "../Types/String.h" const Matrix3x3 Matrix3x3::Zero(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); const Matrix3x3 Matrix3x3::Identity( @@ -10,11 +11,37 @@ const Matrix3x3 Matrix3x3::Identity( 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); +Matrix3x3::Matrix3x3(const Matrix& matrix) +{ + Platform::MemoryCopy(&M11, &matrix.M11, sizeof(Vector3)); + Platform::MemoryCopy(&M21, &matrix.M21, sizeof(Vector3)); + Platform::MemoryCopy(&M31, &matrix.M31, sizeof(Vector3)); +} + String Matrix3x3::ToString() const { return String::Format(TEXT("{}"), *this); } +void Matrix3x3::NormalizeScale() +{ + const float scaleX = 1.0f / Vector3(M11, M21, M31).Length(); + const float scaleY = 1.0f / Vector3(M12, M22, M32).Length(); + const float scaleZ = 1.0f / Vector3(M13, M23, M33).Length(); + + M11 *= scaleX; + M21 *= scaleX; + M31 *= scaleX; + + M12 *= scaleY; + M22 *= scaleY; + M32 *= scaleY; + + M13 *= scaleZ; + M23 *= scaleZ; + M33 *= scaleZ; +} + void Matrix3x3::Invert(const Matrix3x3& value, Matrix3x3& result) { const float d11 = value.M22 * value.M33 + value.M23 * -value.M32; diff --git a/Source/Engine/Core/Math/Matrix3x3.h b/Source/Engine/Core/Math/Matrix3x3.h index b8227776c..f005abc3a 100644 --- a/Source/Engine/Core/Math/Matrix3x3.h +++ b/Source/Engine/Core/Math/Matrix3x3.h @@ -113,6 +113,12 @@ public: Platform::MemoryCopy(Raw, values, sizeof(float) * 9); } + /// + /// Initializes a new instance of the struct. + /// + /// The 4 by 4 matrix to initialize from with rotation and scale (translation is skipped). + explicit Matrix3x3(const Matrix& matrix); + public: String ToString() const; @@ -255,6 +261,11 @@ public: Transpose(*this, *this); } + /// + /// Removes any scaling from the matrix by performing the normalization (each row magnitude is 1). + /// + void NormalizeScale(); + public: /// diff --git a/Source/Engine/Core/Math/OrientedBoundingBox.cpp b/Source/Engine/Core/Math/OrientedBoundingBox.cpp index 7aaeabb18..593c8cb46 100644 --- a/Source/Engine/Core/Math/OrientedBoundingBox.cpp +++ b/Source/Engine/Core/Math/OrientedBoundingBox.cpp @@ -8,11 +8,18 @@ OrientedBoundingBox::OrientedBoundingBox(const BoundingBox& bb) { - const Vector3 center = bb.Minimum + (bb.Maximum - bb.Minimum) / 2.0f; + const Vector3 center = bb.Minimum + (bb.Maximum - bb.Minimum) * 0.5f; Extents = bb.Maximum - center; Matrix::Translation(center, Transformation); } +OrientedBoundingBox::OrientedBoundingBox(const Vector3& extents, const Matrix3x3& rotationScale, const Vector3& translation) + : Extents(extents) + , Transformation(rotationScale) +{ + Transformation.SetTranslation(translation); +} + OrientedBoundingBox::OrientedBoundingBox(Vector3 points[], int32 pointCount) { ASSERT(points && pointCount > 0); @@ -104,6 +111,12 @@ void OrientedBoundingBox::GetBoundingBox(BoundingBox& result) const BoundingBox::FromPoints(corners, 8, result); } +void OrientedBoundingBox::Transform(const Matrix& matrix) +{ + const Matrix tmp = Transformation; + Matrix::Multiply(tmp, matrix, Transformation); +} + ContainmentType OrientedBoundingBox::Contains(const Vector3& point, float* distance) const { // Transform the point into the obb coordinates diff --git a/Source/Engine/Core/Math/OrientedBoundingBox.h b/Source/Engine/Core/Math/OrientedBoundingBox.h index b76791ed7..a1e8236ab 100644 --- a/Source/Engine/Core/Math/OrientedBoundingBox.h +++ b/Source/Engine/Core/Math/OrientedBoundingBox.h @@ -40,6 +40,8 @@ public: Transformation = transformation; } + OrientedBoundingBox(const Vector3& extents, const Matrix3x3& rotationScale, const Vector3& translation); + // Init // @param minimum The minimum vertex of the bounding box. // @param maximum The maximum vertex of the bounding box. @@ -99,10 +101,7 @@ public: // Transforms this box using a transformation matrix. // @param mat The transformation matrix. - void Transform(const Matrix& mat) - { - Transformation *= mat; - } + void Transform(const Matrix& matrix); // Scales the OBB by scaling its Extents without affecting the Transformation matrix. // By keeping Transformation matrix scaling-free, the collision detection methods will be more accurate. diff --git a/Source/Engine/Core/Math/Quaternion.cpp b/Source/Engine/Core/Math/Quaternion.cpp index df2ae7da3..b03ab8133 100644 --- a/Source/Engine/Core/Math/Quaternion.cpp +++ b/Source/Engine/Core/Math/Quaternion.cpp @@ -4,6 +4,7 @@ #include "Vector3.h" #include "Vector4.h" #include "Matrix.h" +#include "Matrix3x3.h" #include "Math.h" #include "../Types/String.h" @@ -208,6 +209,59 @@ void Quaternion::RotationMatrix(const Matrix& matrix, Quaternion& result) result.Normalize(); } +void Quaternion::RotationMatrix(const Matrix3x3& matrix, Quaternion& result) +{ + float sqrtV; + float half; + const float scale = matrix.M11 + matrix.M22 + matrix.M33; + + if (scale > 0.0f) + { + sqrtV = Math::Sqrt(scale + 1.0f); + result.W = sqrtV * 0.5f; + sqrtV = 0.5f / sqrtV; + + result.X = (matrix.M23 - matrix.M32) * sqrtV; + result.Y = (matrix.M31 - matrix.M13) * sqrtV; + result.Z = (matrix.M12 - matrix.M21) * sqrtV; + } + else if (matrix.M11 >= matrix.M22 && matrix.M11 >= matrix.M33) + { + sqrtV = Math::Sqrt(1.0f + matrix.M11 - matrix.M22 - matrix.M33); + half = 0.5f / sqrtV; + + result = Quaternion( + 0.5f * sqrtV, + (matrix.M12 + matrix.M21) * half, + (matrix.M13 + matrix.M31) * half, + (matrix.M23 - matrix.M32) * half); + } + else if (matrix.M22 > matrix.M33) + { + sqrtV = Math::Sqrt(1.0f + matrix.M22 - matrix.M11 - matrix.M33); + half = 0.5f / sqrtV; + + result = Quaternion( + (matrix.M21 + matrix.M12) * half, + 0.5f * sqrtV, + (matrix.M32 + matrix.M23) * half, + (matrix.M31 - matrix.M13) * half); + } + else + { + sqrtV = Math::Sqrt(1.0f + matrix.M33 - matrix.M11 - matrix.M22); + half = 0.5f / sqrtV; + + result = Quaternion( + (matrix.M31 + matrix.M13) * half, + (matrix.M32 + matrix.M23) * half, + 0.5f * sqrtV, + (matrix.M12 - matrix.M21) * half); + } + + result.Normalize(); +} + void Quaternion::LookAt(const Vector3& eye, const Vector3& target, const Vector3& up, Quaternion& result) { Matrix matrix; diff --git a/Source/Engine/Core/Math/Quaternion.h b/Source/Engine/Core/Math/Quaternion.h index 4fea7e69c..77e3a51fe 100644 --- a/Source/Engine/Core/Math/Quaternion.h +++ b/Source/Engine/Core/Math/Quaternion.h @@ -10,6 +10,7 @@ struct Vector2; struct Vector3; struct Vector4; struct Matrix; +struct Matrix3x3; /// /// Represents a four dimensional mathematical quaternion. Euler angles are stored in: pitch, yaw, roll order (x, y, z). @@ -566,6 +567,11 @@ public: // @param result When the method completes, contains the newly created quaternion static void RotationMatrix(const Matrix& matrix, Quaternion& result); + // Creates a quaternion given a rotation matrix + // @param matrix The rotation matrix + // @param result When the method completes, contains the newly created quaternion + static void RotationMatrix(const Matrix3x3& matrix, Quaternion& result); + // Creates a left-handed, look-at quaternion // @param eye The position of the viewer's eye // @param target The camera look-at target diff --git a/Source/Engine/Core/Math/Triangle.h b/Source/Engine/Core/Math/Triangle.h index 72aee5409..a830f7e3e 100644 --- a/Source/Engine/Core/Math/Triangle.h +++ b/Source/Engine/Core/Math/Triangle.h @@ -49,6 +49,13 @@ public: { } +public: + + Vector3 GetNormal() const + { + return Vector3::Normalize((V1 - V0) ^ (V2 - V0)); + } + public: // Determines if there is an intersection between the current object and a Ray diff --git a/Source/Engine/Core/Math/Vector3.cpp b/Source/Engine/Core/Math/Vector3.cpp index 83faa28a3..567e107f6 100644 --- a/Source/Engine/Core/Math/Vector3.cpp +++ b/Source/Engine/Core/Math/Vector3.cpp @@ -9,6 +9,7 @@ #include "Color.h" #include "Quaternion.h" #include "Matrix.h" +#include "Matrix3x3.h" #include "Int2.h" #include "Int3.h" #include "Int4.h" @@ -215,9 +216,15 @@ void Vector3::Transform(const Vector3& vector, const Matrix& transform, Vector3& void Vector3::Transform(const Vector3* vectors, const Matrix& transform, Vector3* results, int32 vectorsCount) { for (int32 i = 0; i < vectorsCount; i++) - { Transform(vectors[i], transform, results[i]); - } +} + +void Vector3::Transform(const Vector3& vector, const Matrix3x3& transform, Vector3& result) +{ + result = Vector3( + vector.X * transform.M11 + vector.Y * transform.M21 + vector.Z * transform.M31, + vector.X * transform.M12 + vector.Y * transform.M22 + vector.Z * transform.M32, + vector.X * transform.M13 + vector.Y * transform.M23 + vector.Z * transform.M33); } Vector3 Vector3::Transform(const Vector3& vector, const Matrix& transform) diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h index 96eee174e..9ab6bf450 100644 --- a/Source/Engine/Core/Math/Vector3.h +++ b/Source/Engine/Core/Math/Vector3.h @@ -11,6 +11,7 @@ struct Double3; struct Double4; struct Quaternion; struct Matrix; +struct Matrix3x3; struct Vector2; struct Vector4; struct Color; @@ -215,7 +216,7 @@ public: } /// - /// Returns average arithmetic of all the components + /// Returns the average arithmetic of all the components. /// float AverageArithmetic() const { @@ -223,7 +224,7 @@ public: } /// - /// Gets sum of all vector components values. + /// Gets the sum of all vector components values. /// float SumValues() const { @@ -231,7 +232,7 @@ public: } /// - /// Returns minimum value of all the components. + /// Returns the minimum value of all the components. /// float MinValue() const { @@ -239,7 +240,7 @@ public: } /// - /// Returns maximum value of all the components. + /// Returns the maximum value of all the components. /// float MaxValue() const { @@ -255,9 +256,8 @@ public: } /// - /// Returns true if vector has one or more components equal to +/- infinity + /// Returns true if vector has one or more components equal to +/- infinity. /// - /// True if one or more components equal to +/- infinity bool IsInfinity() const { return isinf(X) || isinf(Y) || isinf(Z); @@ -714,6 +714,12 @@ public: // @param vectorsCount Amount of vectors to transform static void Transform(const Vector3* vectors, const Matrix& transform, Vector3* results, int32 vectorsCount); + // Transforms a 3D vector by the given matrix + // @param vector The source vector + // @param transform The transformation matrix + // @param result When the method completes, contains the transformed Vector3 + static void Transform(const Vector3& vector, const Matrix3x3& transform, Vector3& result); + // Transforms a 3D vector by the given matrix // @param vector The source vector // @param transform The transformation matrix diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index 5d86ba4d3..a471b41c4 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -559,7 +559,7 @@ void Foliage::OnFoliageTypeModelLoaded(int32 index) } BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } { PROFILE_CPU_NAMED("Create Clusters"); @@ -606,7 +606,7 @@ void Foliage::RebuildClusters() _box = BoundingBox(_transform.Translation, _transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); return; } @@ -696,7 +696,7 @@ void Foliage::RebuildClusters() _box = totalBounds; BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } // Insert all instances to the clusters @@ -845,6 +845,10 @@ bool Foliage::Intersects(const Ray& ray, float& distance, Vector3& normal, int32 void Foliage::Draw(RenderContext& renderContext) { + if (renderContext.View.Pass == DrawPass::GlobalSDF) + return; // TODO: Foliage rendering to Global SDF + if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) + return; // Not supported if (Instances.IsEmpty()) return; auto& view = renderContext.View; @@ -1016,11 +1020,6 @@ void Foliage::Draw(RenderContext& renderContext) #endif } -void Foliage::DrawGeneric(RenderContext& renderContext) -{ - Draw(renderContext); -} - bool Foliage::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) { int32 instanceIndex; @@ -1227,12 +1226,12 @@ void Foliage::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie void Foliage::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void Foliage::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddGeometry(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); // Base Actor::OnEnable(); @@ -1240,7 +1239,7 @@ void Foliage::OnEnable() void Foliage::OnDisable() { - GetSceneRendering()->RemoveGeometry(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); diff --git a/Source/Engine/Foliage/Foliage.h b/Source/Engine/Foliage/Foliage.h index e4f32529f..19e45fbeb 100644 --- a/Source/Engine/Foliage/Foliage.h +++ b/Source/Engine/Foliage/Foliage.h @@ -197,7 +197,6 @@ public: // [Actor] void Draw(RenderContext& renderContext) override; - void DrawGeneric(RenderContext& renderContext) override; bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; diff --git a/Source/Engine/Foliage/FoliageType.cpp b/Source/Engine/Foliage/FoliageType.cpp index 75bd33191..1aa14bacb 100644 --- a/Source/Engine/Foliage/FoliageType.cpp +++ b/Source/Engine/Foliage/FoliageType.cpp @@ -208,4 +208,11 @@ void FoliageType::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DESERIALIZE(PlacementRandomRollAngle); DESERIALIZE_BIT(PlacementAlignToNormal); DESERIALIZE_BIT(PlacementRandomYaw); + + // [Deprecated on 07.02.2022, expires on 07.02.2024] + if (modifier->EngineBuild <= 6330) + DrawModes |= DrawPass::GlobalSDF; + // [Deprecated on 27.04.2022, expires on 27.04.2024] + if (modifier->EngineBuild <= 6331) + DrawModes |= DrawPass::GlobalSurfaceAtlas; } diff --git a/Source/Engine/Graphics/DynamicBuffer.cpp b/Source/Engine/Graphics/DynamicBuffer.cpp index dd96d7d2c..d4db1b957 100644 --- a/Source/Engine/Graphics/DynamicBuffer.cpp +++ b/Source/Engine/Graphics/DynamicBuffer.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "DynamicBuffer.h" +#include "PixelFormatExtensions.h" #include "GPUDevice.h" #include "Engine/Core/Log.h" #include "Engine/Core/Utilities.h" @@ -87,3 +88,24 @@ void DynamicBuffer::Dispose() SAFE_DELETE_GPU_RESOURCE(_buffer); Data.Resize(0); } + +void DynamicStructuredBuffer::InitDesc(GPUBufferDescription& desc, int32 numElements) +{ + desc = GPUBufferDescription::Structured(numElements, _stride, _isUnorderedAccess); + desc.Usage = GPUResourceUsage::Dynamic; +} + +DynamicTypedBuffer::DynamicTypedBuffer(uint32 initialCapacity, PixelFormat format, bool isUnorderedAccess, const String& name) + : DynamicBuffer(initialCapacity, PixelFormatExtensions::SizeInBytes(format), name) + , _format(format) + , _isUnorderedAccess(isUnorderedAccess) +{ +} + +void DynamicTypedBuffer::InitDesc(GPUBufferDescription& desc, int32 numElements) +{ + auto bufferFlags = GPUBufferFlags::ShaderResource; + if (_isUnorderedAccess) + bufferFlags |= GPUBufferFlags::UnorderedAccess; + desc = GPUBufferDescription::Buffer(numElements * _stride, bufferFlags, _format, nullptr, _stride, GPUResourceUsage::Dynamic); +} diff --git a/Source/Engine/Graphics/DynamicBuffer.h b/Source/Engine/Graphics/DynamicBuffer.h index 9f5f651be..9f251ff14 100644 --- a/Source/Engine/Graphics/DynamicBuffer.h +++ b/Source/Engine/Graphics/DynamicBuffer.h @@ -170,3 +170,58 @@ protected: desc = GPUBufferDescription::Index(_stride, numElements, GPUResourceUsage::Dynamic); } }; + +/// +/// Dynamic structured buffer that allows to upload data to the GPU from CPU (supports dynamic resizing). +/// +class FLAXENGINE_API DynamicStructuredBuffer : public DynamicBuffer +{ +private: + bool _isUnorderedAccess; + +public: + + /// + /// Init + /// + /// Initial capacity of the buffer (in bytes). + /// Stride in bytes. + /// True if unordered access usage. + /// Buffer name. + DynamicStructuredBuffer(uint32 initialCapacity, uint32 stride, bool isUnorderedAccess = false, const String& name = String::Empty) + : DynamicBuffer(initialCapacity, stride, name) + , _isUnorderedAccess(isUnorderedAccess) + { + } + +protected: + + // [DynamicBuffer] + void InitDesc(GPUBufferDescription& desc, int32 numElements) override; +}; + +/// +/// Dynamic Typed buffer that allows to upload data to the GPU from CPU (supports dynamic resizing). +/// +class FLAXENGINE_API DynamicTypedBuffer : public DynamicBuffer +{ +private: + PixelFormat _format; + bool _isUnorderedAccess; + +public: + + /// + /// Init + /// + /// Initial capacity of the buffer (in bytes). + /// Format of the data. + /// True if unordered access usage. + /// Buffer name. + DynamicTypedBuffer(uint32 initialCapacity, PixelFormat format, bool isUnorderedAccess = false, const String& name = String::Empty); + +protected: + + // [DynamicBuffer] + void InitDesc(GPUBufferDescription& desc, int32 numElements) override; +}; diff --git a/Source/Engine/Graphics/Enums.h b/Source/Engine/Graphics/Enums.h index 8b9057f75..55398accb 100644 --- a/Source/Engine/Graphics/Enums.h +++ b/Source/Engine/Graphics/Enums.h @@ -702,6 +702,16 @@ API_ENUM(Attributes="Flags") enum class DrawPass : int32 /// MotionVectors = 1 << 4, + /// + /// The Global Sign Distance Field (SDF) rendering pass. Used for software raytracing though the scene on a GPU. + /// + GlobalSDF = 1 << 5, + + /// + /// The Global Surface Atlas rendering pass. Used for software raytracing though the scene on a GPU to evaluate the object surface material properties. + /// + GlobalSurfaceAtlas = 1 << 6, + /// /// The debug quad overdraw rendering (editor-only). /// @@ -712,13 +722,13 @@ API_ENUM(Attributes="Flags") enum class DrawPass : int32 /// The default set of draw passes for the scene objects. /// API_ENUM(Attributes="HideInEditor") - Default = Depth | GBuffer | Forward | Distortion | MotionVectors, + Default = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF | GlobalSurfaceAtlas, /// /// The all draw passes combined into a single mask. /// API_ENUM(Attributes="HideInEditor") - All = Depth | GBuffer | Forward | Distortion | MotionVectors, + All = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF | GlobalSurfaceAtlas, }; DECLARE_ENUM_OPERATORS(DrawPass); @@ -847,6 +857,16 @@ API_ENUM() enum class ViewMode /// Draw geometry overdraw to visualize performance of pixels rendering. /// QuadOverdraw = 23, + + /// + /// Draw Global Sign Distant Field (SDF) preview. + /// + GlobalSDF = 24, + + /// + /// Draw Global Surface Atlas preview. + /// + GlobalSurfaceAtlas = 25, }; /// @@ -997,7 +1017,7 @@ API_ENUM(Attributes="Flags") enum class ViewFlags : int64 /// /// Default flags for materials/models previews generating. /// - DefaultAssetPreview = Reflections | Decals | GI | DirectionalLights | PointLights | SpotLights | SkyLights | SpecularLight | AntiAliasing | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | ContactShadows, + DefaultAssetPreview = Reflections | Decals | DirectionalLights | PointLights | SpotLights | SkyLights | SpecularLight | AntiAliasing | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | ContactShadows, }; DECLARE_ENUM_OPERATORS(ViewFlags); diff --git a/Source/Engine/Graphics/GPUContext.h b/Source/Engine/Graphics/GPUContext.h index 59699417e..b87faf6a2 100644 --- a/Source/Engine/Graphics/GPUContext.h +++ b/Source/Engine/Graphics/GPUContext.h @@ -553,7 +553,7 @@ public: /// /// Sets the rendering viewport and scissor rectangle. /// - /// The viewport. + /// The viewport (in pixels). API_FUNCTION() FORCE_INLINE void SetViewportAndScissors(const Viewport& viewport) { SetViewport(viewport); @@ -575,13 +575,13 @@ public: /// /// Sets the rendering viewport. /// - /// The viewport. + /// The viewport (in pixels). API_FUNCTION() virtual void SetViewport(API_PARAM(Ref) const Viewport& viewport) = 0; /// /// Sets the scissor rectangle. /// - /// The scissor rectangle. + /// The scissor rectangle (in pixels). API_FUNCTION() virtual void SetScissor(API_PARAM(Ref) const Rectangle& scissorRect) = 0; public: diff --git a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp index f0ac5300b..a8fefdbca 100644 --- a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp @@ -48,7 +48,7 @@ void DecalMaterialShader::Bind(BindParameters& params) bindMeta.Context = context; bindMeta.Constants = cb; bindMeta.Input = nullptr; - bindMeta.Buffers = nullptr; + bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = true; bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index 29ef818e0..50780743b 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp @@ -74,7 +74,7 @@ void DeferredMaterialShader::Bind(BindParameters& params) bindMeta.Context = context; bindMeta.Constants = cb; bindMeta.Input = nullptr; - bindMeta.Buffers = nullptr; + bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = false; bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); diff --git a/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp index 61d6fb42a..f491ee700 100644 --- a/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp @@ -62,7 +62,7 @@ void DeformableMaterialShader::Bind(BindParameters& params) bindMeta.Context = context; bindMeta.Constants = cb; bindMeta.Input = nullptr; - bindMeta.Buffers = nullptr; + bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = false; bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); diff --git a/Source/Engine/Graphics/Materials/IMaterial.h b/Source/Engine/Graphics/Materials/IMaterial.h index f22feda91..c59aebb58 100644 --- a/Source/Engine/Graphics/Materials/IMaterial.h +++ b/Source/Engine/Graphics/Materials/IMaterial.h @@ -5,6 +5,7 @@ #include "MaterialInfo.h" struct MaterialParamsLink; +class GPUShader; class GPUContext; class GPUTextureView; class RenderBuffers; @@ -26,6 +27,12 @@ public: /// The constant reference to the material descriptor. virtual const MaterialInfo& GetInfo() const = 0; + /// + /// Gets the shader resource. + /// + /// The material shader resource. + virtual GPUShader* GetShader() const = 0; + /// /// Determines whether material is a surface shader. /// diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index c99d358e3..87d4193ef 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -11,6 +11,8 @@ #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPULimits.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Renderer/GlobalSignDistanceFieldPass.h" #include "Engine/Streaming/Streaming.h" bool MaterialInfo8::operator==(const MaterialInfo8& other) const @@ -157,6 +159,9 @@ const Char* ToString(MaterialParameterType value) case MaterialParameterType::TextureGroupSampler: result = TEXT("TextureGroupSampler"); break; + case MaterialParameterType::GlobalSDF: + result = TEXT("GlobalSDF"); + break; default: result = TEXT(""); break; @@ -198,7 +203,6 @@ Variant MaterialParameter::GetValue() const case MaterialParameterType::GPUTexture: return _asGPUTexture.Get(); default: - CRASH; return Variant::Zero; } } @@ -303,6 +307,8 @@ void MaterialParameter::SetValue(const Variant& value) invalidType = true; } break; + case MaterialParameterType::GlobalSDF: + break; default: invalidType = true; } @@ -477,6 +483,16 @@ void MaterialParameter::Bind(BindMeta& meta) const case MaterialParameterType::TextureGroupSampler: meta.Context->BindSampler(_registerIndex, Streaming::GetTextureGroupSampler(_asInteger)); break; + case MaterialParameterType::GlobalSDF: + { + GlobalSignDistanceFieldPass::BindingData bindingData; + if (GlobalSignDistanceFieldPass::Instance()->Get(meta.Buffers, bindingData)) + Platform::MemoryClear(&bindingData, sizeof(bindingData)); + for (int32 i = 0; i < 4; i++) + meta.Context->BindSR(_registerIndex + i, bindingData.Cascades[i] ? bindingData.Cascades[i]->ViewVolume() : nullptr); + *((GlobalSignDistanceFieldPass::ConstantsData*)(meta.Constants.Get() + _offset)) = bindingData.Constants; + break; + } default: break; } diff --git a/Source/Engine/Graphics/Materials/MaterialParams.h b/Source/Engine/Graphics/Materials/MaterialParams.h index 664da2b19..a367114c2 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.h +++ b/Source/Engine/Graphics/Materials/MaterialParams.h @@ -128,6 +128,11 @@ enum class MaterialParameterType : byte /// The texture sampler derived from texture group settings. /// TextureGroupSampler = 19, + + /// + /// The Global SDF (textures and constants). + /// + GlobalSDF = 20, }; const Char* ToString(MaterialParameterType value); diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index 4c789b2ea..8257147c8 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -98,8 +98,6 @@ public: /// The created and loaded material or null if failed. static MaterialShader* CreateDummy(MemoryReadStream& shaderCacheStream, const MaterialInfo& info); - GPUShader* GetShader() const; - /// /// Clears the loaded data. /// @@ -114,5 +112,6 @@ public: // [IMaterial] const MaterialInfo& GetInfo() const override; + GPUShader* GetShader() const override; bool IsReady() const override; }; diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp index 4a69ccc59..c9f65e3fb 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -22,6 +22,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanFog) @@ -39,7 +40,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanDirectionalLights.First(); const auto shadowPass = ShadowsPass::Instance(); - const bool useShadow = shadowPass->LastDirLightIndex == 0; + const bool useShadow = shadowPass->LastDirLightIndex == 0 && canUseShadow; if (useShadow) { data.DirectionalLightShadow = shadowPass->LastDirLight; @@ -49,7 +50,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanUnBindSR(dirLightShaderRegisterIndex); } - dirLight.SetupLightData(&data.DirectionalLight, view, useShadow); + dirLight.SetupLightData(&data.DirectionalLight, useShadow); } else { @@ -62,7 +63,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanSkyLights.HasItems()) { auto& skyLight = cache->SkyLights.First(); - skyLight.SetupLightData(&data.SkyLight, view, false); + skyLight.SetupLightData(&data.SkyLight, false); const auto texture = skyLight.Image ? skyLight.Image->GetTexture() : nullptr; context->BindSR(skyLightShaderRegisterIndex, GET_TEXTURE_VIEW_SAFE(texture)); } @@ -103,7 +104,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanPointLights[i]; if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) { - light.SetupLightData(&data.LocalLights[data.LocalLightsCount], view, false); + light.SetupLightData(&data.LocalLights[data.LocalLightsCount], false); data.LocalLightsCount++; } } @@ -112,7 +113,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanSpotLights[i]; if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) { - light.SetupLightData(&data.LocalLights[data.LocalLightsCount], view, false); + light.SetupLightData(&data.LocalLights[data.LocalLightsCount], false); data.LocalLightsCount++; } } diff --git a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp index 808278c04..b93298d7a 100644 --- a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp @@ -67,7 +67,7 @@ void TerrainMaterialShader::Bind(BindParameters& params) bindMeta.Context = context; bindMeta.Constants = cb; bindMeta.Input = nullptr; - bindMeta.Buffers = nullptr; + bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = false; bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index e7078543f..01a61d5eb 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -89,12 +89,12 @@ namespace } else { - auto v = Half2(0, 0); + auto v = Half2::Zero; for (uint32 i = 0; i < vertexCount; i++) vb1[i].TexCoord = v; } { - auto v = Half2(0, 0); + auto v = Half2::Zero; for (uint32 i = 0; i < vertexCount; i++) vb1[i].LightmapUVs = v; } diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index d1e867c6c..f5f6ce94f 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -531,6 +531,17 @@ void MeshData::TransformBuffer(const Matrix& matrix) } } +void MeshData::NormalizeBlendWeights() +{ + ASSERT(Positions.Count() == BlendWeights.Count()); + for (int32 i = 0; i < Positions.Count(); i++) + { + const float sum = BlendWeights[i].SumValues(); + const float invSum = sum > ZeroTolerance ? 1.0f / sum : 0.0f; + BlendWeights[i] *= invSum; + } +} + void MeshData::Merge(MeshData& other) { // Merge index buffer (and remap indices) @@ -600,6 +611,21 @@ bool MaterialSlotEntry::UsesProperties() const Normals.TextureIndex != -1; } +BoundingBox ModelLodData::GetBox() const +{ + if (Meshes.IsEmpty()) + return BoundingBox::Empty; + BoundingBox bounds; + Meshes[0]->CalculateBox(bounds); + for (int32 i = 1; i < Meshes.Count(); i++) + { + BoundingBox b; + Meshes[i]->CalculateBox(b); + BoundingBox::Merge(bounds, b, bounds); + } + return bounds; +} + void ModelData::CalculateLODsScreenSizes() { const float autoComputeLodPowerBase = 0.5f; diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index 4d4733fb7..364400384 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -2,7 +2,6 @@ #pragma once -#include "Engine/Core/Common.h" #include "Engine/Core/Math/BoundingSphere.h" #include "Engine/Core/Math/BoundingBox.h" #include "Engine/Core/Math/Int4.h" @@ -281,16 +280,7 @@ public: /// /// Normalizes the blend weights. Requires to have vertices with positions and blend weights setup. /// - void NormalizeBlendWeights() - { - ASSERT(Positions.Count() == BlendWeights.Count()); - for (int32 i = 0; i < Positions.Count(); i++) - { - const float sum = BlendWeights[i].SumValues(); - const float invSum = sum > ZeroTolerance ? 1.0f / sum : 0.0f; - BlendWeights[i] *= invSum; - } - } + void NormalizeBlendWeights(); /// /// Merges this mesh data with the specified other mesh. @@ -409,6 +399,11 @@ public: { Meshes.ClearDelete(); } + + /// + /// Gets the bounding box combined for all meshes in this model LOD. + /// + BoundingBox GetBox() const; }; /// diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 67f3683bd..062f7eaea 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -404,7 +404,7 @@ bool UpdateMesh(SkinnedMesh* mesh, MonoArray* verticesObj, MonoArray* trianglesO } else { - auto v = Half2(0, 0); + auto v = Half2::Zero; for (uint32 i = 0; i < vertexCount; i++) vb[i].TexCoord = v; } diff --git a/Source/Engine/Graphics/RenderBuffers.cpp b/Source/Engine/Graphics/RenderBuffers.cpp index df2962e52..a98b13d91 100644 --- a/Source/Engine/Graphics/RenderBuffers.cpp +++ b/Source/Engine/Graphics/RenderBuffers.cpp @@ -25,6 +25,11 @@ RenderBuffers::RenderBuffers(const SpawnParams& params) #undef CREATE_TEXTURE } +String RenderBuffers::CustomBuffer::ToString() const +{ + return Name; +} + RenderBuffers::~RenderBuffers() { Release(); @@ -61,6 +66,15 @@ void RenderBuffers::Prepare() UPDATE_LAZY_KEEP_RT(HalfResDepth); UPDATE_LAZY_KEEP_RT(LuminanceMap); #undef UPDATE_LAZY_KEEP_RT + for (int32 i = CustomBuffers.Count() - 1; i >= 0; i--) + { + CustomBuffer* e = CustomBuffers[i]; + if (frameIndex - e->LastFrameUsed >= LAZY_FRAMES_COUNT) + { + Delete(e); + CustomBuffers.RemoveAt(i); + } + } } GPUTexture* RenderBuffers::RequestHalfResDepth(GPUContext* context) @@ -184,4 +198,5 @@ void RenderBuffers::Release() UPDATE_LAZY_KEEP_RT(HalfResDepth); UPDATE_LAZY_KEEP_RT(LuminanceMap); #undef UPDATE_LAZY_KEEP_RT + CustomBuffers.ClearDelete(); } diff --git a/Source/Engine/Graphics/RenderBuffers.h b/Source/Engine/Graphics/RenderBuffers.h index 1eebe47f6..149b4c12b 100644 --- a/Source/Engine/Graphics/RenderBuffers.h +++ b/Source/Engine/Graphics/RenderBuffers.h @@ -16,23 +16,36 @@ #define GBUFFER2_FORMAT PixelFormat::R8G8B8A8_UNorm #define GBUFFER3_FORMAT PixelFormat::R8G8B8A8_UNorm +// Light accumulation buffer format (direct+indirect light, materials emissive) +#define LIGHT_BUFFER_FORMAT PixelFormat::R11G11B10_Float + /// /// The scene rendering buffers container. /// API_CLASS() class FLAXENGINE_API RenderBuffers : public ScriptingObject { -DECLARE_SCRIPTING_TYPE(RenderBuffers); -protected: + DECLARE_SCRIPTING_TYPE(RenderBuffers); + /// + /// The custom rendering state. + /// + class CustomBuffer : public Object + { + public: + String Name; + uint64 LastFrameUsed = 0; + + String ToString() const override; + }; + +protected: int32 _width = 0; int32 _height = 0; float _aspectRatio = 0.0f; Viewport _viewport; - Array> _resources; public: - union { struct @@ -80,15 +93,16 @@ public: GPUTexture* TemporalAA = nullptr; uint64 LastFrameTemporalAA = 0; -public: + // Maps the custom buffer type into the object that holds the state. + Array CustomBuffers; +public: /// /// Finalizes an instance of the class. /// ~RenderBuffers(); public: - /// /// Prepares buffers for rendering a scene. Called before rendering so other parts can reuse calculated value. /// @@ -102,7 +116,6 @@ public: GPUTexture* RequestHalfResDepth(GPUContext* context); public: - /// /// Gets the buffers width (in pixels). /// @@ -143,6 +156,31 @@ public: return _viewport; } + template + const T* FindCustomBuffer(const StringView& name) const + { + for (CustomBuffer* e : CustomBuffers) + { + if (e->Name == name) + return (const T*)e; + } + return nullptr; + } + + template + T* GetCustomBuffer(const StringView& name) + { + for (CustomBuffer* e : CustomBuffers) + { + if (e->Name == name) + return (T*)e; + } + CustomBuffer* result = New(); + result->Name = name; + CustomBuffers.Add(result); + return (T*)result; + } + /// /// Gets the current GPU memory usage by all the buffers (in bytes). /// @@ -162,7 +200,6 @@ public: API_FIELD(ReadOnly) GPUTexture* MotionVectors; public: - /// /// Allocates the buffers. /// diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index 42e7f6805..62ee24b2b 100644 --- a/Source/Engine/Graphics/RenderTask.cpp +++ b/Source/Engine/Graphics/RenderTask.cpp @@ -229,6 +229,8 @@ SceneRenderTask::~SceneRenderTask() { if (Buffers) Buffers->DeleteObjectNow(); + if (_customActorsScene) + Delete(_customActorsScene); } void SceneRenderTask::CameraCut() @@ -270,18 +272,29 @@ void SceneRenderTask::CollectPostFxVolumes(RenderContext& renderContext) } } +void AddActorToSceneRendering(SceneRendering* s, Actor* a) +{ + if (a && a->IsActiveInHierarchy()) + { + int32 key = -1; + s->AddActor(a, key); + for (Actor* child : a->Children) + AddActorToSceneRendering(s, child); + } +} + void SceneRenderTask::OnCollectDrawCalls(RenderContext& renderContext) { // Draw actors (collect draw calls) if ((ActorsSource & ActorsSources::CustomActors) != 0) { - for (auto a : CustomActors) - { - if (a && a->GetIsActive()) - { - a->DrawHierarchy(renderContext); - } - } + if (_customActorsScene == nullptr) + _customActorsScene = New(); + else + _customActorsScene->Clear(); + for (Actor* a : CustomActors) + AddActorToSceneRendering(_customActorsScene, a); + _customActorsScene->Draw(renderContext); } if ((ActorsSource & ActorsSources::Scenes) != 0) { @@ -403,7 +416,7 @@ void SceneRenderTask::OnEnd(GPUContext* context) // Swap matrices View.PrevView = View.View; View.PrevProjection = View.Projection; - Matrix::Multiply(View.PrevView, View.PrevProjection, View.PrevViewProjection); + View.PrevViewProjection = View.ViewProjection(); } bool SceneRenderTask::Resize(int32 width, int32 height) diff --git a/Source/Engine/Graphics/RenderTask.h b/Source/Engine/Graphics/RenderTask.h index 741e9c7ed..0604ccdfc 100644 --- a/Source/Engine/Graphics/RenderTask.h +++ b/Source/Engine/Graphics/RenderTask.h @@ -220,6 +220,10 @@ public: API_CLASS() class FLAXENGINE_API SceneRenderTask : public RenderTask { DECLARE_SCRIPTING_TYPE(SceneRenderTask); +protected: + class SceneRendering* _customActorsScene = nullptr; + +public: /// /// Finalizes an instance of the class. diff --git a/Source/Engine/Graphics/RenderView.h b/Source/Engine/Graphics/RenderView.h index 5a4825a3d..8ba994767 100644 --- a/Source/Engine/Graphics/RenderView.h +++ b/Source/Engine/Graphics/RenderView.h @@ -97,6 +97,16 @@ public: /// API_FIELD() bool IsOfflinePass = false; + /// + /// Flag used by single-frame rendering passes (eg. thumbnail rendering, model view caching) to reject LOD transitions animations and other temporal draw effects. + /// + API_FIELD() bool IsSingleFrame = false; + + /// + /// Flag used by custom passes to skip any object culling when drawing. + /// + API_FIELD() bool IsCullingDisabled = false; + /// /// The static flags mask used to hide objects that don't have a given static flags. Eg. use StaticFlags::Lightmap to render only objects that can use lightmap. /// diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp index d84231406..efaed6d8f 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp @@ -282,16 +282,18 @@ bool ShaderAssetBase::LoadShaderCache(ShaderCacheResult& result) editorDefine.Definition = "1"; #endif InitCompilationOptions(options); - if (ShadersCompilation::Compile(options)) + const bool failed = ShadersCompilation::Compile(options); + + // Encrypt source code + Encryption::EncryptBytes(reinterpret_cast(source), sourceLength); + + if (failed) { LOG(Error, "Failed to compile shader '{0}'", parent->ToString()); return true; } LOG(Info, "Shader '{0}' compiled! Cache size: {1} bytes", parent->ToString(), cacheStream.GetPosition()); - // Encrypt source code - Encryption::EncryptBytes(reinterpret_cast(source), sourceLength); - // Save compilation result (based on current caching policy) if (cachingMode == ShaderStorage::CachingMode::AssetInternal) { @@ -345,7 +347,7 @@ bool ShaderAssetBase::LoadShaderCache(ShaderCacheResult& result) result.Data.Link(cacheChunk->Data); } #if COMPILE_WITH_SHADER_CACHE_MANAGER - // Check if has cached shader + // Check if has cached shader else if (cachedEntry.IsValid() || ShaderCacheManager::TryGetEntry(shaderProfile, parent->GetID(), cachedEntry)) { // Load results from cache diff --git a/Source/Engine/Graphics/Shaders/GPUShader.h b/Source/Engine/Graphics/Shaders/GPUShader.h index 35f239e26..28ef2ee62 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.h +++ b/Source/Engine/Graphics/Shaders/GPUShader.h @@ -12,7 +12,7 @@ class GPUShaderProgram; /// /// The runtime version of the shaders cache supported by the all graphics back-ends. The same for all the shader cache formats (easier to sync and validate). /// -#define GPU_SHADER_CACHE_VERSION 8 +#define GPU_SHADER_CACHE_VERSION 9 /// /// Represents collection of shader programs with permutations and custom names. diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp index f9cf13cab..69131efa7 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.cpp +++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp @@ -431,11 +431,6 @@ bool GPUTexture::Init(const GPUTextureDescription& desc) LOG(Warning, "Cannot create texture. Only 2D Texture can be used as a Depth Stencil. Description: {0}", desc.ToString()); return true; } - if (desc.MipLevels != 1) - { - LOG(Warning, "Cannot create texture. Volume texture cannot have more than 1 mip level. Description: {0}", desc.ToString()); - return true; - } if (desc.ArraySize != 1) { LOG(Warning, "Cannot create texture. Volume texture cannot create array of volume textures. Description: {0}", desc.ToString()); @@ -607,7 +602,25 @@ GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipInde ASSERT(IsAllocated()); ASSERT(mipIndex < MipLevels() && data.IsValid()); ASSERT(data.Length() >= slicePitch); - // TODO: support texture data upload to the GPU on a main thread during rendering without this async task (faster direct upload) + + // Optimize texture upload invoked during rendering + if (IsInMainThread() && GPUDevice::Instance->IsRendering()) + { + // Update all array slices + const byte* dataSource = data.Get(); + for (int32 arrayIndex = 0; arrayIndex < _desc.ArraySize; arrayIndex++) + { + GPUDevice::Instance->GetMainContext()->UpdateTexture(this, arrayIndex, mipIndex, dataSource, rowPitch, slicePitch); + dataSource += slicePitch; + } + if (mipIndex == HighestResidentMipIndex() - 1) + { + // Mark as mip loaded + SetResidentMipLevels(ResidentMipLevels() + 1); + } + return nullptr; + } + auto task = ::New(this, mipIndex, data, rowPitch, slicePitch, copyData); ASSERT_LOW_LAYER(task && task->HasReference(this)); return task; @@ -800,5 +813,6 @@ void GPUTexture::SetResidentMipLevels(int32 count) if (_residentMipLevels == count || !IsRegularTexture()) return; _residentMipLevels = count; - onResidentMipsChanged(); + OnResidentMipsChanged(); + ResidentMipsChanged(this); } diff --git a/Source/Engine/Graphics/Textures/GPUTexture.h b/Source/Engine/Graphics/Textures/GPUTexture.h index 6c4d03063..2171dd373 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.h +++ b/Source/Engine/Graphics/Textures/GPUTexture.h @@ -568,12 +568,17 @@ public: /// Sets the number of resident mipmap levels in the texture (already uploaded to the GPU). /// API_PROPERTY() void SetResidentMipLevels(int32 count); + + /// + /// Event called when texture residency gets changed. Texture Mip gets loaded into GPU memory and is ready to use. + /// + Delegate ResidentMipsChanged; protected: virtual bool OnInit() = 0; uint64 calculateMemoryUsage() const; - virtual void onResidentMipsChanged() = 0; + virtual void OnResidentMipsChanged() = 0; public: diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.cpp b/Source/Engine/Graphics/Textures/StreamingTexture.cpp index 0290048fc..96a023c3b 100644 --- a/Source/Engine/Graphics/Textures/StreamingTexture.cpp +++ b/Source/Engine/Graphics/Textures/StreamingTexture.cpp @@ -233,6 +233,7 @@ protected: { Swap(_streamingTexture->_texture, _newTexture); _streamingTexture->GetTexture()->SetResidentMipLevels(_uploadedMipCount); + _streamingTexture->ResidencyChanged(); SAFE_DELETE_GPU_RESOURCE(_newTexture); // Base @@ -447,6 +448,7 @@ Task* StreamingTexture::CreateStreamingTask(int32 residency) { // Do the quick data release _texture->ReleaseGPU(); + ResidencyChanged(); } else { diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp index f558152f1..37d4a663f 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp @@ -732,7 +732,9 @@ void GPUContextDX11::UpdateTexture(GPUTexture* texture, int32 arrayIndex, int32 auto textureDX11 = static_cast(texture); const int32 subresourceIndex = RenderToolsDX::CalcSubresourceIndex(mipIndex, arrayIndex, texture->MipLevels()); - const uint32 depthPitch = texture->IsVolume() ? slicePitch / texture->Depth() : slicePitch; + uint32 depthPitch = slicePitch; + if (texture->IsVolume()) + depthPitch /= Math::Max(1, texture->Depth() >> mipIndex); _context->UpdateSubresource(textureDX11->GetResource(), subresourceIndex, nullptr, data, (UINT)rowPitch, (UINT)depthPitch); //D3D11_MAPPED_SUBRESOURCE mapped; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index 89d791f55..1b08c06ec 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -518,12 +518,12 @@ bool GPUDeviceDX11::Init() dsDesc.FrontFace = defaultStencilOp; dsDesc.BackFace = defaultStencilOp; int32 index; -#define CREATE_DEPTH_STENCIL_STATE(depthTextEnable, depthWrite) \ - dsDesc.DepthEnable = depthTextEnable; \ +#define CREATE_DEPTH_STENCIL_STATE(depthEnable, depthWrite) \ + dsDesc.DepthEnable = depthEnable; \ dsDesc.DepthWriteMask = depthWrite ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO; \ for(int32 depthFunc = 1; depthFunc <= 8; depthFunc++) { \ dsDesc.DepthFunc = (D3D11_COMPARISON_FUNC)depthFunc; \ - index = (int32)depthFunc + (depthTextEnable ? 0 : 9) + (depthWrite ? 0 : 18); \ + index = (int32)depthFunc + (depthEnable ? 0 : 9) + (depthWrite ? 0 : 18); \ HRESULT result = _device->CreateDepthStencilState(&dsDesc, &DepthStencilStates[index]); \ LOG_DIRECTX_RESULT_WITH_RETURN(result); } CREATE_DEPTH_STENCIL_STATE(false, false); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.cpp index f5e071f68..8927f9435 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.cpp @@ -109,37 +109,37 @@ bool GPUTextureDX11::OnInit() return false; } -void GPUTextureDX11::onResidentMipsChanged() +void GPUTextureDX11::OnResidentMipsChanged() { - // We support changing resident mip maps only for regular textures (render targets and depth buffers don't use that feature at all) - ASSERT(IsRegularTexture() && _handlesPerSlice.Count() == 1); - ASSERT(!IsVolume()); - - // Fill description + const int32 firstMipIndex = MipLevels() - ResidentMipLevels(); + const int32 mipLevels = ResidentMipLevels(); D3D11_SHADER_RESOURCE_VIEW_DESC srDesc; srDesc.Format = _dxgiFormatSRV; if (IsCubeMap()) { srDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE; - srDesc.TextureCube.MostDetailedMip = MipLevels() - ResidentMipLevels(); - srDesc.TextureCube.MipLevels = ResidentMipLevels(); + srDesc.TextureCube.MostDetailedMip = firstMipIndex; + srDesc.TextureCube.MipLevels = mipLevels; + } + else if (IsVolume()) + { + srDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; + srDesc.Texture3D.MostDetailedMip = firstMipIndex; + srDesc.Texture3D.MipLevels = mipLevels; } else { srDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srDesc.Texture2D.MostDetailedMip = MipLevels() - ResidentMipLevels(); - srDesc.Texture2D.MipLevels = ResidentMipLevels(); + srDesc.Texture2D.MostDetailedMip = firstMipIndex; + srDesc.Texture2D.MipLevels = mipLevels; } - - // Create new view ID3D11ShaderResourceView* srView; VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateShaderResourceView(_resource, &srDesc, &srView)); - - // Change view - if (_handlesPerSlice[0].GetParent() == nullptr) - _handlesPerSlice[0].Init(this, nullptr, srView, nullptr, nullptr, Format(), MultiSampleLevel()); + GPUTextureViewDX11& view = IsVolume() ? _handleVolume : _handlesPerSlice[0]; + if (view.GetParent() == nullptr) + view.Init(this, nullptr, srView, nullptr, nullptr, Format(), MultiSampleLevel()); else - _handlesPerSlice[0].SetSRV(srView); + view.SetSRV(srView); } void GPUTextureDX11::OnReleaseGPU() diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.h b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.h index 51bdff1d4..b40bee48d 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.h @@ -277,7 +277,7 @@ protected: // [GPUTexture] bool OnInit() override; - void onResidentMipsChanged() override; + void OnResidentMipsChanged() override; void OnReleaseGPU() override; }; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp index 1bba5880d..5945257e8 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp @@ -208,36 +208,39 @@ bool GPUTextureDX12::OnInit() return false; } -void GPUTextureDX12::onResidentMipsChanged() +void GPUTextureDX12::OnResidentMipsChanged() { - // We support changing resident mip maps only for regular textures (render targets and depth buffers don't use that feature at all) - ASSERT(IsRegularTexture() && _handlesPerSlice.Count() == 1); - ASSERT(!IsVolume()); - - // Fill description + const int32 firstMipIndex = MipLevels() - ResidentMipLevels(); + const int32 mipLevels = ResidentMipLevels(); D3D12_SHADER_RESOURCE_VIEW_DESC srDesc; srDesc.Format = _dxgiFormatSRV; srDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; if (IsCubeMap()) { srDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE; - srDesc.TextureCube.MostDetailedMip = MipLevels() - ResidentMipLevels(); - srDesc.TextureCube.MipLevels = ResidentMipLevels(); + srDesc.TextureCube.MostDetailedMip = firstMipIndex; + srDesc.TextureCube.MipLevels = mipLevels; srDesc.TextureCube.ResourceMinLODClamp = 0; } + else if (IsVolume()) + { + srDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D; + srDesc.Texture3D.MostDetailedMip = firstMipIndex; + srDesc.Texture3D.MipLevels = mipLevels; + srDesc.Texture3D.ResourceMinLODClamp = 0; + } else { srDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; - srDesc.Texture2D.MostDetailedMip = MipLevels() - ResidentMipLevels(); - srDesc.Texture2D.MipLevels = ResidentMipLevels(); + srDesc.Texture2D.MostDetailedMip = firstMipIndex; + srDesc.Texture2D.MipLevels = mipLevels; srDesc.Texture2D.PlaneSlice = 0; srDesc.Texture2D.ResourceMinLODClamp = 0; } - - // Change view - if (_handlesPerSlice[0].GetParent() == nullptr) - _handlesPerSlice[0].Init(this, _device, this, Format(), MultiSampleLevel()); - _handlesPerSlice[0].SetSRV(srDesc); + GPUTextureViewDX12& view = IsVolume() ? _handleVolume : _handlesPerSlice[0]; + if (view.GetParent() == nullptr) + view.Init(this, _device, this, Format(), MultiSampleLevel()); + view.SetSRV(srDesc); } void GPUTextureDX12::OnReleaseGPU() diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.h index c91471846..db274c91a 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.h @@ -216,7 +216,7 @@ protected: // [GPUTexture] bool OnInit() override; - void onResidentMipsChanged() override; + void OnResidentMipsChanged() override; void OnReleaseGPU() override; }; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp index eaf4a4afb..7bba6a3ba 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp @@ -90,29 +90,30 @@ bool UploadBufferDX12::UploadTexture(GPUContextDX12* context, ID3D12Resource* te _device->GetDevice()->GetCopyableFootprints(&resourceDesc, subresourceIndex, 1, 0, &footprint, &numRows, &rowPitchAligned, &mipSizeAligned); rowPitchAligned = footprint.Footprint.RowPitch; mipSizeAligned = rowPitchAligned * footprint.Footprint.Height; + const uint32 numSlices = resourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D ? Math::Max(1, resourceDesc.DepthOrArraySize >> mipIndex) : 1; + const uint64 sliceSizeAligned = numSlices * mipSizeAligned; // Allocate data - const DynamicAllocation allocation = Allocate(mipSizeAligned, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT); - if (allocation.Size != mipSizeAligned) + const DynamicAllocation allocation = Allocate(sliceSizeAligned, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT); + if (allocation.Size != sliceSizeAligned) return true; - // Check if can copy rows at once byte* ptr = (byte*)srcData; - ASSERT(srcSlicePitch <= mipSizeAligned); - if (srcRowPitch == rowPitchAligned) + ASSERT(srcSlicePitch <= sliceSizeAligned); + if (srcSlicePitch == sliceSizeAligned) { // Copy data at once Platform::MemoryCopy(allocation.CPUAddress, ptr, srcSlicePitch); } else { - // Use per row copy + // Copy data per-row byte* dst = static_cast(allocation.CPUAddress); ASSERT(srcRowPitch <= rowPitchAligned); - for (uint32 i = 0; i < numRows; i++) + const uint32 numCopies = numSlices * numRows; + for (uint32 i = 0; i < numCopies; i++) { Platform::MemoryCopy(dst, ptr, srcRowPitch); - dst += rowPitchAligned; ptr += srcRowPitch; } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.h index 0e031d402..dfff8b203 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.h @@ -141,9 +141,8 @@ struct DynamicAllocation } /// - /// Returns true if allocation is invalid + /// Returns true if allocation is invalid. /// - /// True if allocation in invalid bool IsInvalid() const { return CPUAddress == nullptr || Size == 0 || Page == nullptr; diff --git a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h index 3dcf92ced..0dfcef1ff 100644 --- a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h +++ b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h @@ -212,16 +212,17 @@ namespace RenderToolsDX if (errorCode == DXGI_ERROR_DEVICE_REMOVED || errorCode == DXGI_ERROR_DEVICE_RESET || errorCode == DXGI_ERROR_DRIVER_INTERNAL_ERROR) { HRESULT reason = S_OK; + const RendererType rendererType = GPUDevice::Instance ? GPUDevice::Instance->GetRendererType() : RendererType::Unknown; #if GRAPHICS_API_DIRECTX12 - if (GPUDevice::Instance->GetRendererType() == RendererType::DirectX12) + if (rendererType == RendererType::DirectX12) { reason = ((ID3D12Device*)GPUDevice::Instance->GetNativePtr())->GetDeviceRemovedReason(); } #endif #if GRAPHICS_API_DIRECTX11 - if (GPUDevice::Instance->GetRendererType() == RendererType::DirectX11 || - GPUDevice::Instance->GetRendererType() == RendererType::DirectX10_1 || - GPUDevice::Instance->GetRendererType() == RendererType::DirectX10) + if (rendererType == RendererType::DirectX11 || + rendererType == RendererType::DirectX10_1 || + rendererType == RendererType::DirectX10) { reason = ((ID3D11Device*)GPUDevice::Instance->GetNativePtr())->GetDeviceRemovedReason(); } diff --git a/Source/Engine/GraphicsDevice/Null/GPUTextureNull.h b/Source/Engine/GraphicsDevice/Null/GPUTextureNull.h index abfd5aaaf..81b3c22d7 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUTextureNull.h +++ b/Source/Engine/GraphicsDevice/Null/GPUTextureNull.h @@ -57,7 +57,7 @@ protected: return false; } - void onResidentMipsChanged() override + void OnResidentMipsChanged() override { } }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp index 37ef2722e..171e60b8b 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp @@ -8,7 +8,6 @@ #include "GPUDeviceVulkan.h" #include "RenderToolsVulkan.h" #include "GPUContextVulkan.h" -#include "GPUAdapterVulkan.h" #include "CmdBufferVulkan.h" #include "Engine/Threading/Threading.h" #include "Engine/Engine/Engine.h" @@ -30,39 +29,48 @@ void DescriptorSetLayoutInfoVulkan::CacheTypesUsageID() _typesUsageID = id; } -void DescriptorSetLayoutInfoVulkan::AddDescriptor(int32 descriptorSetIndex, const VkDescriptorSetLayoutBinding& descriptor) -{ - _layoutTypes[descriptor.descriptorType]++; - - if (descriptorSetIndex >= _setLayouts.Count()) - { - _setLayouts.Resize(descriptorSetIndex + 1); - } - - SetLayout& descSetLayout = _setLayouts[descriptorSetIndex]; - descSetLayout.LayoutBindings.Add(descriptor); - - _hash = Crc::MemCrc32(&descriptor, sizeof(descriptor), _hash); -} - void DescriptorSetLayoutInfoVulkan::AddBindingsForStage(VkShaderStageFlagBits stageFlags, DescriptorSet::Stage descSet, const SpirvShaderDescriptorInfo* descriptorInfo) { - const int32 descriptorSetIndex = (int32)descSet; + const int32 descriptorSetIndex = descSet; + if (descriptorSetIndex >= _setLayouts.Count()) + _setLayouts.Resize(descriptorSetIndex + 1); + SetLayout& descSetLayout = _setLayouts[descriptorSetIndex]; VkDescriptorSetLayoutBinding binding; - binding.descriptorCount = 1; binding.stageFlags = stageFlags; binding.pImmutableSamplers = nullptr; - for (uint32 descriptorIndex = 0; descriptorIndex < descriptorInfo->DescriptorTypesCount; descriptorIndex++) { auto& descriptor = descriptorInfo->DescriptorTypes[descriptorIndex]; binding.binding = descriptorIndex; binding.descriptorType = descriptor.DescriptorType; - AddDescriptor(descriptorSetIndex, binding); + binding.descriptorCount = descriptor.Count; + + _layoutTypes[binding.descriptorType]++; + descSetLayout.LayoutBindings.Add(binding); + _hash = Crc::MemCrc32(&binding, sizeof(binding), _hash); } } +bool DescriptorSetLayoutInfoVulkan::operator==(const DescriptorSetLayoutInfoVulkan& other) const +{ + if (other._setLayouts.Count() != _setLayouts.Count()) + return false; + if (other._typesUsageID != _typesUsageID) + return false; + + for (int32 index = 0; index < other._setLayouts.Count(); index++) + { + const int32 bindingsCount = _setLayouts[index].LayoutBindings.Count(); + if (other._setLayouts[index].LayoutBindings.Count() != bindingsCount) + return false; + if (bindingsCount != 0 && Platform::MemoryCompare(other._setLayouts[index].LayoutBindings.Get(), _setLayouts[index].LayoutBindings.Get(), bindingsCount * sizeof(VkDescriptorSetLayoutBinding))) + return false; + } + + return true; +} + DescriptorSetLayoutVulkan::DescriptorSetLayoutVulkan(GPUDeviceVulkan* device) : _device(device) { @@ -370,20 +378,20 @@ PipelineLayoutVulkan::~PipelineLayoutVulkan() } } -uint32 DescriptorSetWriterVulkan::SetupDescriptorWrites(const SpirvShaderDescriptorInfo& info, VkWriteDescriptorSet* writeDescriptors, VkDescriptorImageInfo* imageInfo, VkDescriptorBufferInfo* bufferInfo, uint8* bindingToDynamicOffset) +uint32 DescriptorSetWriterVulkan::SetupDescriptorWrites(const SpirvShaderDescriptorInfo& info, VkWriteDescriptorSet* writeDescriptors, VkDescriptorImageInfo* imageInfo, VkDescriptorBufferInfo* bufferInfo, VkBufferView* texelBufferView, uint8* bindingToDynamicOffset) { - ASSERT(info.DescriptorTypesCount <= 64); + ASSERT(info.DescriptorTypesCount <= SpirvShaderDescriptorInfo::MaxDescriptors); WriteDescriptors = writeDescriptors; WritesCount = info.DescriptorTypesCount; BindingToDynamicOffset = bindingToDynamicOffset; - uint32 dynamicOffsetIndex = 0; for (uint32 i = 0; i < info.DescriptorTypesCount; i++) { + const auto& descriptor = info.DescriptorTypes[i]; writeDescriptors->sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeDescriptors->dstBinding = i; - writeDescriptors->descriptorCount = 1; - writeDescriptors->descriptorType = info.DescriptorTypes[i].DescriptorType; + writeDescriptors->descriptorCount = descriptor.Count; + writeDescriptors->descriptorType = descriptor.DescriptorType; switch (writeDescriptors->descriptorType) { @@ -394,25 +402,28 @@ uint32 DescriptorSetWriterVulkan::SetupDescriptorWrites(const SpirvShaderDescrip break; case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: - writeDescriptors->pBufferInfo = bufferInfo++; + writeDescriptors->pBufferInfo = bufferInfo; + bufferInfo += descriptor.Count; break; case VK_DESCRIPTOR_TYPE_SAMPLER: case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: - writeDescriptors->pImageInfo = imageInfo++; + writeDescriptors->pImageInfo = imageInfo; + imageInfo += descriptor.Count; break; case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: + writeDescriptors->pTexelBufferView = texelBufferView; + texelBufferView += descriptor.Count; break; default: - CRASH; + CRASH; break; } writeDescriptors++; } - return dynamicOffsetIndex; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h index da95ac48b..695da1be9 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h @@ -117,7 +117,6 @@ protected: uint32 _typesUsageID = ~0; void CacheTypesUsageID(); - void AddDescriptor(int32 descriptorSetIndex, const VkDescriptorSetLayoutBinding& descriptor); public: @@ -138,11 +137,6 @@ public: return _setLayouts; } - inline const uint32* GetLayoutTypes() const - { - return _layoutTypes; - } - inline uint32 GetTypesUsageID() const { return _typesUsageID; @@ -160,25 +154,7 @@ public: _setLayouts = info._setLayouts; } - inline bool operator ==(const DescriptorSetLayoutInfoVulkan& other) const - { - if (other._setLayouts.Count() != _setLayouts.Count()) - return false; - if (other._typesUsageID != _typesUsageID) - return false; - - for (int32 index = 0; index < other._setLayouts.Count(); index++) - { - const int32 bindingsCount = _setLayouts[index].LayoutBindings.Count(); - if (other._setLayouts[index].LayoutBindings.Count() != bindingsCount) - return false; - - if (bindingsCount != 0 && Platform::MemoryCompare(other._setLayouts[index].LayoutBindings.Get(), _setLayouts[index].LayoutBindings.Get(), bindingsCount * sizeof(VkDescriptorSetLayoutBinding))) - return false; - } - - return true; - } + bool operator==(const DescriptorSetLayoutInfoVulkan& other) const; friend inline uint32 GetHash(const DescriptorSetLayoutInfoVulkan& key) { @@ -413,6 +389,7 @@ struct DescriptorSetWriteContainerVulkan { Array DescriptorImageInfo; Array DescriptorBufferInfo; + Array DescriptorTexelBufferView; Array DescriptorWrites; Array BindingToDynamicOffset; @@ -420,6 +397,7 @@ struct DescriptorSetWriteContainerVulkan { DescriptorImageInfo.Resize(0); DescriptorBufferInfo.Resize(0); + DescriptorTexelBufferView.Resize(0); DescriptorWrites.Resize(0); BindingToDynamicOffset.Resize(0); } @@ -436,26 +414,24 @@ public: public: - uint32 SetupDescriptorWrites(const SpirvShaderDescriptorInfo& info, VkWriteDescriptorSet* writeDescriptors, VkDescriptorImageInfo* imageInfo, VkDescriptorBufferInfo* bufferInfo, byte* bindingToDynamicOffset); + uint32 SetupDescriptorWrites(const SpirvShaderDescriptorInfo& info, VkWriteDescriptorSet* writeDescriptors, VkDescriptorImageInfo* imageInfo, VkDescriptorBufferInfo* bufferInfo, VkBufferView* texelBufferView, byte* bindingToDynamicOffset); - bool WriteUniformBuffer(uint32 descriptorIndex, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range) const + bool WriteUniformBuffer(uint32 descriptorIndex, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range, uint32 index = 0) const { ASSERT(descriptorIndex < WritesCount); ASSERT(WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); - VkDescriptorBufferInfo* bufferInfo = const_cast(WriteDescriptors[descriptorIndex].pBufferInfo); - ASSERT(bufferInfo); + auto* bufferInfo = const_cast(WriteDescriptors[descriptorIndex].pBufferInfo + index); bool edited = DescriptorSet::CopyAndReturnNotEqual(bufferInfo->buffer, buffer); edited |= DescriptorSet::CopyAndReturnNotEqual(bufferInfo->offset, offset); edited |= DescriptorSet::CopyAndReturnNotEqual(bufferInfo->range, range); return edited; } - bool WriteDynamicUniformBuffer(uint32 descriptorIndex, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range, uint32 dynamicOffset) const + bool WriteDynamicUniformBuffer(uint32 descriptorIndex, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range, uint32 dynamicOffset, uint32 index = 0) const { ASSERT(descriptorIndex < WritesCount); ASSERT(WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC); - VkDescriptorBufferInfo* bufferInfo = const_cast(WriteDescriptors[descriptorIndex].pBufferInfo); - ASSERT(bufferInfo); + auto* bufferInfo = const_cast(WriteDescriptors[descriptorIndex].pBufferInfo + index); bool edited = DescriptorSet::CopyAndReturnNotEqual(bufferInfo->buffer, buffer); edited |= DescriptorSet::CopyAndReturnNotEqual(bufferInfo->offset, offset); edited |= DescriptorSet::CopyAndReturnNotEqual(bufferInfo->range, range); @@ -464,63 +440,61 @@ public: return edited; } - bool WriteSampler(uint32 descriptorIndex, VkSampler sampler) const + bool WriteSampler(uint32 descriptorIndex, VkSampler sampler, uint32 index = 0) const { ASSERT(descriptorIndex < WritesCount); ASSERT(WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER || WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); - VkDescriptorImageInfo* imageInfo = const_cast(WriteDescriptors[descriptorIndex].pImageInfo); - ASSERT(imageInfo); + auto* imageInfo = const_cast(WriteDescriptors[descriptorIndex].pImageInfo + index); bool edited = DescriptorSet::CopyAndReturnNotEqual(imageInfo->sampler, sampler); return edited; } - bool WriteImage(uint32 descriptorIndex, VkImageView imageView, VkImageLayout layout) const + bool WriteImage(uint32 descriptorIndex, VkImageView imageView, VkImageLayout layout, uint32 index = 0) const { ASSERT(descriptorIndex < WritesCount); ASSERT(WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE || WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); - VkDescriptorImageInfo* imageInfo = const_cast(WriteDescriptors[descriptorIndex].pImageInfo); - ASSERT(imageInfo); + auto* imageInfo = const_cast(WriteDescriptors[descriptorIndex].pImageInfo + index); bool edited = DescriptorSet::CopyAndReturnNotEqual(imageInfo->imageView, imageView); edited |= DescriptorSet::CopyAndReturnNotEqual(imageInfo->imageLayout, layout); return edited; } - bool WriteStorageImage(uint32 descriptorIndex, VkImageView imageView, VkImageLayout layout) const + bool WriteStorageImage(uint32 descriptorIndex, VkImageView imageView, VkImageLayout layout, uint32 index = 0) const { ASSERT(descriptorIndex < WritesCount); ASSERT(WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE); - VkDescriptorImageInfo* imageInfo = const_cast(WriteDescriptors[descriptorIndex].pImageInfo); - ASSERT(imageInfo); + auto* imageInfo = const_cast(WriteDescriptors[descriptorIndex].pImageInfo + index); bool edited = DescriptorSet::CopyAndReturnNotEqual(imageInfo->imageView, imageView); edited |= DescriptorSet::CopyAndReturnNotEqual(imageInfo->imageLayout, layout); return edited; } - bool WriteStorageTexelBuffer(uint32 descriptorIndex, const VkBufferView* bufferView) const + bool WriteStorageTexelBuffer(uint32 descriptorIndex, VkBufferView bufferView, uint32 index = 0) const { ASSERT(descriptorIndex < WritesCount); ASSERT(WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER); - WriteDescriptors[descriptorIndex].pTexelBufferView = bufferView; + auto* bufferInfo = const_cast(WriteDescriptors[descriptorIndex].pTexelBufferView + index); + *bufferInfo = bufferView; return true; } - bool WriteStorageBuffer(uint32 descriptorIndex, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range) const + bool WriteStorageBuffer(uint32 descriptorIndex, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range, uint32 index = 0) const { ASSERT(descriptorIndex < WritesCount); ASSERT(WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER || WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC); - VkDescriptorBufferInfo* bufferInfo = const_cast(WriteDescriptors[descriptorIndex].pBufferInfo); - ASSERT(bufferInfo); + auto* bufferInfo = const_cast(WriteDescriptors[descriptorIndex].pBufferInfo + index); bool edited = DescriptorSet::CopyAndReturnNotEqual(bufferInfo->buffer, buffer); edited |= DescriptorSet::CopyAndReturnNotEqual(bufferInfo->offset, offset); edited |= DescriptorSet::CopyAndReturnNotEqual(bufferInfo->range, range); return edited; } - bool WriteUniformTexelBuffer(uint32 descriptorIndex, const VkBufferView* view) const + bool WriteUniformTexelBuffer(uint32 descriptorIndex, VkBufferView view, uint32 index = 0) const { ASSERT(descriptorIndex < WritesCount); ASSERT(WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); - return DescriptorSet::CopyAndReturnNotEqual(WriteDescriptors[descriptorIndex].pTexelBufferView, view); + auto* bufferInfo = const_cast(WriteDescriptors[descriptorIndex].pTexelBufferView + index); + return DescriptorSet::CopyAndReturnNotEqual(*bufferInfo, view); } void SetDescriptorSet(VkDescriptorSet descriptorSet) const diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp index 8cdf5969d..0dda4488f 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp @@ -44,10 +44,10 @@ void GPUBufferViewVulkan::Release() #endif } -void GPUBufferViewVulkan::DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, const VkBufferView*& bufferView) +void GPUBufferViewVulkan::DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, VkBufferView& bufferView) { ASSERT_LOW_LAYER(View != VK_NULL_HANDLE); - bufferView = &View; + bufferView = View; context->AddBufferBarrier(Owner, VK_ACCESS_SHADER_READ_BIT); } @@ -60,10 +60,10 @@ void GPUBufferViewVulkan::DescriptorAsStorageBuffer(GPUContextVulkan* context, V context->AddBufferBarrier(Owner, VK_ACCESS_SHADER_READ_BIT); } -void GPUBufferViewVulkan::DescriptorAsStorageTexelBuffer(GPUContextVulkan* context, const VkBufferView*& bufferView) +void GPUBufferViewVulkan::DescriptorAsStorageTexelBuffer(GPUContextVulkan* context, VkBufferView& bufferView) { ASSERT_LOW_LAYER(View != VK_NULL_HANDLE); - bufferView = &View; + bufferView = View; context->AddBufferBarrier(Owner, VK_ACCESS_SHADER_READ_BIT); } @@ -98,7 +98,7 @@ bool GPUBufferVulkan::OnInit() bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; if (useSRV && !(_desc.Flags & GPUBufferFlags::Structured)) bufferInfo.usage |= VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT; - if (useUAV || _desc.Flags & GPUBufferFlags::RawBuffer) + if (useUAV || _desc.Flags & GPUBufferFlags::RawBuffer || _desc.Flags & GPUBufferFlags::Structured) bufferInfo.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; if (useUAV && useSRV) bufferInfo.usage |= VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.h index a495cf46c..9b78c266f 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.h @@ -48,9 +48,9 @@ public: } // [DescriptorOwnerResourceVulkan] - void DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, const VkBufferView*& bufferView) override; + void DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, VkBufferView& bufferView) override; void DescriptorAsStorageBuffer(GPUContextVulkan* context, VkBuffer& buffer, VkDeviceSize& offset, VkDeviceSize& range) override; - void DescriptorAsStorageTexelBuffer(GPUContextVulkan* context, const VkBufferView*& bufferView) override; + void DescriptorAsStorageTexelBuffer(GPUContextVulkan* context, VkBufferView& bufferView) override; }; /// diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index 1142e3a64..e9d64ce71 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -444,117 +444,122 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des const auto& descriptor = descriptorInfo.DescriptorTypes[i]; const int32 descriptorIndex = descriptor.Binding; DescriptorOwnerResourceVulkan** handles = _handles[(int32)descriptor.BindingType]; - - switch (descriptor.DescriptorType) + for (uint32 index = 0; index < descriptor.Count; index++) { - case VK_DESCRIPTOR_TYPE_SAMPLER: - { - // Sampler - const VkSampler sampler = _samplerHandles[descriptor.Slot]; - ASSERT(sampler); - needsWrite |= dsWriter.WriteSampler(descriptorIndex, sampler); - break; - } - case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: - { - // Shader Resource (Texture) - auto handle = (GPUTextureViewVulkan*)handles[descriptor.Slot]; - if (!handle) + const int32 slot = descriptor.Slot + index; + switch (descriptor.DescriptorType) { - const auto dummy = _device->HelperResources.GetDummyTexture(descriptor.ResourceType); - switch (descriptor.ResourceType) + case VK_DESCRIPTOR_TYPE_SAMPLER: + { + // Sampler + const VkSampler sampler = _samplerHandles[slot]; + ASSERT(sampler); + needsWrite |= dsWriter.WriteSampler(descriptorIndex, sampler, index); + break; + } + case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: + { + // Shader Resource (Texture) + auto handle = (GPUTextureViewVulkan*)handles[slot]; + if (!handle) { - case SpirvShaderResourceType::Texture1D: - case SpirvShaderResourceType::Texture2D: - handle = static_cast(dummy->View(0)); - break; - case SpirvShaderResourceType::Texture3D: - handle = static_cast(dummy->ViewVolume()); - break; - case SpirvShaderResourceType::TextureCube: - case SpirvShaderResourceType::Texture1DArray: - case SpirvShaderResourceType::Texture2DArray: - handle = static_cast(dummy->ViewArray()); - break; + const auto dummy = _device->HelperResources.GetDummyTexture(descriptor.ResourceType); + switch (descriptor.ResourceType) + { + case SpirvShaderResourceType::Texture1D: + case SpirvShaderResourceType::Texture2D: + handle = static_cast(dummy->View(0)); + break; + case SpirvShaderResourceType::Texture3D: + handle = static_cast(dummy->ViewVolume()); + break; + case SpirvShaderResourceType::TextureCube: + case SpirvShaderResourceType::Texture1DArray: + case SpirvShaderResourceType::Texture2DArray: + handle = static_cast(dummy->ViewArray()); + break; + } } + VkImageView imageView; + VkImageLayout layout; + handle->DescriptorAsImage(this, imageView, layout); + ASSERT(imageView != VK_NULL_HANDLE); + needsWrite |= dsWriter.WriteImage(descriptorIndex, imageView, layout, index); + break; } - VkImageView imageView; - VkImageLayout layout; - handle->DescriptorAsImage(this, imageView, layout); - ASSERT(imageView != VK_NULL_HANDLE); - needsWrite |= dsWriter.WriteImage(descriptorIndex, imageView, layout); - break; - } - case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: - { - // Shader Resource (Buffer) - auto sr = handles[descriptor.Slot]; - if (!sr) + case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: { - const auto dummy = _device->HelperResources.GetDummyBuffer(); - sr = (DescriptorOwnerResourceVulkan*)dummy->View()->GetNativePtr(); + // Shader Resource (Buffer) + auto sr = handles[slot]; + if (!sr) + { + const auto dummy = _device->HelperResources.GetDummyBuffer(); + sr = (DescriptorOwnerResourceVulkan*)dummy->View()->GetNativePtr(); + } + VkBufferView bufferView; + sr->DescriptorAsUniformTexelBuffer(this, bufferView); + ASSERT(bufferView != VK_NULL_HANDLE); + needsWrite |= dsWriter.WriteUniformTexelBuffer(descriptorIndex, bufferView, index); + break; } - const VkBufferView* bufferView; - sr->DescriptorAsUniformTexelBuffer(this, bufferView); - needsWrite |= dsWriter.WriteUniformTexelBuffer(descriptorIndex, bufferView); - break; - } - case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: - { - // Unordered Access (Texture) - auto ua = handles[descriptor.Slot]; - ASSERT(ua); - VkImageView imageView; - VkImageLayout layout; - ua->DescriptorAsStorageImage(this, imageView, layout); - needsWrite |= dsWriter.WriteStorageImage(descriptorIndex, imageView, layout); - break; - } - case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: - { - // Unordered Access (Buffer) - auto ua = handles[descriptor.Slot]; - if (!ua) + case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: { - const auto dummy = _device->HelperResources.GetDummyBuffer(); - ua = (DescriptorOwnerResourceVulkan*)dummy->View()->GetNativePtr(); + // Unordered Access (Texture) + auto ua = handles[slot]; + ASSERT(ua); + VkImageView imageView; + VkImageLayout layout; + ua->DescriptorAsStorageImage(this, imageView, layout); + needsWrite |= dsWriter.WriteStorageImage(descriptorIndex, imageView, layout, index); + break; } - VkBuffer buffer; - VkDeviceSize offset, range; - ua->DescriptorAsStorageBuffer(this, buffer, offset, range); - needsWrite |= dsWriter.WriteStorageBuffer(descriptorIndex, buffer, offset, range); - break; - } - case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: - { - // Unordered Access (Buffer) - auto ua = handles[descriptor.Slot]; - if (!ua) + case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: { - const auto dummy = _device->HelperResources.GetDummyBuffer(); - ua = (DescriptorOwnerResourceVulkan*)dummy->View()->GetNativePtr(); + // Unordered Access (Buffer) + auto ua = handles[slot]; + if (!ua) + { + const auto dummy = _device->HelperResources.GetDummyBuffer(); + ua = (DescriptorOwnerResourceVulkan*)dummy->View()->GetNativePtr(); + } + VkBuffer buffer; + VkDeviceSize offset, range; + ua->DescriptorAsStorageBuffer(this, buffer, offset, range); + needsWrite |= dsWriter.WriteStorageBuffer(descriptorIndex, buffer, offset, range, index); + break; + } + case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: + { + // Unordered Access (Buffer) + auto ua = handles[slot]; + if (!ua) + { + const auto dummy = _device->HelperResources.GetDummyBuffer(); + ua = (DescriptorOwnerResourceVulkan*)dummy->View()->GetNativePtr(); + } + VkBufferView bufferView; + ua->DescriptorAsStorageTexelBuffer(this, bufferView); + ASSERT(bufferView != VK_NULL_HANDLE); + needsWrite |= dsWriter.WriteStorageTexelBuffer(descriptorIndex, bufferView, index); + break; + } + case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: + { + // Constant Buffer + auto cb = handles[slot]; + ASSERT(cb); + VkBuffer buffer; + VkDeviceSize offset, range; + uint32 dynamicOffset; + cb->DescriptorAsDynamicUniformBuffer(this, buffer, offset, range, dynamicOffset); + needsWrite |= dsWriter.WriteDynamicUniformBuffer(descriptorIndex, buffer, offset, range, dynamicOffset, index); + break; + } + default: + // Unknown or invalid descriptor type + CRASH; + break; } - const VkBufferView* bufferView; - ua->DescriptorAsStorageTexelBuffer(this, bufferView); - needsWrite |= dsWriter.WriteStorageTexelBuffer(descriptorIndex, bufferView); - break; - } - case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: - { - // Constant Buffer - auto cb = handles[descriptor.Slot]; - ASSERT(cb); - VkBuffer buffer; - VkDeviceSize offset, range; - uint32 dynamicOffset; - cb->DescriptorAsDynamicUniformBuffer(this, buffer, offset, range, dynamicOffset); - needsWrite |= dsWriter.WriteDynamicUniformBuffer(descriptorIndex, buffer, offset, range, dynamicOffset); - break; - } - default: - // Unknown or invalid descriptor type - CRASH; - break; } } } @@ -1102,6 +1107,10 @@ void GPUContextVulkan::Dispatch(GPUShaderProgramCS* shader, uint32 threadGroupCo vkCmdDispatch(cmdBuffer->GetHandle(), threadGroupCountX, threadGroupCountY, threadGroupCountZ); RENDER_STAT_DISPATCH_CALL(); + // Place a barrier between dispatches, so that UAVs can be read+write in subsequent passes + // TODO: optimize it by moving inputs/outputs into higher-layer so eg. Global SDF can manually optimize it + vkCmdPipelineBarrier(cmdBuffer->GetHandle(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 0, nullptr); + #if VK_ENABLE_BARRIERS_DEBUG LOG(Warning, "Dispatch"); #endif @@ -1136,6 +1145,10 @@ void GPUContextVulkan::DispatchIndirect(GPUShaderProgramCS* shader, GPUBuffer* b vkCmdDispatchIndirect(cmdBuffer->GetHandle(), bufferForArgsVulkan->GetHandle(), offsetForArgs); RENDER_STAT_DISPATCH_CALL(); + // Place a barrier between dispatches, so that UAVs can be read+write in subsequent passes + // TODO: optimize it by moving inputs/outputs into higher-layer so eg. Global SDF can manually optimize it + vkCmdPipelineBarrier(cmdBuffer->GetHandle(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 0, nullptr); + #if VK_ENABLE_BARRIERS_DEBUG LOG(Warning, "DispatchIndirect"); #endif @@ -1494,7 +1507,7 @@ void GPUContextVulkan::CopyResource(GPUResource* dstResource, GPUResource* srcRe ASSERT(bufferCopy.size == dstBufferVulkan->GetSize()); vkCmdCopyBuffer(cmdBuffer->GetHandle(), srcBufferVulkan->GetHandle(), dstBufferVulkan->GetHandle(), 1, &bufferCopy); } - // Texture -> Texture + // Texture -> Texture else if (srcType == GPUResource::ObjectType::Texture && dstType == GPUResource::ObjectType::Texture) { if (dstTextureVulkan->IsStaging()) @@ -1505,7 +1518,7 @@ void GPUContextVulkan::CopyResource(GPUResource* dstResource, GPUResource* srcRe ASSERT(dstTextureVulkan->StagingBuffer && srcTextureVulkan->StagingBuffer); CopyResource(dstTextureVulkan->StagingBuffer, srcTextureVulkan->StagingBuffer); } - // Texture -> Staging Texture + // Texture -> Staging Texture else { // Transition resources @@ -1635,7 +1648,7 @@ void GPUContextVulkan::CopySubresource(GPUResource* dstResource, uint32 dstSubre ASSERT(bufferCopy.size == dstBufferVulkan->GetSize()); vkCmdCopyBuffer(cmdBuffer->GetHandle(), srcBufferVulkan->GetHandle(), dstBufferVulkan->GetHandle(), 1, &bufferCopy); } - // Texture -> Texture + // Texture -> Texture else if (srcType == GPUResource::ObjectType::Texture && dstType == GPUResource::ObjectType::Texture) { const int32 dstMipMaps = dstTextureVulkan->MipLevels(); @@ -1653,7 +1666,7 @@ void GPUContextVulkan::CopySubresource(GPUResource* dstResource, uint32 dstSubre ASSERT(dstTextureVulkan->StagingBuffer && srcTextureVulkan->StagingBuffer); CopyResource(dstTextureVulkan->StagingBuffer, srcTextureVulkan->StagingBuffer); } - // Texture -> Staging Texture + // Texture -> Staging Texture else { // Transition resources diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 1bc66b5b6..fc8e1f4bc 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -209,11 +209,22 @@ static VKAPI_ATTR VkBool32 VKAPI_PTR DebugUtilsCallback(VkDebugUtilsMessageSever type = TEXT("Perf"); } - if (callbackData->pMessageIdName) - LOG(Info, "[Vulkan] {0} {1}:{2}({3}) {4}", type, severity, callbackData->messageIdNumber, String(callbackData->pMessageIdName), String(callbackData->pMessage)); - else - LOG(Info, "[Vulkan] {0} {1}:{2} {3}", type, severity, callbackData->messageIdNumber, String(callbackData->pMessage)); + // Fix invalid characters in hex values (bug in Debug Layer) + char* handleStart = (char*)StringUtils::FindIgnoreCase(callbackData->pMessage, "0x"); + while (handleStart != nullptr) + { + while (*handleStart != ' ' && *handleStart != 0) + *handleStart++ = Math::Clamp(*handleStart, '0', 'z'); + if (*handleStart == 0) + break; + handleStart = (char*)StringUtils::FindIgnoreCase(handleStart, "0x"); + } + const String message(callbackData->pMessage); + if (callbackData->pMessageIdName) + LOG(Info, "[Vulkan] {0} {1}:{2}({3}) {4}", type, severity, callbackData->messageIdNumber, String(callbackData->pMessageIdName), message); + else + LOG(Info, "[Vulkan] {0} {1}:{2} {3}", type, severity, callbackData->messageIdNumber, message); return VK_FALSE; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h index 20f6d324f..f7fc6a221 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h @@ -782,7 +782,7 @@ public: /// /// The GPU context. Can be used to add memory barriers to the pipeline before binding the descriptor to the pipeline. /// The buffer view. - virtual void DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, const VkBufferView*& bufferView) + virtual void DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, VkBufferView& bufferView) { CRASH; } @@ -804,7 +804,7 @@ public: /// /// The GPU context. Can be used to add memory barriers to the pipeline before binding the descriptor to the pipeline. /// The buffer view. - virtual void DescriptorAsStorageTexelBuffer(GPUContextVulkan* context, const VkBufferView*& bufferView) + virtual void DescriptorAsStorageTexelBuffer(GPUContextVulkan* context, VkBufferView& bufferView) { CRASH; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp index d7dc050b7..0cccccf38 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp @@ -51,9 +51,11 @@ ComputePipelineStateVulkan* GPUShaderProgramCSVulkan::GetOrCreateState() uint32 dynamicOffsetsCount = 0; if (DescriptorInfo.DescriptorTypesCount != 0) { + // TODO: merge into a single allocation _pipelineState->DSWriteContainer.DescriptorWrites.AddZeroed(DescriptorInfo.DescriptorTypesCount); _pipelineState->DSWriteContainer.DescriptorImageInfo.AddZeroed(DescriptorInfo.ImageInfosCount); _pipelineState->DSWriteContainer.DescriptorBufferInfo.AddZeroed(DescriptorInfo.BufferInfosCount); + _pipelineState->DSWriteContainer.DescriptorTexelBufferView.AddZeroed(DescriptorInfo.TexelBufferViewsCount); ASSERT(DescriptorInfo.DescriptorTypesCount < 255); _pipelineState->DSWriteContainer.BindingToDynamicOffset.AddDefault(DescriptorInfo.DescriptorTypesCount); @@ -62,9 +64,10 @@ ComputePipelineStateVulkan* GPUShaderProgramCSVulkan::GetOrCreateState() VkWriteDescriptorSet* currentDescriptorWrite = _pipelineState->DSWriteContainer.DescriptorWrites.Get(); VkDescriptorImageInfo* currentImageInfo = _pipelineState->DSWriteContainer.DescriptorImageInfo.Get(); VkDescriptorBufferInfo* currentBufferInfo = _pipelineState->DSWriteContainer.DescriptorBufferInfo.Get(); + VkBufferView* currentTexelBufferView = _pipelineState->DSWriteContainer.DescriptorTexelBufferView.Get(); uint8* currentBindingToDynamicOffsetMap = _pipelineState->DSWriteContainer.BindingToDynamicOffset.Get(); - dynamicOffsetsCount = _pipelineState->DSWriter.SetupDescriptorWrites(DescriptorInfo, currentDescriptorWrite, currentImageInfo, currentBufferInfo, currentBindingToDynamicOffsetMap); + dynamicOffsetsCount = _pipelineState->DSWriter.SetupDescriptorWrites(DescriptorInfo, currentDescriptorWrite, currentImageInfo, currentBufferInfo, currentTexelBufferView, currentBindingToDynamicOffsetMap); } _pipelineState->DynamicOffsets.AddZeroed(dynamicOffsetsCount); @@ -337,9 +340,11 @@ bool GPUPipelineStateVulkan::Init(const Description& desc) if (descriptor == nullptr || descriptor->DescriptorTypesCount == 0) continue; + // TODO: merge into a single allocation for a whole PSO DSWriteContainer.DescriptorWrites.AddZeroed(descriptor->DescriptorTypesCount); DSWriteContainer.DescriptorImageInfo.AddZeroed(descriptor->ImageInfosCount); DSWriteContainer.DescriptorBufferInfo.AddZeroed(descriptor->BufferInfosCount); + DSWriteContainer.DescriptorTexelBufferView.AddZeroed(descriptor->TexelBufferViewsCount); ASSERT(descriptor->DescriptorTypesCount < 255); DSWriteContainer.BindingToDynamicOffset.AddDefault(descriptor->DescriptorTypesCount); @@ -349,6 +354,7 @@ bool GPUPipelineStateVulkan::Init(const Description& desc) VkWriteDescriptorSet* currentDescriptorWrite = DSWriteContainer.DescriptorWrites.Get(); VkDescriptorImageInfo* currentImageInfo = DSWriteContainer.DescriptorImageInfo.Get(); VkDescriptorBufferInfo* currentBufferInfo = DSWriteContainer.DescriptorBufferInfo.Get(); + VkBufferView* currentTexelBufferView = DSWriteContainer.DescriptorTexelBufferView.Get(); byte* currentBindingToDynamicOffsetMap = DSWriteContainer.BindingToDynamicOffset.Get(); uint32 dynamicOffsetsStart[DescriptorSet::GraphicsStagesCount]; uint32 dynamicOffsetsCount = 0; @@ -360,12 +366,13 @@ bool GPUPipelineStateVulkan::Init(const Description& desc) if (descriptor == nullptr || descriptor->DescriptorTypesCount == 0) continue; - const uint32 numDynamicOffsets = DSWriter[stage].SetupDescriptorWrites(*descriptor, currentDescriptorWrite, currentImageInfo, currentBufferInfo, currentBindingToDynamicOffsetMap); + const uint32 numDynamicOffsets = DSWriter[stage].SetupDescriptorWrites(*descriptor, currentDescriptorWrite, currentImageInfo, currentBufferInfo, currentTexelBufferView, currentBindingToDynamicOffsetMap); dynamicOffsetsCount += numDynamicOffsets; currentDescriptorWrite += descriptor->DescriptorTypesCount; currentImageInfo += descriptor->ImageInfosCount; currentBufferInfo += descriptor->BufferInfosCount; + currentTexelBufferView += descriptor->TexelBufferViewsCount; currentBindingToDynamicOffsetMap += descriptor->DescriptorTypesCount; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp index be8f26255..eb4466ab8 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp @@ -425,29 +425,19 @@ void GPUTextureVulkan::initHandles() } } -void GPUTextureVulkan::onResidentMipsChanged() +void GPUTextureVulkan::OnResidentMipsChanged() { - // We support changing resident mip maps only for regular textures (render targets and depth buffers don't use that feature at all) - ASSERT(IsRegularTexture() && _handlesPerSlice.Count() == 1); - ASSERT(!IsVolume()); - - // Change view - auto& handle = _handlesPerSlice[0]; - handle.Release(); + // Update view VkExtent3D extent; extent.width = Width(); extent.height = Height(); extent.depth = Depth(); const int32 firstMipIndex = MipLevels() - ResidentMipLevels(); const int32 mipLevels = ResidentMipLevels(); - if (IsCubeMap()) - { - handle.Init(_device, this, _image, mipLevels, Format(), MultiSampleLevel(), extent, VK_IMAGE_VIEW_TYPE_CUBE, mipLevels, firstMipIndex, ArraySize()); - } - else - { - handle.Init(_device, this, _image, mipLevels, Format(), MultiSampleLevel(), extent, VK_IMAGE_VIEW_TYPE_2D, mipLevels, firstMipIndex, ArraySize()); - } + const VkImageViewType viewType = IsVolume() ? VK_IMAGE_VIEW_TYPE_3D : (IsCubeMap() ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D); + GPUTextureViewVulkan& view = IsVolume() ? _handleVolume : _handlesPerSlice[0]; + view.Release(); + view.Init(_device, this, _image, mipLevels, Format(), MultiSampleLevel(), extent, viewType, mipLevels, firstMipIndex, ArraySize()); } void GPUTextureVulkan::OnReleaseGPU() diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.h index aec0ed428..fcbbd2c01 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.h @@ -182,7 +182,7 @@ protected: // [GPUTexture] bool OnInit() override; - void onResidentMipsChanged() override; + void OnResidentMipsChanged() override; void OnReleaseGPU() override; }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/Types.h b/Source/Engine/GraphicsDevice/Vulkan/Types.h index a97a777d2..f7ebe3f93 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/Types.h +++ b/Source/Engine/GraphicsDevice/Vulkan/Types.h @@ -94,10 +94,16 @@ struct SpirvShaderDescriptorInfo /// The resource type. /// SpirvShaderResourceType ResourceType; + + /// + /// The amount of slots used by the descriptor (eg. array of textures size). + /// + uint32 Count; }; uint16 ImageInfosCount; uint16 BufferInfosCount; + uint32 TexelBufferViewsCount; uint32 DescriptorTypesCount; Descriptor DescriptorTypes[MaxDescriptors]; }; diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 5ca9e957a..95e1e9865 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -81,6 +81,7 @@ Actor::Actor(const SpawnParams& params) , _physicsScene(nullptr) , HideFlags(HideFlags::None) { + _drawNoCulling = 0; } SceneRendering* Actor::GetSceneRendering() const @@ -1232,45 +1233,6 @@ void Actor::Draw(RenderContext& renderContext) { } -void Actor::DrawGeneric(RenderContext& renderContext) -{ - // Generic drawing uses only GBuffer Fill Pass and simple frustum culling (see SceneRendering for more optimized drawing) - if (renderContext.View.Pass & DrawPass::GBuffer) - { - Draw(renderContext); - } -} - -void Actor::DrawHierarchy(RenderContext& renderContext) -{ - // Draw actor itself - DrawGeneric(renderContext); - - // Draw children - if (renderContext.View.IsOfflinePass) - { - for (int32 i = 0; i < Children.Count(); i++) - { - auto child = Children[i]; - if (child->GetIsActive() && child->GetStaticFlags() & renderContext.View.StaticFlagsMask) - { - child->DrawHierarchy(renderContext); - } - } - } - else - { - for (int32 i = 0; i < Children.Count(); i++) - { - auto child = Children[i]; - if (child->GetIsActive()) - { - child->DrawHierarchy(renderContext); - } - } - } -} - #if USE_EDITOR void Actor::OnDebugDraw() diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 2a7f39df6..3600c21bb 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -30,6 +30,7 @@ DECLARE_SCENE_OBJECT(Actor); friend Level; friend PrefabManager; friend Scene; + friend SceneRendering; friend Prefab; friend PrefabInstanceData; @@ -39,6 +40,7 @@ protected: int8 _isActiveInHierarchy : 1; int8 _isPrefabRoot : 1; int8 _isEnabled : 1; + int8 _drawNoCulling : 1; byte _layer; byte _tag; StaticFlags _staticFlags; @@ -646,23 +648,11 @@ public: public: /// - /// Draws this actor. Called by Scene Rendering service. This call is more optimized than generic Draw (eg. models are rendered during all passed but other actors are invoked only during GBufferFill pass). + /// Draws this actor. Called by Scene Rendering service. This call is more optimized than generic Draw (eg. geometry is rendered during all pass types but other actors are drawn only during GBufferFill pass). /// /// The rendering context. virtual void Draw(RenderContext& renderContext); - /// - /// Draws this actor. Called during custom actor rendering or any other generic rendering from code. - /// - /// The rendering context. - virtual void DrawGeneric(RenderContext& renderContext); - - /// - /// Draws this actor and all its children (full scene hierarchy part). - /// - /// The rendering context. - void DrawHierarchy(RenderContext& renderContext); - #if USE_EDITOR /// diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 44b50ac3a..eb385ce8b 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -548,7 +548,7 @@ void AnimatedModel::UpdateBounds() BoundingBox::Transform(_boxLocal, _world, _box); BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void AnimatedModel::UpdateSockets() @@ -692,6 +692,10 @@ void AnimatedModel::Update() void AnimatedModel::Draw(RenderContext& renderContext) { + if (renderContext.View.Pass == DrawPass::GlobalSDF) + return; // TODO: Animated Model rendering to Global SDF + if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) + return; // No supported GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world); const DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass & (int32)renderContext.View.GetShadowsDrawPassMask(ShadowsMode)); @@ -728,14 +732,6 @@ void AnimatedModel::Draw(RenderContext& renderContext) GEOMETRY_DRAW_STATE_EVENT_END(_drawState, _world); } -void AnimatedModel::DrawGeneric(RenderContext& renderContext) -{ - if (renderContext.View.RenderLayersMask.Mask & GetLayerMask() && renderContext.View.CullingFrustum.Intersects(_box)) - { - Draw(renderContext); - } -} - #if USE_EDITOR #include "Engine/Debug/DebugDraw.h" @@ -817,6 +813,13 @@ void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* m DESERIALIZE(RootMotionTarget); Entries.DeserializeIfExists(stream, "Buffer", modifier); + + // [Deprecated on 07.02.2022, expires on 07.02.2024] + if (modifier->EngineBuild <= 6330) + DrawModes |= DrawPass::GlobalSDF; + // [Deprecated on 27.04.2022, expires on 27.04.2024] + if (modifier->EngineBuild <= 6331) + DrawModes |= DrawPass::GlobalSurfaceAtlas; } bool AnimatedModel::IntersectsEntry(int32 entryIndex, const Ray& ray, float& distance, Vector3& normal) @@ -881,5 +884,5 @@ void AnimatedModel::OnTransformChanged() BoundingBox::Transform(_boxLocal, _world, _box); BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index d8491e78a..9eb45a169 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -376,7 +376,6 @@ public: // [ModelInstanceActor] bool HasContentLoaded() const override; void Draw(RenderContext& renderContext) override; - void DrawGeneric(RenderContext& renderContext) override; #if USE_EDITOR void OnDebugDrawSelected() override; BoundingBox GetEditorBox() const override; diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp index ab736c673..288a060c3 100644 --- a/Source/Engine/Level/Actors/Camera.cpp +++ b/Source/Engine/Level/Actors/Camera.cpp @@ -249,7 +249,9 @@ bool Camera::HasContentLoaded() const void Camera::Draw(RenderContext& renderContext) { - if (renderContext.View.Flags & ViewFlags::EditorSprites && _previewModel && _previewModel->IsLoaded()) + if (renderContext.View.Flags & ViewFlags::EditorSprites + && _previewModel + && _previewModel->IsLoaded()) { GeometryDrawStateData drawState; Mesh::DrawInfo draw; @@ -259,14 +261,16 @@ void Camera::Draw(RenderContext& renderContext) draw.Lightmap = nullptr; draw.LightmapUVs = nullptr; draw.Flags = StaticFlags::Transform; - draw.DrawModes = (DrawPass)(DrawPass::Default & renderContext.View.Pass); + draw.DrawModes = (DrawPass)((DrawPass::Depth | DrawPass::GBuffer | DrawPass::Forward) & renderContext.View.Pass); BoundingSphere::FromBox(_previewModelBox, draw.Bounds); draw.PerInstanceRandom = GetPerInstanceRandom(); draw.LODBias = 0; draw.ForcedLOD = -1; draw.VertexColors = nullptr; - - _previewModel->Draw(renderContext, draw); + if (draw.DrawModes != DrawPass::None) + { + _previewModel->Draw(renderContext, draw); + } } } @@ -354,7 +358,7 @@ void Camera::OnEnable() { Cameras.Add(this); #if USE_EDITOR - GetSceneRendering()->AddCommonNoCulling(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #endif // Base @@ -364,7 +368,7 @@ void Camera::OnEnable() void Camera::OnDisable() { #if USE_EDITOR - GetSceneRendering()->RemoveCommonNoCulling(this); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); #endif Cameras.Remove(this); if (CutSceneCamera == this) diff --git a/Source/Engine/Level/Actors/Camera.h b/Source/Engine/Level/Actors/Camera.h index 938fe1bdc..35d2df94d 100644 --- a/Source/Engine/Level/Actors/Camera.h +++ b/Source/Engine/Level/Actors/Camera.h @@ -52,6 +52,7 @@ private: ModelInstanceEntries _previewModelBuffer; BoundingBox _previewModelBox; Matrix _previewModelWorld; + int32 _sceneRenderingKey = -1; #endif public: diff --git a/Source/Engine/Level/Actors/Decal.cpp b/Source/Engine/Level/Actors/Decal.cpp index a55f51c85..20df5e21b 100644 --- a/Source/Engine/Level/Actors/Decal.cpp +++ b/Source/Engine/Level/Actors/Decal.cpp @@ -69,12 +69,13 @@ BoundingBox Decal::GetEditorBox() const void Decal::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateCommon(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void Decal::Draw(RenderContext& renderContext) { if ((renderContext.View.Flags & ViewFlags::Decals) != 0 && + renderContext.View.Pass & DrawPass::GBuffer && Material && Material->IsLoaded() && Material->IsDecal()) @@ -121,7 +122,7 @@ bool Decal::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) void Decal::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddCommon(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -135,7 +136,7 @@ void Decal::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommon(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); @@ -156,5 +157,5 @@ void Decal::OnTransformChanged() BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateCommon(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } diff --git a/Source/Engine/Level/Actors/DirectionalLight.cpp b/Source/Engine/Level/Actors/DirectionalLight.cpp index 854b36aac..38ac0407d 100644 --- a/Source/Engine/Level/Actors/DirectionalLight.cpp +++ b/Source/Engine/Level/Actors/DirectionalLight.cpp @@ -10,6 +10,7 @@ DirectionalLight::DirectionalLight(const SpawnParams& params) : LightWithShadow(params) { + _drawNoCulling = 1; Brightness = 8.0f; } @@ -19,6 +20,7 @@ void DirectionalLight::Draw(RenderContext& renderContext) AdjustBrightness(renderContext.View, brightness); if (Brightness > ZeroTolerance && (renderContext.View.Flags & ViewFlags::DirectionalLights) != 0 + && renderContext.View.Pass & DrawPass::GBuffer && (ViewDistance < ZeroTolerance || Vector3::DistanceSquared(renderContext.View.Position, GetPosition()) < ViewDistance * ViewDistance)) { RendererDirectionalLightData data; @@ -67,7 +69,7 @@ bool DirectionalLight::IntersectsItself(const Ray& ray, float& distance, Vector3 void DirectionalLight::OnEnable() { - GetSceneRendering()->AddCommonNoCulling(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -81,7 +83,7 @@ void DirectionalLight::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommonNoCulling(this); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base LightWithShadow::OnDisable(); diff --git a/Source/Engine/Level/Actors/DirectionalLight.h b/Source/Engine/Level/Actors/DirectionalLight.h index ab6330953..ae966559e 100644 --- a/Source/Engine/Level/Actors/DirectionalLight.h +++ b/Source/Engine/Level/Actors/DirectionalLight.h @@ -9,9 +9,11 @@ /// API_CLASS() class FLAXENGINE_API DirectionalLight : public LightWithShadow { -DECLARE_SCENE_OBJECT(DirectionalLight); -public: + DECLARE_SCENE_OBJECT(DirectionalLight); +private: + int32 _sceneRenderingKey = -1; +public: /// /// The number of cascades used for slicing the range of depth covered by the light. Values are 1, 2 or 4 cascades; a typical scene uses 4 cascades. /// @@ -19,7 +21,6 @@ public: int32 CascadeCount = 4; public: - // [LightWithShadow] void Draw(RenderContext& renderContext) override; void Serialize(SerializeStream& stream, const void* otherObj) override; @@ -27,7 +28,6 @@ public: bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; protected: - // [LightWithShadow] void OnEnable() override; void OnDisable() override; diff --git a/Source/Engine/Level/Actors/EnvironmentProbe.cpp b/Source/Engine/Level/Actors/EnvironmentProbe.cpp index 9778836eb..51bab7cf4 100644 --- a/Source/Engine/Level/Actors/EnvironmentProbe.cpp +++ b/Source/Engine/Level/Actors/EnvironmentProbe.cpp @@ -127,12 +127,15 @@ void EnvironmentProbe::UpdateBounds() _sphere = BoundingSphere(GetPosition(), GetScaledRadius()); BoundingBox::FromSphere(_sphere, _box); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateCommon(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void EnvironmentProbe::Draw(RenderContext& renderContext) { - if (Brightness > ZeroTolerance && (renderContext.View.Flags & ViewFlags::Reflections) != 0 && HasProbeLoaded()) + if (Brightness > ZeroTolerance && + (renderContext.View.Flags & ViewFlags::Reflections) != 0 && + renderContext.View.Pass & DrawPass::GBuffer && + HasProbeLoaded()) { renderContext.List->EnvironmentProbes.Add(this); } @@ -156,7 +159,7 @@ void EnvironmentProbe::OnDebugDrawSelected() void EnvironmentProbe::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateCommon(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void EnvironmentProbe::Serialize(SerializeStream& stream, const void* otherObj) @@ -199,7 +202,7 @@ bool EnvironmentProbe::IntersectsItself(const Ray& ray, float& distance, Vector3 void EnvironmentProbe::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddCommon(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -213,7 +216,7 @@ void EnvironmentProbe::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommon(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp index 34d1dd255..82d5dd7bb 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp @@ -16,6 +16,8 @@ ExponentialHeightFog::ExponentialHeightFog(const SpawnParams& params) : Actor(params) { + _drawNoCulling = 1; + // Load shader _shader = Content::LoadAsyncInternal(TEXT("Shaders/Fog")); if (_shader == nullptr) @@ -31,7 +33,11 @@ void ExponentialHeightFog::Draw(RenderContext& renderContext) { // Render only when shader is valid and fog can be rendered // Do not render exponential fog in orthographic views - if ((renderContext.View.Flags & ViewFlags::Fog) != 0 && _shader && _shader->IsLoaded() && renderContext.View.IsPerspectiveProjection()) + if ((renderContext.View.Flags & ViewFlags::Fog) != 0 + && renderContext.View.Pass & DrawPass::GBuffer + && _shader + && _shader->IsLoaded() + && renderContext.View.IsPerspectiveProjection()) { // Prepare if (_psFog.States[0] == nullptr) @@ -203,7 +209,7 @@ void ExponentialHeightFog::DrawFog(GPUContext* context, RenderContext& renderCon void ExponentialHeightFog::OnEnable() { - GetSceneRendering()->AddCommonNoCulling(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -217,7 +223,7 @@ void ExponentialHeightFog::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommonNoCulling(this); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.h b/Source/Engine/Level/Actors/ExponentialHeightFog.h index dc205ce62..302fd07b3 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.h +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.h @@ -14,14 +14,13 @@ /// API_CLASS() class FLAXENGINE_API ExponentialHeightFog : public Actor, public IFogRenderer { -DECLARE_SCENE_OBJECT(ExponentialHeightFog); + DECLARE_SCENE_OBJECT(ExponentialHeightFog); private: - AssetReference _shader; GPUPipelineStatePermutationsPs<2> _psFog; + int32 _sceneRenderingKey = -1; public: - /// /// The fog density factor. /// @@ -61,7 +60,6 @@ public: float FogCutoffDistance = 0.0f; public: - /// /// Directional light used for Directional Inscattering. /// @@ -90,7 +88,6 @@ public: Color DirectionalInscatteringColor = Color(0.25, 0.25f, 0.125f); public: - /// /// Whether to enable Volumetric fog. Graphics quality settings control the resolution of the fog simulation. /// @@ -133,7 +130,6 @@ public: float VolumetricFogDistance = 6000.0f; private: - #if COMPILE_WITH_DEV_ENV void OnShaderReloading(Asset* obj) { @@ -142,7 +138,6 @@ private: #endif public: - // [Actor] #if USE_EDITOR BoundingBox GetEditorBox() const override @@ -163,7 +158,6 @@ public: void DrawFog(GPUContext* context, RenderContext& renderContext, GPUTextureView* output) override; protected: - // [Actor] void OnEnable() override; void OnDisable() override; diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.cpp b/Source/Engine/Level/Actors/ModelInstanceActor.cpp index 69e78fd6a..8cf3a3620 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.cpp +++ b/Source/Engine/Level/Actors/ModelInstanceActor.cpp @@ -34,12 +34,12 @@ MaterialInstance* ModelInstanceActor::CreateAndSetVirtualMaterialInstance(int32 void ModelInstanceActor::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void ModelInstanceActor::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddGeometry(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); // Base Actor::OnEnable(); @@ -47,8 +47,8 @@ void ModelInstanceActor::OnEnable() void ModelInstanceActor::OnDisable() { - GetSceneRendering()->RemoveGeometry(this, _sceneRenderingKey); - // Base Actor::OnDisable(); + + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); } diff --git a/Source/Engine/Level/Actors/PointLight.cpp b/Source/Engine/Level/Actors/PointLight.cpp index 36aa08805..04024e480 100644 --- a/Source/Engine/Level/Actors/PointLight.cpp +++ b/Source/Engine/Level/Actors/PointLight.cpp @@ -66,12 +66,12 @@ void PointLight::UpdateBounds() BoundingBox::FromSphere(_sphere, _box); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateCommon(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void PointLight::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddCommon(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -85,7 +85,7 @@ void PointLight::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommon(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base LightWithShadow::OnDisable(); @@ -106,6 +106,7 @@ void PointLight::Draw(RenderContext& renderContext) const float radius = GetScaledRadius(); if ((renderContext.View.Flags & ViewFlags::PointLights) != 0 && brightness > ZeroTolerance + && renderContext.View.Pass & DrawPass::GBuffer && radius > ZeroTolerance && (ViewDistance < ZeroTolerance || Vector3::DistanceSquared(renderContext.View.Position, GetPosition()) < ViewDistance * ViewDistance)) { @@ -165,7 +166,7 @@ void PointLight::OnDebugDrawSelected() void PointLight::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateCommon(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void PointLight::Serialize(SerializeStream& stream, const void* otherObj) diff --git a/Source/Engine/Level/Actors/Sky.cpp b/Source/Engine/Level/Actors/Sky.cpp index 046602010..59d95d3f8 100644 --- a/Source/Engine/Level/Actors/Sky.cpp +++ b/Source/Engine/Level/Actors/Sky.cpp @@ -29,6 +29,8 @@ Sky::Sky(const SpawnParams& params) , _psSky(nullptr) , _psFog(nullptr) { + _drawNoCulling = 1; + // Load shader _shader = Content::LoadAsyncInternal(TEXT("Shaders/Sky")); if (_shader == nullptr) @@ -237,7 +239,7 @@ void Sky::EndPlay() void Sky::OnEnable() { - GetSceneRendering()->AddCommonNoCulling(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -251,7 +253,7 @@ void Sky::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommonNoCulling(this); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); diff --git a/Source/Engine/Level/Actors/Sky.h b/Source/Engine/Level/Actors/Sky.h index 57c2fd42a..5f2f78a2f 100644 --- a/Source/Engine/Level/Actors/Sky.h +++ b/Source/Engine/Level/Actors/Sky.h @@ -16,19 +16,17 @@ class GPUPipelineState; /// API_CLASS() class FLAXENGINE_API Sky : public Actor, public IAtmosphericFogRenderer, public ISkyRenderer { -DECLARE_SCENE_OBJECT(Sky); + DECLARE_SCENE_OBJECT(Sky); private: - AssetReference _shader; GPUPipelineState* _psSky; GPUPipelineState* _psFog; + int32 _sceneRenderingKey = -1; public: - ~Sky(); public: - /// /// Directional light that is used to simulate the sun. /// @@ -48,7 +46,6 @@ public: float SunPower = 8.0f; private: - #if COMPILE_WITH_DEV_ENV void OnShaderReloading(Asset* obj) { @@ -59,7 +56,6 @@ private: void InitConfig(AtmosphericFogData& config) const; public: - // [Actor] #if USE_EDITOR BoundingBox GetEditorBox() const override @@ -81,7 +77,6 @@ public: void ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) override; protected: - // [Actor] void EndPlay() override; void OnEnable() override; diff --git a/Source/Engine/Level/Actors/SkyLight.cpp b/Source/Engine/Level/Actors/SkyLight.cpp index 2fd4d226b..6bab5440b 100644 --- a/Source/Engine/Level/Actors/SkyLight.cpp +++ b/Source/Engine/Level/Actors/SkyLight.cpp @@ -17,6 +17,7 @@ SkyLight::SkyLight(const SpawnParams& params) : Light(params) , _radius(1000000.0f) { + _drawNoCulling = 1; Brightness = 2.0f; UpdateBounds(); } @@ -100,6 +101,7 @@ void SkyLight::Draw(RenderContext& renderContext) float brightness = Brightness; AdjustBrightness(renderContext.View, brightness); if ((renderContext.View.Flags & ViewFlags::SkyLights) != 0 + && renderContext.View.Pass & DrawPass::GBuffer && brightness > ZeroTolerance && (ViewDistance < ZeroTolerance || Vector3::DistanceSquared(renderContext.View.Position, GetPosition()) < ViewDistance * ViewDistance)) { @@ -170,7 +172,7 @@ bool SkyLight::HasContentLoaded() const void SkyLight::OnEnable() { - GetSceneRendering()->AddCommonNoCulling(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -184,7 +186,7 @@ void SkyLight::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommonNoCulling(this); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Light::OnDisable(); diff --git a/Source/Engine/Level/Actors/SkyLight.h b/Source/Engine/Level/Actors/SkyLight.h index ad5a7f7f4..605be3af2 100644 --- a/Source/Engine/Level/Actors/SkyLight.h +++ b/Source/Engine/Level/Actors/SkyLight.h @@ -11,9 +11,8 @@ /// API_CLASS() class FLAXENGINE_API SkyLight : public Light { -DECLARE_SCENE_OBJECT(SkyLight); + DECLARE_SCENE_OBJECT(SkyLight); public: - /// /// Sky light source mode. /// @@ -31,12 +30,11 @@ public: }; private: - AssetReference _bakedProbe; float _radius; + int32 _sceneRenderingKey = -1; public: - /// /// Additional color to add. Source texture colors are summed with it. Can be used to apply custom ambient color. /// @@ -62,7 +60,6 @@ public: AssetReference CustomTexture; public: - /// /// Gets the radius. /// @@ -98,7 +95,6 @@ public: } public: - /// /// Bakes that probe. /// @@ -112,11 +108,9 @@ public: void SetProbeData(TextureData& data); private: - void UpdateBounds(); public: - // [Light] void Draw(RenderContext& renderContext) override; #if USE_EDITOR @@ -127,7 +121,6 @@ public: bool HasContentLoaded() const override; protected: - // [Light] void OnEnable() override; void OnDisable() override; diff --git a/Source/Engine/Level/Actors/Skybox.cpp b/Source/Engine/Level/Actors/Skybox.cpp index 25510a5ff..f76beff7e 100644 --- a/Source/Engine/Level/Actors/Skybox.cpp +++ b/Source/Engine/Level/Actors/Skybox.cpp @@ -14,6 +14,7 @@ Skybox::Skybox(const SpawnParams& params) : Actor(params) { + _drawNoCulling = 1; } void Skybox::setupProxy() @@ -121,7 +122,7 @@ void Skybox::ApplySky(GPUContext* context, RenderContext& renderContext, const M void Skybox::OnEnable() { - GetSceneRendering()->AddCommonNoCulling(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -135,7 +136,7 @@ void Skybox::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommonNoCulling(this); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); diff --git a/Source/Engine/Level/Actors/Skybox.h b/Source/Engine/Level/Actors/Skybox.h index ba64be45d..3e44dd015 100644 --- a/Source/Engine/Level/Actors/Skybox.h +++ b/Source/Engine/Level/Actors/Skybox.h @@ -13,13 +13,12 @@ /// API_CLASS() class FLAXENGINE_API Skybox : public Actor, public ISkyRenderer { -DECLARE_SCENE_OBJECT(Skybox); + DECLARE_SCENE_OBJECT(Skybox); private: - AssetReference _proxyMaterial; + int32 _sceneRenderingKey = -1; public: - /// /// The cube texture to draw. /// @@ -51,11 +50,9 @@ public: float Exposure = 0.0f; private: - void setupProxy(); public: - // [Actor] #if USE_EDITOR BoundingBox GetEditorBox() const override @@ -74,7 +71,6 @@ public: void ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) override; protected: - // [Actor] void OnEnable() override; void OnDisable() override; diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 6794ccbb2..f2443570e 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -210,7 +210,7 @@ void SplineModel::OnSplineUpdated() BoundingSphere::Merge(_sphere, _instances[i].Sphere, _sphere); BoundingBox::FromSphere(_sphere, _box); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void SplineModel::UpdateDeformationBuffer() @@ -258,7 +258,7 @@ void SplineModel::UpdateDeformationBuffer() AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); for (int32 chunk = 0; chunk < chunksPerSegment; chunk++) { - const float alpha = (chunk == chunksPerSegment - 1)? 1.0f : ((float)chunk * chunksPerSegmentInv); + const float alpha = (chunk == chunksPerSegment - 1) ? 1.0f : ((float)chunk * chunksPerSegmentInv); // Evaluate transformation at the curve AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, transform); @@ -350,6 +350,10 @@ void SplineModel::Draw(RenderContext& renderContext) if (!_spline || !Model || !Model->IsLoaded() || !Model->CanBeRendered() || actorDrawModes == DrawPass::None) return; auto model = Model.Get(); + if (renderContext.View.Pass == DrawPass::GlobalSDF) + return; // TODO: Spline Model rendering to Global SDF + if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) + return; // TODO: Spline Model rendering to Global Surface Atlas if (!Entries.IsValidFor(model)) Entries.Setup(model); @@ -374,7 +378,7 @@ void SplineModel::Draw(RenderContext& renderContext) for (int32 segment = 0; segment < _instances.Count(); segment++) { auto& instance = _instances[segment]; - if (!renderContext.View.CullingFrustum.Intersects(instance.Sphere)) + if (!(renderContext.View.IsCullingDisabled || renderContext.View.CullingFrustum.Intersects(instance.Sphere))) continue; drawCall.Deformable.Segment = (float)segment; @@ -431,11 +435,6 @@ void SplineModel::Draw(RenderContext& renderContext) } } -void SplineModel::DrawGeneric(RenderContext& renderContext) -{ - Draw(renderContext); -} - bool SplineModel::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) { return false; @@ -474,6 +473,13 @@ void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DESERIALIZE(DrawModes); Entries.DeserializeIfExists(stream, "Buffer", modifier); + + // [Deprecated on 07.02.2022, expires on 07.02.2024] + if (modifier->EngineBuild <= 6330) + DrawModes |= DrawPass::GlobalSDF; + // [Deprecated on 27.04.2022, expires on 27.04.2024] + if (modifier->EngineBuild <= 6331) + DrawModes |= DrawPass::GlobalSurfaceAtlas; } void SplineModel::OnTransformChanged() diff --git a/Source/Engine/Level/Actors/SplineModel.h b/Source/Engine/Level/Actors/SplineModel.h index 94bbb28c0..b21edbb3a 100644 --- a/Source/Engine/Level/Actors/SplineModel.h +++ b/Source/Engine/Level/Actors/SplineModel.h @@ -115,7 +115,6 @@ public: // [ModelInstanceActor] bool HasContentLoaded() const override; void Draw(RenderContext& renderContext) override; - void DrawGeneric(RenderContext& renderContext) override; bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; diff --git a/Source/Engine/Level/Actors/SpotLight.cpp b/Source/Engine/Level/Actors/SpotLight.cpp index 1cfcda89f..234786e3c 100644 --- a/Source/Engine/Level/Actors/SpotLight.cpp +++ b/Source/Engine/Level/Actors/SpotLight.cpp @@ -114,12 +114,12 @@ void SpotLight::UpdateBounds() BoundingBox::FromSphere(_sphere, _box); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateCommon(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void SpotLight::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddCommon(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -133,7 +133,7 @@ void SpotLight::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommon(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base LightWithShadow::OnDisable(); @@ -154,6 +154,7 @@ void SpotLight::Draw(RenderContext& renderContext) const float radius = GetScaledRadius(); const float outerConeAngle = GetOuterConeAngle(); if ((renderContext.View.Flags & ViewFlags::SpotLights) != 0 + && renderContext.View.Pass & DrawPass::GBuffer && brightness > ZeroTolerance && radius > ZeroTolerance && outerConeAngle > ZeroTolerance diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index fe3c7f9be..cdfd2a2e1 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -9,6 +9,8 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Level/Prefabs/PrefabManager.h" #include "Engine/Level/Scene/Scene.h" +#include "Engine/Renderer/GlobalSignDistanceFieldPass.h" +#include "Engine/Renderer/GI/GlobalSurfaceAtlasPass.h" #include "Engine/Utilities/Encryption.h" #if USE_EDITOR #include "Editor/Editor.h" @@ -162,20 +164,46 @@ void StaticModel::RemoveVertexColors() void StaticModel::OnModelChanged() { + if (_residencyChangedModel) + { + _residencyChangedModel = nullptr; + Model->ResidencyChanged.Unbind(this); + } RemoveVertexColors(); Entries.Release(); - if (Model && !Model->IsLoaded()) - { UpdateBounds(); - } + else if (!Model && _sceneRenderingKey != -1) + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); } void StaticModel::OnModelLoaded() { Entries.SetupIfInvalid(Model); - UpdateBounds(); + if (_sceneRenderingKey == -1 && _scene && _isActiveInHierarchy && _isEnabled && !_residencyChangedModel) + { + // Register for rendering but once the model has any LOD loaded + if (Model->GetLoadedLODs() == 0) + { + _residencyChangedModel = Model; + _residencyChangedModel->ResidencyChanged.Bind(this); + } + else + { + GetSceneRendering()->AddActor(this, _sceneRenderingKey); + } + } +} + +void StaticModel::OnModelResidencyChanged() +{ + if (_sceneRenderingKey == -1 && _scene && Model && Model->GetLoadedLODs() > 0 && _residencyChangedModel) + { + GetSceneRendering()->AddActor(this, _sceneRenderingKey); + _residencyChangedModel->ResidencyChanged.Unbind(this); + _residencyChangedModel = nullptr; + } } void StaticModel::UpdateBounds() @@ -199,7 +227,7 @@ void StaticModel::UpdateBounds() } BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } bool StaticModel::HasContentLoaded() const @@ -209,72 +237,73 @@ bool StaticModel::HasContentLoaded() const void StaticModel::Draw(RenderContext& renderContext) { + const DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass); + if (!Model || !Model->IsLoaded() || !Model->CanBeRendered() || drawModes == DrawPass::None) + return; + if (renderContext.View.Pass == DrawPass::GlobalSDF) + { + GlobalSignDistanceFieldPass::Instance()->RasterizeModelSDF(this, Model->SDF, _world, _box); + return; + } + if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) + { + GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, this, _sphere, _world, Model->LODs.Last().GetBox()); + return; + } GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world); - const DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass); - if (Model && Model->IsLoaded() && drawModes != DrawPass::None) + // Flush vertex colors if need to + if (_vertexColorsDirty) { - // Flush vertex colors if need to - if (_vertexColorsDirty) + for (int32 lodIndex = 0; lodIndex < _vertexColorsCount; lodIndex++) { - for (int32 lodIndex = 0; lodIndex < _vertexColorsCount; lodIndex++) + auto& vertexColorsData = _vertexColorsData[lodIndex]; + auto& vertexColorsBuffer = _vertexColorsBuffer[lodIndex]; + if (vertexColorsData.HasItems()) { - auto& vertexColorsData = _vertexColorsData[lodIndex]; - auto& vertexColorsBuffer = _vertexColorsBuffer[lodIndex]; - if (vertexColorsData.HasItems()) + const uint32 size = vertexColorsData.Count() * sizeof(Color32); + if (!vertexColorsBuffer) + vertexColorsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("VertexColors")); + if (vertexColorsBuffer->GetSize() != size) { - const uint32 size = vertexColorsData.Count() * sizeof(Color32); - if (!vertexColorsBuffer) - vertexColorsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("VertexColors")); - if (vertexColorsBuffer->GetSize() != size) - { - if (vertexColorsBuffer->Init(GPUBufferDescription::Vertex(sizeof(Color32), vertexColorsData.Count()))) - return; - } - GPUDevice::Instance->GetMainContext()->UpdateBuffer(vertexColorsBuffer, vertexColorsData.Get(), size); - } - else - { - SAFE_DELETE_GPU_RESOURCE(vertexColorsBuffer); + if (vertexColorsBuffer->Init(GPUBufferDescription::Vertex(sizeof(Color32), vertexColorsData.Count()))) + return; } + GPUDevice::Instance->GetMainContext()->UpdateBuffer(vertexColorsBuffer, vertexColorsData.Get(), size); + } + else + { + SAFE_DELETE_GPU_RESOURCE(vertexColorsBuffer); } - _vertexColorsDirty = false; } + _vertexColorsDirty = false; + } #if USE_EDITOR - // Disable motion blur effects in editor without play mode enabled to hide minor artifacts on objects moving - if (!Editor::IsPlayMode) - _drawState.PrevWorld = _world; + // Disable motion blur effects in editor without play mode enabled to hide minor artifacts on objects moving + if (!Editor::IsPlayMode) + _drawState.PrevWorld = _world; #endif - Mesh::DrawInfo draw; - draw.Buffer = &Entries; - draw.World = &_world; - draw.DrawState = &_drawState; - draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); - draw.LightmapUVs = &Lightmap.UVsArea; - draw.Flags = _staticFlags; - draw.DrawModes = drawModes; - draw.Bounds = _sphere; - draw.PerInstanceRandom = GetPerInstanceRandom(); - draw.LODBias = _lodBias; - draw.ForcedLOD = _forcedLod; - draw.VertexColors = _vertexColorsCount ? _vertexColorsBuffer : nullptr; + Mesh::DrawInfo draw; + draw.Buffer = &Entries; + draw.World = &_world; + draw.DrawState = &_drawState; + draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); + draw.LightmapUVs = &Lightmap.UVsArea; + draw.Flags = _staticFlags; + draw.DrawModes = drawModes; + draw.Bounds = _sphere; + draw.PerInstanceRandom = GetPerInstanceRandom(); + draw.LODBias = _lodBias; + draw.ForcedLOD = _forcedLod; + draw.VertexColors = _vertexColorsCount ? _vertexColorsBuffer : nullptr; - Model->Draw(renderContext, draw); - } + Model->Draw(renderContext, draw); GEOMETRY_DRAW_STATE_EVENT_END(_drawState, _world); } -void StaticModel::DrawGeneric(RenderContext& renderContext) -{ - if (renderContext.View.RenderLayersMask.Mask & GetLayerMask() && renderContext.View.CullingFrustum.Intersects(_box)) - { - Draw(renderContext); - } -} - bool StaticModel::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) { bool result = false; @@ -417,6 +446,12 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DrawModes = DrawPass::Depth; } } + // [Deprecated on 07.02.2022, expires on 07.02.2024] + if (modifier->EngineBuild <= 6330) + DrawModes |= DrawPass::GlobalSDF; + // [Deprecated on 27.04.2022, expires on 27.04.2024] + if (modifier->EngineBuild <= 6331) + DrawModes |= DrawPass::GlobalSurfaceAtlas; { const auto member = stream.FindMember("RenderPasses"); @@ -488,3 +523,30 @@ void StaticModel::OnTransformChanged() _transform.GetWorld(_world); UpdateBounds(); } + +void StaticModel::OnEnable() +{ + if (_scene && _sceneRenderingKey == -1 && !_residencyChangedModel && Model && Model->IsLoaded() && Model->GetLoadedLODs() != 0) + { + GetSceneRendering()->AddActor(this, _sceneRenderingKey); + } + + // Skip ModelInstanceActor (add to SceneRendering manually) + Actor::OnEnable(); +} + +void StaticModel::OnDisable() +{ + // Skip ModelInstanceActor (add to SceneRendering manually) + Actor::OnDisable(); + + if (_sceneRenderingKey != -1) + { + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); + } + if (_residencyChangedModel) + { + _residencyChangedModel->ResidencyChanged.Unbind(this); + _residencyChangedModel = nullptr; + } +} diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h index d51e50aee..db0ece1a6 100644 --- a/Source/Engine/Level/Actors/StaticModel.h +++ b/Source/Engine/Level/Actors/StaticModel.h @@ -25,6 +25,7 @@ private: byte _vertexColorsCount; Array _vertexColorsData[MODEL_MAX_LODS]; GPUBuffer* _vertexColorsBuffer[MODEL_MAX_LODS]; + Model* _residencyChangedModel = nullptr; public: @@ -182,6 +183,7 @@ private: void OnModelChanged(); void OnModelLoaded(); + void OnModelResidencyChanged(); void UpdateBounds(); public: @@ -189,7 +191,6 @@ public: // [ModelInstanceActor] bool HasContentLoaded() const override; void Draw(RenderContext& renderContext) override; - void DrawGeneric(RenderContext& renderContext) override; bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; @@ -200,4 +201,6 @@ protected: // [ModelInstanceActor] void OnTransformChanged() override; + void OnEnable() override; + void OnDisable() override; }; diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 39e358faf..5ff5d8c89 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -378,9 +378,10 @@ void Level::DrawActors(RenderContext& renderContext) //ScopeLock lock(ScenesLock); - for (int32 i = 0; i < Scenes.Count(); i++) + for (Scene* scene : Scenes) { - Scenes[i]->Rendering.Draw(renderContext); + if (scene->IsActiveInHierarchy()) + scene->Rendering.Draw(renderContext); } } @@ -390,9 +391,10 @@ void Level::CollectPostFxVolumes(RenderContext& renderContext) //ScopeLock lock(ScenesLock); - for (int32 i = 0; i < Scenes.Count(); i++) + for (Scene* scene : Scenes) { - Scenes[i]->Rendering.CollectPostFxVolumes(renderContext); + if (scene->IsActiveInHierarchy()) + scene->Rendering.CollectPostFxVolumes(renderContext); } } diff --git a/Source/Engine/Level/Scene/Scene.cpp b/Source/Engine/Level/Scene/Scene.cpp index 166373e27..6c25c2f89 100644 --- a/Source/Engine/Level/Scene/Scene.cpp +++ b/Source/Engine/Level/Scene/Scene.cpp @@ -60,7 +60,6 @@ NavMeshBoundsVolume* SceneNavigation::FindNavigationBoundsOverlap(const Bounding Scene::Scene(const SpawnParams& params) : Actor(params) - , Rendering(this) , LightmapsData(this) , CSGData(this) { diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index 252793eae..affd8e576 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -1,369 +1,66 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. +#define SCENE_RENDERING_USE_PROFILER 0 + #include "SceneRendering.h" -#include "Scene.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderView.h" - -#define SCENE_RENDERING_USE_PROFILER 0 -#define SCENE_RENDERING_USE_SIMD 0 - +#include "Engine/Renderer/RenderList.h" +#include "Engine/Threading/Threading.h" #if SCENE_RENDERING_USE_PROFILER #include "Engine/Profiler/ProfilerCPU.h" #endif -#if SCENE_RENDERING_USE_SIMD - -#include "Engine/Core/SIMD.h" - -ALIGN_BEGIN(16) struct CullDataSIMD +ISceneRenderingListener::~ISceneRenderingListener() { - float xs[8]; - float ys[8]; - float zs[8]; - float ds[8]; -} ALIGN_END(16); - -#endif - -int32 SceneRendering::DrawEntries::Add(Actor* obj) -{ - int32 key = 0; - for (; key < List.Count(); key++) + for (SceneRendering* scene : _scenes) { - if (List[key].Actor == nullptr) - break; + scene->_listeners.Remove(this); } - if (key == List.Count()) - List.AddOne(); - auto& e = List[key]; - e.Actor = obj; - e.LayerMask = obj->GetLayerMask(); - e.Bounds = obj->GetSphere(); - return key; } -void SceneRendering::DrawEntries::Update(Actor* obj, int32 key) +void ISceneRenderingListener::ListenSceneRendering(SceneRendering* scene) { - if (List.IsEmpty()) - return; - auto& e = List[key]; - ASSERT_LOW_LAYER(obj == e.Actor); - e.LayerMask = obj->GetLayerMask(); - e.Bounds = obj->GetSphere(); -} - -void SceneRendering::DrawEntries::Remove(Actor* obj, int32 key) -{ - if (List.IsEmpty()) - return; - auto& e = List[key]; - ASSERT_LOW_LAYER(obj == e.Actor); - e.Actor = nullptr; - e.LayerMask = 0; -} - -void SceneRendering::DrawEntries::Clear() -{ - List.Clear(); -} - -void SceneRendering::DrawEntries::CullAndDraw(RenderContext& renderContext) -{ - auto& view = renderContext.View; - const BoundingFrustum frustum = view.CullingFrustum; -#if SCENE_RENDERING_USE_SIMD - CullDataSIMD cullData; + if (!_scenes.Contains(scene)) { - // Near - auto plane = view.Frustum.GetNear(); - cullData.xs[0] = plane.Normal.X; - cullData.ys[0] = plane.Normal.Y; - cullData.zs[0] = plane.Normal.Z; - cullData.ds[0] = plane.D; - - // Far - plane = view.Frustum.GetFar(); - cullData.xs[1] = plane.Normal.X; - cullData.ys[1] = plane.Normal.Y; - cullData.zs[1] = plane.Normal.Z; - cullData.ds[1] = plane.D; - - // Left - plane = view.Frustum.GetLeft(); - cullData.xs[2] = plane.Normal.X; - cullData.ys[2] = plane.Normal.Y; - cullData.zs[2] = plane.Normal.Z; - cullData.ds[2] = plane.D; - - // Right - plane = view.Frustum.GetRight(); - cullData.xs[3] = plane.Normal.X; - cullData.ys[3] = plane.Normal.Y; - cullData.zs[3] = plane.Normal.Z; - cullData.ds[3] = plane.D; - - // Top - plane = view.Frustum.GetTop(); - cullData.xs[4] = plane.Normal.X; - cullData.ys[4] = plane.Normal.Y; - cullData.zs[4] = plane.Normal.Z; - cullData.ds[4] = plane.D; - - // Bottom - plane = view.Frustum.GetBottom(); - cullData.xs[5] = plane.Normal.X; - cullData.ys[5] = plane.Normal.Y; - cullData.zs[5] = plane.Normal.Z; - cullData.ds[5] = plane.D; - - // Extra 0 - cullData.xs[6] = 0; - cullData.ys[6] = 0; - cullData.zs[6] = 0; - cullData.ds[6] = 0; - - // Extra 1 - cullData.xs[7] = 0; - cullData.ys[7] = 0; - cullData.zs[7] = 0; - cullData.ds[7] = 0; + _scenes.Add(scene); + scene->_listeners.Add(this); } - - SimdVector4 px = SIMD::Load(cullData.xs); - SimdVector4 py = SIMD::Load(cullData.ys); - SimdVector4 pz = SIMD::Load(cullData.zs); - SimdVector4 pd = SIMD::Load(cullData.ds); - SimdVector4 px2 = SIMD::Load(&cullData.xs[4]); - SimdVector4 py2 = SIMD::Load(&cullData.ys[4]); - SimdVector4 pz2 = SIMD::Load(&cullData.zs[4]); - SimdVector4 pd2 = SIMD::Load(&cullData.ds[4]); - - for (int32 i = 0; i < List.Count(); i++) - { - auto e = List[i]; - - SimdVector4 cx = SIMD::Splat(e.Bounds.Center.X); - SimdVector4 cy = SIMD::Splat(e.Bounds.Center.Y); - SimdVector4 cz = SIMD::Splat(e.Bounds.Center.Z); - SimdVector4 r = SIMD::Splat(-e.Bounds.Radius); - - SimdVector4 t = SIMD::Mul(cx, px); - t = SIMD::Add(t, SIMD::Mul(cy, py)); - t = SIMD::Add(t, SIMD::Mul(cz, pz)); - t = SIMD::Add(t, pd); - t = SIMD::Sub(t, r); - if (SIMD::MoveMask(t)) - continue; - - t = SIMD::Mul(cx, px2); - t = SIMD::Add(t, SIMD::Mul(cy, py2)); - t = SIMD::Add(t, SIMD::Mul(cz, pz2)); - t = SIMD::Add(t, pd2); - t = SIMD::Sub(t, r); - if (SIMD::MoveMask(t)) - continue; - - if (view.RenderLayersMask.Mask & e.LayerMask) - { -#if SCENE_RENDERING_USE_PROFILER - PROFILE_CPU(); -#if TRACY_ENABLE - ___tracy_scoped_zone.Name(*e.Actor->GetName(), e.Actor->GetName().Length()); -#endif -#endif - e.Actor->Draw(renderContext); - } - } -#else - for (int32 i = 0; i < List.Count(); i++) - { - auto e = List[i]; - if (view.RenderLayersMask.Mask & e.LayerMask && frustum.Intersects(e.Bounds)) - { -#if SCENE_RENDERING_USE_PROFILER - PROFILE_CPU(); -#if TRACY_ENABLE - ___tracy_scoped_zone.Name(*e.Actor->GetName(), e.Actor->GetName().Length()); -#endif -#endif - e.Actor->Draw(renderContext); - } - } -#endif -} - -void SceneRendering::DrawEntries::CullAndDrawOffline(RenderContext& renderContext) -{ - auto& view = renderContext.View; - const BoundingFrustum frustum = view.CullingFrustum; -#if SCENE_RENDERING_USE_SIMD - CullDataSIMD cullData; - { - // Near - auto plane = view.Frustum.GetNear(); - cullData.xs[0] = plane.Normal.X; - cullData.ys[0] = plane.Normal.Y; - cullData.zs[0] = plane.Normal.Z; - cullData.ds[0] = plane.D; - - // Far - plane = view.Frustum.GetFar(); - cullData.xs[1] = plane.Normal.X; - cullData.ys[1] = plane.Normal.Y; - cullData.zs[1] = plane.Normal.Z; - cullData.ds[1] = plane.D; - - // Left - plane = view.Frustum.GetLeft(); - cullData.xs[2] = plane.Normal.X; - cullData.ys[2] = plane.Normal.Y; - cullData.zs[2] = plane.Normal.Z; - cullData.ds[2] = plane.D; - - // Right - plane = view.Frustum.GetRight(); - cullData.xs[3] = plane.Normal.X; - cullData.ys[3] = plane.Normal.Y; - cullData.zs[3] = plane.Normal.Z; - cullData.ds[3] = plane.D; - - // Top - plane = view.Frustum.GetTop(); - cullData.xs[4] = plane.Normal.X; - cullData.ys[4] = plane.Normal.Y; - cullData.zs[4] = plane.Normal.Z; - cullData.ds[4] = plane.D; - - // Bottom - plane = view.Frustum.GetBottom(); - cullData.xs[5] = plane.Normal.X; - cullData.ys[5] = plane.Normal.Y; - cullData.zs[5] = plane.Normal.Z; - cullData.ds[5] = plane.D; - - // Extra 0 - cullData.xs[6] = 0; - cullData.ys[6] = 0; - cullData.zs[6] = 0; - cullData.ds[6] = 0; - - // Extra 1 - cullData.xs[7] = 0; - cullData.ys[7] = 0; - cullData.zs[7] = 0; - cullData.ds[7] = 0; - } - - SimdVector4 px = SIMD::Load(cullData.xs); - SimdVector4 py = SIMD::Load(cullData.ys); - SimdVector4 pz = SIMD::Load(cullData.zs); - SimdVector4 pd = SIMD::Load(cullData.ds); - SimdVector4 px2 = SIMD::Load(&cullData.xs[4]); - SimdVector4 py2 = SIMD::Load(&cullData.ys[4]); - SimdVector4 pz2 = SIMD::Load(&cullData.zs[4]); - SimdVector4 pd2 = SIMD::Load(&cullData.ds[4]); - - for (int32 i = 0; i < List.Count(); i++) - { - auto e = List[i]; - - SimdVector4 cx = SIMD::Splat(e.Bounds.Center.X); - SimdVector4 cy = SIMD::Splat(e.Bounds.Center.Y); - SimdVector4 cz = SIMD::Splat(e.Bounds.Center.Z); - SimdVector4 r = SIMD::Splat(-e.Bounds.Radius); - - SimdVector4 t = SIMD::Mul(cx, px); - t = SIMD::Add(t, SIMD::Mul(cy, py)); - t = SIMD::Add(t, SIMD::Mul(cz, pz)); - t = SIMD::Add(t, pd); - t = SIMD::Sub(t, r); - if (SIMD::MoveMask(t)) - continue; - - t = SIMD::Mul(cx, px2); - t = SIMD::Add(t, SIMD::Mul(cy, py2)); - t = SIMD::Add(t, SIMD::Mul(cz, pz2)); - t = SIMD::Add(t, pd2); - t = SIMD::Sub(t, r); - if (SIMD::MoveMask(t)) - continue; - - if (view.RenderLayersMask.Mask & e.LayerMask && e.Actor->GetStaticFlags() & renderContext.View.StaticFlagsMask) - { -#if SCENE_RENDERING_USE_PROFILER - PROFILE_CPU(); -#if TRACY_ENABLE - ___tracy_scoped_zone.Name(*e.Actor->GetName(), e.Actor->GetName().Length()); -#endif -#endif - e.Actor->Draw(renderContext); - } - } -#else - for (int32 i = 0; i < List.Count(); i++) - { - auto e = List[i]; - if (view.RenderLayersMask.Mask & e.LayerMask && frustum.Intersects(e.Bounds) && e.Actor->GetStaticFlags() & view.StaticFlagsMask) - { -#if SCENE_RENDERING_USE_PROFILER - PROFILE_CPU(); -#if TRACY_ENABLE - ___tracy_scoped_zone.Name(*e.Actor->GetName(), e.Actor->GetName().Length()); -#endif -#endif - e.Actor->Draw(renderContext); - } - } -#endif -} - -SceneRendering::SceneRendering(::Scene* scene) - : Scene(scene) -{ } void SceneRendering::Draw(RenderContext& renderContext) { - // Skip if disabled - if (!Scene->GetIsActive()) - return; + ScopeLock lock(Locker); auto& view = renderContext.View; + const BoundingFrustum frustum = view.CullingFrustum; + renderContext.List->Scenes.Add(this); // Draw all visual components if (view.IsOfflinePass) { - Geometry.CullAndDrawOffline(renderContext); - if (view.Pass & DrawPass::GBuffer) + for (int32 i = 0; i < Actors.Count(); i++) { - Common.CullAndDrawOffline(renderContext); - for (int32 i = 0; i < CommonNoCulling.Count(); i++) + auto& e = Actors[i]; + if (view.RenderLayersMask.Mask & e.LayerMask && (e.NoCulling || frustum.Intersects(e.Bounds)) && e.Actor->GetStaticFlags() & view.StaticFlagsMask) { - auto actor = CommonNoCulling[i]; - if (actor->GetStaticFlags() & view.StaticFlagsMask && view.RenderLayersMask.HasLayer(actor->GetLayer())) - actor->Draw(renderContext); +#if SCENE_RENDERING_USE_PROFILER + PROFILE_CPU_ACTOR(e.Actor); +#endif + e.Actor->Draw(renderContext); } } } else { - Geometry.CullAndDraw(renderContext); - if (view.Pass & DrawPass::GBuffer) + for (int32 i = 0; i < Actors.Count(); i++) { - Common.CullAndDraw(renderContext); - for (int32 i = 0; i < CommonNoCulling.Count(); i++) + auto& e = Actors[i]; + if (view.RenderLayersMask.Mask & e.LayerMask && (e.NoCulling || frustum.Intersects(e.Bounds))) { - auto actor = CommonNoCulling[i]; - if (view.RenderLayersMask.HasLayer(actor->GetLayer())) - { #if SCENE_RENDERING_USE_PROFILER - PROFILE_CPU(); -#if TRACY_ENABLE - ___tracy_scoped_zone.Name(*actor->GetName(), actor->GetName().Length()); + PROFILE_CPU_ACTOR(e.Actor); #endif -#endif - actor->Draw(renderContext); - } + e.Actor->Draw(renderContext); } } } @@ -395,10 +92,67 @@ void SceneRendering::CollectPostFxVolumes(RenderContext& renderContext) void SceneRendering::Clear() { - Geometry.Clear(); - Common.Clear(); - CommonNoCulling.Clear(); + ScopeLock lock(Locker); + for (auto* listener : _listeners) + { + listener->OnSceneRenderingClear(this); + listener->_scenes.Remove(this); + } + _listeners.Clear(); + Actors.Clear(); #if USE_EDITOR PhysicsDebug.Clear(); #endif } + +void SceneRendering::AddActor(Actor* a, int32& key) +{ + ScopeLock lock(Locker); + if (key == -1) + { + // TODO: track removedCount and skip searching for free entry if there is none + key = 0; + for (; key < Actors.Count(); key++) + { + if (Actors[key].Actor == nullptr) + break; + } + if (key == Actors.Count()) + Actors.AddOne(); + auto& e = Actors[key]; + e.Actor = a; + e.LayerMask = a->GetLayerMask(); + e.Bounds = a->GetSphere(); + e.NoCulling = a->_drawNoCulling; + for (auto* listener : _listeners) + listener->OnSceneRenderingAddActor(a); + } +} + +void SceneRendering::UpdateActor(Actor* a, int32 key) +{ + ScopeLock lock(Locker); + if (Actors.IsEmpty()) + return; + auto& e = Actors[key]; + ASSERT_LOW_LAYER(a == e.Actor); + for (auto* listener : _listeners) + listener->OnSceneRenderingUpdateActor(a, e.Bounds); + e.LayerMask = a->GetLayerMask(); + e.Bounds = a->GetSphere(); +} + +void SceneRendering::RemoveActor(Actor* a, int32& key) +{ + ScopeLock lock(Locker); + if (Actors.HasItems()) + { + auto& e = Actors[key]; + ASSERT_LOW_LAYER(a == e.Actor); + for (auto* listener : _listeners) + listener->OnSceneRenderingRemoveActor(a); + e.Actor = nullptr; + e.LayerMask = 0; + } + key = -1; +} diff --git a/Source/Engine/Level/Scene/SceneRendering.h b/Source/Engine/Level/Scene/SceneRendering.h index 0f6c32c73..9faa8d6f5 100644 --- a/Source/Engine/Level/Scene/SceneRendering.h +++ b/Source/Engine/Level/Scene/SceneRendering.h @@ -6,9 +6,10 @@ #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Math/BoundingSphere.h" #include "Engine/Level/Actor.h" -#include "Engine/Level/Types.h" +#include "Engine/Platform/CriticalSection.h" class SceneRenderTask; +class SceneRendering; struct PostProcessSettings; struct RenderContext; struct RenderView; @@ -19,7 +20,6 @@ struct RenderView; class FLAXENGINE_API IPostFxSettingsProvider { public: - /// /// Collects the settings for rendering of the specified task. /// @@ -34,51 +34,61 @@ public: virtual void Blend(PostProcessSettings& other, float weight) = 0; }; +/// +/// Interface for objects to plug into Scene Rendering and listen for its evens such as static actors changes which are relevant for drawing cache. +/// +/// +class FLAXENGINE_API ISceneRenderingListener +{ + friend SceneRendering; +private: + Array> _scenes; +public: + ~ISceneRenderingListener(); + + // Starts listening to the scene rendering events. + void ListenSceneRendering(SceneRendering* scene); + + // Events called by Scene Rendering + virtual void OnSceneRenderingAddActor(Actor* a) = 0; + virtual void OnSceneRenderingUpdateActor(Actor* a, const BoundingSphere& prevBounds) = 0; + virtual void OnSceneRenderingRemoveActor(Actor* a) = 0; + virtual void OnSceneRenderingClear(SceneRendering* scene) = 0; +}; + /// /// Scene rendering helper subsystem that boosts the level rendering by providing efficient objects cache and culling implementation. /// class FLAXENGINE_API SceneRendering { - friend Scene; #if USE_EDITOR typedef Function PhysicsDebugCallback; friend class ViewportIconsRendererService; #endif - struct DrawEntry +public: + struct DrawActor { Actor* Actor; uint32 LayerMask; + int8 NoCulling : 1; BoundingSphere Bounds; }; - struct DrawEntries - { - Array List; - - int32 Add(Actor* obj); - void Update(Actor* obj, int32 key); - void Remove(Actor* obj, int32 key); - void Clear(); - void CullAndDraw(RenderContext& renderContext); - void CullAndDrawOffline(RenderContext& renderContext); - }; + Array Actors; + Array PostFxProviders; + CriticalSection Locker; private: - - Scene* Scene; - DrawEntries Geometry; - DrawEntries Common; - Array CommonNoCulling; - Array PostFxProviders; #if USE_EDITOR Array PhysicsDebug; Array ViewportIcons; #endif - explicit SceneRendering(::Scene* scene); + // Listener - some rendering systems cache state of the scene (eg. in RenderBuffers::CustomBuffer), this extensions allows those systems to invalidate cache and handle scene changes + friend ISceneRenderingListener; + Array> _listeners; public: - /// /// Draws the scene. Performs the optimized actors culling and draw calls submission for the current render pass (defined by the render view). /// @@ -97,48 +107,9 @@ public: void Clear(); public: - - FORCE_INLINE int32 AddGeometry(Actor* obj) - { - return Geometry.Add(obj); - } - - FORCE_INLINE void UpdateGeometry(Actor* obj, int32 key) - { - Geometry.Update(obj, key); - } - - FORCE_INLINE void RemoveGeometry(Actor* obj, int32& key) - { - Geometry.Remove(obj, key); - key = -1; - } - - FORCE_INLINE int32 AddCommon(Actor* obj) - { - return Common.Add(obj); - } - - FORCE_INLINE void UpdateCommon(Actor* obj, int32 key) - { - Common.Update(obj, key); - } - - FORCE_INLINE void RemoveCommon(Actor* obj, int32& key) - { - Common.Remove(obj, key); - key = -1; - } - - FORCE_INLINE void AddCommonNoCulling(Actor* obj) - { - CommonNoCulling.Add(obj); - } - - FORCE_INLINE void RemoveCommonNoCulling(Actor* obj) - { - CommonNoCulling.Remove(obj); - } + void AddActor(Actor* a, int32& key); + void UpdateActor(Actor* a, int32 key); + void RemoveActor(Actor* a, int32& key); FORCE_INLINE void AddPostFxProvider(IPostFxSettingsProvider* obj) { @@ -151,7 +122,6 @@ public: } #if USE_EDITOR - template FORCE_INLINE void AddPhysicsDebug(T* obj) { @@ -177,6 +147,5 @@ public: { ViewportIcons.Remove(obj); } - #endif }; diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp index 39b1f8b9b..986072fa3 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp @@ -121,14 +121,14 @@ int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) // Calculate particles to spawn during this frame switch (node->TypeID) { - // Constant Spawn Rate + // Constant Spawn Rate case 100: { const float rate = Math::Max((float)TryGetValue(node->GetBox(0), node->Values[2]), 0.0f); spawnCount += rate * context.DeltaTime; break; } - // Single Burst + // Single Burst case 101: { const bool isFirstUpdate = (context.Data->Time - context.DeltaTime) <= 0.0f; @@ -139,7 +139,7 @@ int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) } break; } - // Periodic + // Periodic case 102: { float& nextSpawnTime = data.NextSpawnTime; @@ -152,7 +152,7 @@ int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) } break; } - // Periodic Burst (range) + // Periodic Burst (range) case 103: { float& nextSpawnTime = data.NextSpawnTime; @@ -186,7 +186,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* switch (node->TypeID) { - // Orient Sprite + // Orient Sprite case 201: case 303: { @@ -229,7 +229,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* } break; } - // Orient Model + // Orient Model case 213: case 309: { @@ -246,7 +246,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* } break; } - // Update Age + // Update Age case 300: { PARTICLE_EMITTER_MODULE("Update Age"); @@ -259,7 +259,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* } break; } - // Gravity/Force + // Gravity/Force case 301: case 304: { @@ -288,7 +288,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* } break; } - // Conform to Sphere + // Conform to Sphere case 305: { PARTICLE_EMITTER_MODULE("Conform to Sphere"); @@ -351,7 +351,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Kill (sphere) + // Kill (sphere) case 306: { PARTICLE_EMITTER_MODULE("Kill"); @@ -400,7 +400,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Kill (box) + // Kill (box) case 307: { PARTICLE_EMITTER_MODULE("Kill"); @@ -454,7 +454,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Kill (custom) + // Kill (custom) case 308: { PARTICLE_EMITTER_MODULE("Kill (custom)"); @@ -492,7 +492,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Linear Drag + // Linear Drag case 310: { PARTICLE_EMITTER_MODULE("Linear Drag"); @@ -538,7 +538,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Turbulence + // Turbulence case 311: { PARTICLE_EMITTER_MODULE("Turbulence"); @@ -598,7 +598,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Set Attribute + // Set Attribute case 200: case 302: { @@ -630,7 +630,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* } break; } - // Set Position/Lifetime/Age/.. + // Set Position/Lifetime/Age/.. case 250: case 251: case 252: @@ -688,7 +688,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* } break; } - // Position (sphere surface) + // Position (sphere surface) case 202: { PARTICLE_EMITTER_MODULE("Position"); @@ -734,7 +734,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (plane) + // Position (plane) case 203: { PARTICLE_EMITTER_MODULE("Position"); @@ -773,7 +773,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (circle) + // Position (circle) case 204: { PARTICLE_EMITTER_MODULE("Position"); @@ -817,7 +817,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (disc) + // Position (disc) case 205: { PARTICLE_EMITTER_MODULE("Position"); @@ -861,7 +861,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (box surface) + // Position (box surface) case 206: { PARTICLE_EMITTER_MODULE("Position"); @@ -912,7 +912,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (box volume) + // Position (box volume) case 207: { PARTICLE_EMITTER_MODULE("Position"); @@ -951,7 +951,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (cylinder) + // Position (cylinder) case 208: { PARTICLE_EMITTER_MODULE("Position"); @@ -997,7 +997,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (line) + // Position (line) case 209: { PARTICLE_EMITTER_MODULE("Position"); @@ -1036,7 +1036,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (torus) + // Position (torus) case 210: { PARTICLE_EMITTER_MODULE("Position"); @@ -1101,7 +1101,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (sphere volume) + // Position (sphere volume) case 211: { PARTICLE_EMITTER_MODULE("Position"); @@ -1147,13 +1147,13 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (depth) + // Position (depth) case 212: { // Not supported break; } - // Position (spiral) + // Position (spiral) case 214: { PARTICLE_EMITTER_MODULE("Position"); @@ -1204,8 +1204,14 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } + // Position (Global SDF) + case 215: + { + // Not supported + break; + } - // Helper macros for collision modules to share the code + // Helper macros for collision modules to share the code #define COLLISION_BEGIN() \ PARTICLE_EMITTER_MODULE("Collision"); \ auto& positionAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; \ @@ -1245,7 +1251,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* velocityPtr += stride; \ agePtr += stride - // Collision (plane) + // Collision (plane) case 330: { COLLISION_BEGIN(); @@ -1287,7 +1293,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Collision (sphere) + // Collision (sphere) case 331: { COLLISION_BEGIN(); @@ -1332,7 +1338,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Collision (box) + // Collision (box) case 332: { COLLISION_BEGIN(); @@ -1392,7 +1398,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Collision (cylinder) + // Collision (cylinder) case 333: { COLLISION_BEGIN(); @@ -1457,12 +1463,24 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Collision (depth) + // Collision (depth) case 334: { // Not supported break; } + // Conform to Global SDF + case 335: + { + // Not supported + break; + } + // Collision (Global SDF) + case 336: + { + // Not supported + break; + } #undef COLLISION_BEGIN #undef COLLISION_INPUTS_FETCH diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp index f21777389..0e7eec882 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp @@ -15,7 +15,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* nod auto& context = Context.Get(); switch (node->TypeID) { - // Get + // Get case 2: { int32 paramIndex; @@ -96,34 +96,41 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTextures(Box* box, Node* node, { switch (node->TypeID) { - // Scene Texture + // Scene Texture case 6: { // Not supported value = Value::Zero; break; } - // Scene Depth + // Scene Depth case 8: { // Not supported value = Value::Zero; break; } - // Texture + // Texture case 11: { // TODO: support sampling textures in CPU particles value = Value::Zero; break; } - // Load Texture + // Load Texture case 13: { // TODO: support sampling textures in CPU particles value = Value::Zero; break; } + // Sample Global SDF + case 14: + { + // Not supported + value = Value::Zero; + break; + } default: break; } @@ -134,18 +141,18 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTools(Box* box, Node* node, Va auto& context = Context.Get(); switch (node->TypeID) { - // Linearize Depth + // Linearize Depth case 7: { // TODO: support Linearize Depth in CPU particles value = Value::Zero; break; } - // Time + // Time case 8: value = box->ID == 0 ? context.Data->Time : context.DeltaTime; break; - // Transform Position To Screen UV + // Transform Position To Screen UV case 9: { GET_VIEW(); @@ -169,7 +176,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node auto node = (ParticleEmitterGraphCPUNode*)nodeBase; switch (node->TypeID) { - // Particle Attribute + // Particle Attribute case 100: { byte* ptr = ACCESS_PARTICLE_ATTRIBUTE(0); @@ -197,7 +204,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node } break; } - // Particle Attribute (by index) + // Particle Attribute (by index) case 303: { const auto particleIndex = tryGetValue(node->GetBox(1), context.ParticleIndex); @@ -226,61 +233,61 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node } break; } - // Particle Position + // Particle Position case 101: { value = GET_PARTICLE_ATTRIBUTE(0, Vector3); break; } - // Particle Lifetime + // Particle Lifetime case 102: { value = GET_PARTICLE_ATTRIBUTE(0, float); break; } - // Particle Age + // Particle Age case 103: { value = GET_PARTICLE_ATTRIBUTE(0, float); break; } - // Particle Color + // Particle Color case 104: { value = GET_PARTICLE_ATTRIBUTE(0, Vector4); break; } - // Particle Velocity + // Particle Velocity case 105: { value = GET_PARTICLE_ATTRIBUTE(0, Vector3); break; } - // Particle Sprite Size + // Particle Sprite Size case 106: { value = GET_PARTICLE_ATTRIBUTE(0, Vector2); break; } - // Particle Mass + // Particle Mass case 107: { value = GET_PARTICLE_ATTRIBUTE(0, float); break; } - // Particle Rotation + // Particle Rotation case 108: { value = GET_PARTICLE_ATTRIBUTE(0, Vector3); break; } - // Particle Angular Velocity + // Particle Angular Velocity case 109: { value = GET_PARTICLE_ATTRIBUTE(0, Vector3); break; } - // Particle Normalized Age + // Particle Normalized Age case 110: { const float age = GET_PARTICLE_ATTRIBUTE(0, float); @@ -288,55 +295,55 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node value = age / Math::Max(lifetime, ZeroTolerance); break; } - // Particle Radius + // Particle Radius case 111: { value = GET_PARTICLE_ATTRIBUTE(0, float); break; } - // Effect Position + // Effect Position case 200: { value = context.Effect->GetPosition(); break; } - // Effect Rotation + // Effect Rotation case 201: { value = context.Effect->GetOrientation(); break; } - // Effect Scale + // Effect Scale case 202: { value = context.Effect->GetScale(); break; } - // Simulation Mode + // Simulation Mode case 203: { value = box->ID == 0; break; } - // View Position + // View Position case 204: { value = context.ViewTask ? context.ViewTask->View.Position : Vector3::Zero; break; } - // View Direction + // View Direction case 205: { value = context.ViewTask ? context.ViewTask->View.Direction : Vector3::Forward; break; } - // View Far Plane + // View Far Plane case 206: { value = context.ViewTask ? context.ViewTask->View.Far : 0.0f; break; } - // Screen Size + // Screen Size case 207: { const Vector4 size = context.ViewTask ? context.ViewTask->View.ScreenSize : Vector4::Zero; @@ -346,13 +353,13 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node value = Vector2(size.Z, size.W); break; } - // Particle Position (world space) + // Particle Position (world space) case 212: value = GET_PARTICLE_ATTRIBUTE(0, Vector3); if (context.Emitter->SimulationSpace == ParticlesSimulationSpace::Local) value.AsVector3() = context.Effect->GetTransform().LocalToWorld(value.AsVector3()); break; - // Particle Emitter Function + // Particle Emitter Function case 300: { // Load function asset @@ -399,11 +406,11 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node context.GraphStack.Pop(); break; } - // Particle Index + // Particle Index case 301: value = context.ParticleIndex; break; - // Particles Count + // Particles Count case 302: value = (uint32)context.Data->Buffer->CPU.Count; break; @@ -418,7 +425,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupFunction(Box* box, Node* node, auto& context = Context.Get(); switch (node->TypeID) { - // Function Input + // Function Input case 1: { // Find the function call diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp index 1338ee64a..12211de41 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp @@ -102,7 +102,7 @@ void ParticleEmitterGraphCPU::InitializeNode(Node* node) switch (node->Type) { - // Position (spiral) + // Position (spiral) case GRAPH_NODE_MAKE_TYPE(15, 214): node->CustomDataOffset = CustomDataSize; CustomDataSize += sizeof(float); @@ -226,7 +226,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa const auto module = emitter->Graph.RenderModules[moduleIndex]; switch (module->TypeID) { - // Sprite Rendering + // Sprite Rendering case 400: { if (_graph._attrSpriteSize != -1) @@ -246,7 +246,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa } break; } - // Light Rendering + // Light Rendering case 401: { // Prepare graph data @@ -269,7 +269,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa break; } - // Model Rendering + // Model Rendering case 403: { const auto modelAsset = (Model*)module->Assets[0].Get(); @@ -297,7 +297,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa } break; } - // Ribbon Rendering + // Ribbon Rendering case 404: { if (_graph._attrRibbonWidth != -1) @@ -317,7 +317,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa } break; } - // Volumetric Fog Rendering + // Volumetric Fog Rendering case 405: { // Find the maximum radius of the particle diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp index addce771b..c267eea05 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp @@ -10,7 +10,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) auto nodeGpu = (ParticleEmitterGraphGPUNode*)node; switch (node->TypeID) { - // Orient Sprite + // Orient Sprite case 201: case 303: { @@ -28,7 +28,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) } break; } - // Orient Model + // Orient Model case 213: case 309: { @@ -39,14 +39,14 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) } break; } - // Update Age + // Update Age case 300: { auto attribute = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::ReadWrite); _writer.Write(TEXT("\t{0} += DeltaTime;\n"), attribute.Value); break; } - // Gravity/Force + // Gravity/Force case 301: case 304: { @@ -55,7 +55,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) _writer.Write(TEXT("\t{0} += {1} * DeltaTime;\n"), attribute.Value, force.Value); break; } - // Conform to Sphere + // Conform to Sphere case 305: { auto position = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Read); @@ -94,7 +94,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), position.Value, velocity.Value, mass.Value, sphereCenter.Value, sphereRadius.Value, attractionSpeed.Value, attractionForce.Value, stickDistance.Value, stickForce.Value); break; } - // Kill (sphere) + // Kill (sphere) case 306: { UseKill(); @@ -119,7 +119,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), position.Value, sphereCenter.Value, sphereRadius.Value, sign); break; } - // Kill (box) + // Kill (box) case 307: { UseKill(); @@ -149,7 +149,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), position.Value, boxCenter.Value, boxSize.Value, invert); break; } - // Kill (custom) + // Kill (custom) case 308: { UseKill(); @@ -162,7 +162,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), kill.Value); break; } - // Linear Drag + // Linear Drag case 310: { const bool useSpriteSize = node->Values[3].AsBool; @@ -195,7 +195,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) } break; } - // Turbulence + // Turbulence case 311: { auto position = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Read); @@ -225,7 +225,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), position.Value, velocity.Value, mass.Value, fieldPosition.Value, fieldRotation.Value, fieldScale.Value, roughness.Value, intensity.Value, octavesCount.Value); break; } - // Set Attribute + // Set Attribute case 200: case 302: { @@ -235,7 +235,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) SET_ATTRIBUTE(attribute, value.Value); break; } - // Set Position/Lifetime/Age/.. + // Set Position/Lifetime/Age/.. case 250: case 251: case 252: @@ -271,7 +271,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) SET_ATTRIBUTE(attribute, value.Value); break; } - // Position (sphere surface) + // Position (sphere surface) case 202: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -298,7 +298,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, 0, center.Value, radius.Value, arc.Value); break; } - // Position (plane) + // Position (plane) case 203: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -318,7 +318,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, center.Value, size.Value); break; } - // Position (circle) + // Position (circle) case 204: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -343,7 +343,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, 0, center.Value, radius.Value, arc.Value); break; } - // Position (disc) + // Position (disc) case 205: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -368,7 +368,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, 0, center.Value, radius.Value, arc.Value); break; } - // Position (box surface) + // Position (box surface) case 206: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -400,7 +400,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, center.Value, size.Value); break; } - // Position (box volume) + // Position (box volume) case 207: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -420,7 +420,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, center.Value, size.Value); break; } - // Position (cylinder) + // Position (cylinder) case 208: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -447,7 +447,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, center.Value, radius.Value, height.Value, arc.Value); break; } - // Position (line) + // Position (line) case 209: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -467,7 +467,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, start.Value, end.Value); break; } - // Position (torus) + // Position (torus) case 210: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -513,7 +513,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, 0, center.Value, radius.Value, thickness.Value, arc.Value); break; } - // Position (sphere volume) + // Position (sphere volume) case 211: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -540,7 +540,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, 0, center.Value, radius.Value, arc.Value); break; } - // Position (depth) + // Position (depth) case 212: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -574,7 +574,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, uv.Value, depthCullRange.Value, depthOffset.Value, linearDepth.Value, lifetimeAttr.Value); break; } - // Position (spiral) + // Position (spiral) case 214: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -610,8 +610,29 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, velocityAttr.Value, center.Value, rotationSpeed.Value, velocityScale.Value, customDataOffset); break; } + // Position (Global SDF) + case 215: + { + auto position = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::ReadWrite); + auto param = findOrAddGlobalSDF(); + String wsPos = position.Value; + if (IsLocalSimulationSpace()) + wsPos = String::Format(TEXT("mul(float4({0}, 1), WorldMatrix).xyz"), wsPos); + _includes.Add(TEXT("./Flax/GlobalSignDistanceField.hlsl")); + _writer.Write( + TEXT( + " {{\n" + " // Position (Global SDF)\n" + " float3 wsPos = {2};\n" + " float dist;\n" + " float3 dir = -normalize(SampleGlobalSDFGradient({1}, {1}_Tex, wsPos, dist));\n" + " {0} += dir * dist;\n" + " }}\n" + ), position.Value, param.ShaderName, wsPos); + break; + } - // Helper macros for collision modules to share the code + // Helper macros for collision modules to share the code #define COLLISION_BEGIN() \ auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::ReadWrite); \ auto velocityAttr = AccessParticleAttribute(node, nodeGpu->Attributes[1], AccessMode::ReadWrite); \ @@ -641,17 +662,12 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) " {2} += {9};\n" \ " }}\n" - // Collision (plane) + // Collision (plane) case 330: { COLLISION_BEGIN(); - - auto planePositionBox = node->GetBox(5); - auto planeNormalBox = node->GetBox(6); - - const Value planePosition = GetValue(planePositionBox, 8).AsVector3(); - const Value planeNormal = GetValue(planeNormalBox, 9).AsVector3(); - + const Value planePosition = GetValue(node->GetBox(5), 8).AsVector3(); + const Value planeNormal = GetValue(node->GetBox(6), 9).AsVector3(); _writer.Write( TEXT( " {{\n" @@ -674,17 +690,12 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ); break; } - // Collision (sphere) + // Collision (sphere) case 331: { COLLISION_BEGIN(); - - auto spherePositionBox = node->GetBox(5); - auto sphereRadiusBox = node->GetBox(6); - - const Value spherePosition = GetValue(spherePositionBox, 8).AsVector3(); - const Value sphereRadius = GetValue(sphereRadiusBox, 9).AsFloat(); - + const Value spherePosition = GetValue(node->GetBox(5), 8).AsVector3(); + const Value sphereRadius = GetValue(node->GetBox(6), 9).AsFloat(); _writer.Write( TEXT( " {{\n" @@ -710,17 +721,12 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ); break; } - // Collision (box) + // Collision (box) case 332: { COLLISION_BEGIN(); - - auto boxPositionBox = node->GetBox(5); - auto boxSizeBox = node->GetBox(6); - - const Value boxPosition = GetValue(boxPositionBox, 8).AsVector3(); - const Value boxSize = GetValue(boxSizeBox, 9).AsVector3(); - + const Value boxPosition = GetValue(node->GetBox(5), 8).AsVector3(); + const Value boxSize = GetValue(node->GetBox(6), 9).AsVector3(); _writer.Write( TEXT( " {{\n" @@ -761,19 +767,13 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ); break; } - // Collision (cylinder) + // Collision (cylinder) case 333: { COLLISION_BEGIN(); - - auto cylinderPositionBox = node->GetBox(5); - auto cylinderHeightBox = node->GetBox(6); - auto cylinderRadiusBox = node->GetBox(7); - - const Value cylinderPosition = GetValue(cylinderPositionBox, 8).AsVector3(); - const Value cylinderHeight = GetValue(cylinderHeightBox, 9).AsFloat(); - const Value cylinderRadius = GetValue(cylinderRadiusBox, 10).AsFloat(); - + const Value cylinderPosition = GetValue(node->GetBox(5), 8).AsVector3(); + const Value cylinderHeight = GetValue(node->GetBox(6), 9).AsFloat(); + const Value cylinderRadius = GetValue(node->GetBox(7), 10).AsFloat(); _writer.Write( TEXT( " {{\n" @@ -817,17 +817,12 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ); break; } - // Collision (depth) + // Collision (depth) case 334: { COLLISION_BEGIN(); - - auto surfaceThicknessBox = node->GetBox(5); - - const Value surfaceThickness = GetValue(surfaceThicknessBox, 8).AsFloat(); - + const Value surfaceThickness = GetValue(node->GetBox(5), 8).AsFloat(); const auto sceneDepthTexture = findOrAddSceneTexture(MaterialSceneTextures::SceneDepth); - _writer.Write( TEXT( " {{\n" @@ -877,6 +872,82 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ); break; } + // Conform to Global SDF + case 335: + { + auto position = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Read); + auto velocity = AccessParticleAttribute(node, nodeGpu->Attributes[1], AccessMode::ReadWrite); + auto mass = AccessParticleAttribute(node, nodeGpu->Attributes[2], AccessMode::Read); + + const Value attractionSpeed = GetValue(node->GetBox(0), 2).AsFloat(); + const Value attractionForce = GetValue(node->GetBox(1), 3).AsFloat(); + const Value stickDistance = GetValue(node->GetBox(2), 4).AsFloat(); + const Value stickForce = GetValue(node->GetBox(3), 5).AsFloat(); + + auto param = findOrAddGlobalSDF(); + _includes.Add(TEXT("./Flax/GlobalSignDistanceField.hlsl")); + _writer.Write( + TEXT( + " {{\n" + " // Conform to Global SDF\n" + " float dist;\n" + " float3 dir = normalize(SampleGlobalSDFGradient({3}, {3}_Tex, {0}, dist));\n" + " if (dist > 0) dir *= -1;\n" + " float distToSurface = abs(dist);\n" + " float spdNormal = dot(dir, {1});\n" + " float ratio = smoothstep(0.0f, {6} * 2.0f, distToSurface);\n" + " float tgtSpeed = {4} * ratio;\n" + " float deltaSpeed = tgtSpeed - spdNormal;\n" + " float3 deltaVelocity = dir * (sign(deltaSpeed) * min(abs(deltaSpeed), DeltaTime * lerp({7}, {5}, ratio)) / max({2}, PARTICLE_THRESHOLD));\n" + " {1} += deltaVelocity;\n" + " }}\n" + ), position.Value, velocity.Value, mass.Value, param.ShaderName, attractionSpeed.Value, attractionForce.Value, stickDistance.Value, stickForce.Value); + break; + } + // Collision (Global SDF) + case 336: + { + COLLISION_BEGIN(); + auto param = findOrAddGlobalSDF(); + _includes.Add(TEXT("./Flax/GlobalSignDistanceField.hlsl")); + const Char* format = IsLocalSimulationSpace() + ? TEXT( + " {{\n" + " // Collision (Global SDF)\n" + " float3 nextPos = {0} + {1} * DeltaTime;\n" + " nextPos = mul(float4(nextPos, 1), WorldMatrix).xyz;\n" + " float dist = SampleGlobalSDF({10}, {10}_Tex, nextPos);\n" + " if (dist < {5})\n" + " {{\n" + " {0} = mul(float4({0}, 1), WorldMatrix).xyz;\n" + " float3 n = normalize(SampleGlobalSDFGradient({10}, {10}_Tex, {0}, dist));\n" + " {0} += n * -dist;\n" + " {0} = mul(float4({0}, 1), InvWorldMatrix).xyz;\n" + COLLISION_LOGIC() + " }}\n" + ) + : TEXT( + " {{\n" + " // Collision (Global SDF)\n" + " float3 nextPos = {0} + {1} * DeltaTime;\n" + " float dist = SampleGlobalSDF({10}, {10}_Tex, nextPos);\n" + " if (dist < {5})\n" + " {{\n" + " float3 n = normalize(SampleGlobalSDFGradient({10}, {10}_Tex, {0}, dist));\n" + " {0} += n * -dist;\n" + COLLISION_LOGIC() + " }}\n" + ); + _writer.Write(format, + // 0-4 + positionAttr.Value, velocityAttr.Value, ageAttr.Value, invert, sign, + // 5-9 + radius.Value, roughness.Value, elasticity.Value, friction.Value, lifetimeLoss.Value, + // 10 + param.ShaderName + ); + break; + } #undef COLLISION_BEGIN #undef COLLISION_LOGIC diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp index 65935f381..7e6b081ed 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp @@ -117,7 +117,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupParameters(Box* box, Node* node, V { switch (node->TypeID) { - // Get + // Get case 1: case 2: { @@ -188,7 +188,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupParameters(Box* box, Node* node, V value = Value(VariantType::Object, param->ShaderName); break; default: - CRASH; + CRASH; break; } } @@ -208,7 +208,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupTools(Box* box, Node* node, Value& { switch (node->TypeID) { - // Linearize Depth + // Linearize Depth case 7: { // Get input @@ -218,11 +218,11 @@ void ParticleEmitterGPUGenerator::ProcessGroupTools(Box* box, Node* node, Value& linearizeSceneDepth(node, depth, value); break; } - // Time + // Time case 8: value = box->ID == 0 ? Value(VariantType::Float, TEXT("Time")) : Value(VariantType::Float, TEXT("DeltaTime")); break; - // Transform Position To Screen UV + // Transform Position To Screen UV case 9: { const Value position = tryGetValue(node->GetBox(0), Value::Zero).AsVector3(); @@ -242,13 +242,11 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va { switch (node->TypeID) { - // Particle Attribute + // Particle Attribute case 100: - { value = AccessParticleAttribute(node, (StringView)node->Values[0], static_cast(node->Values[1].AsInt), AccessMode::Read); break; - } - // Particle Attribute (by index) + // Particle Attribute (by index) case 303: { const Char* format; @@ -285,61 +283,43 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va value = writeLocal(type, String::Format(format, attribute.Offset, particleIndex.Value), node); break; } - // Particle Position + // Particle Position case 101: - { value = AccessParticleAttribute(node, TEXT("Position"), ParticleAttribute::ValueTypes::Vector3, AccessMode::Read); break; - } - // Particle Lifetime + // Particle Lifetime case 102: - { value = AccessParticleAttribute(node, TEXT("Lifetime"), ParticleAttribute::ValueTypes::Float, AccessMode::Read); break; - } - // Particle Age + // Particle Age case 103: - { value = AccessParticleAttribute(node, TEXT("Age"), ParticleAttribute::ValueTypes::Float, AccessMode::Read); break; - } - // Particle Color + // Particle Color case 104: - { value = AccessParticleAttribute(node, TEXT("Color"), ParticleAttribute::ValueTypes::Vector4, AccessMode::Read); break; - } - // Particle Velocity + // Particle Velocity case 105: - { value = AccessParticleAttribute(node, TEXT("Velocity"), ParticleAttribute::ValueTypes::Vector3, AccessMode::Read); break; - } - // Particle Sprite Size + // Particle Sprite Size case 106: - { value = AccessParticleAttribute(node, TEXT("SpriteSize"), ParticleAttribute::ValueTypes::Vector2, AccessMode::Read); break; - } - // Particle Mass + // Particle Mass case 107: - { value = AccessParticleAttribute(node, TEXT("Mass"), ParticleAttribute::ValueTypes::Float, AccessMode::Read); break; - } - // Particle Rotation + // Particle Rotation case 108: - { value = AccessParticleAttribute(node, TEXT("Rotation"), ParticleAttribute::ValueTypes::Vector3, AccessMode::Read); break; - } - // Particle Angular Velocity + // Particle Angular Velocity case 109: - { value = AccessParticleAttribute(node, TEXT("AngularVelocity"), ParticleAttribute::ValueTypes::Vector3, AccessMode::Read); break; - } - // Particle Normalized Age + // Particle Normalized Age case 110: { const auto age = AccessParticleAttribute(node, TEXT("Age"), ParticleAttribute::ValueTypes::Float, AccessMode::Read); @@ -347,95 +327,65 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va value = writeOperation2(node, age, lifetime, '/'); break; } - // Particle Radius + // Particle Radius case 111: - { value = AccessParticleAttribute(node, TEXT("Radius"), ParticleAttribute::ValueTypes::Float, AccessMode::Read); break; - } - // Effect Position + // Effect Position case 200: - { value = Value(VariantType::Vector3, TEXT("EffectPosition")); break; - } - // Effect Rotation + // Effect Rotation case 201: - { value = Value(VariantType::Quaternion, TEXT("EffectRotation")); break; - } - // Effect Scale + // Effect Scale case 202: - { value = Value(VariantType::Vector3, TEXT("EffectScale")); break; - } - // Simulation Mode + // Simulation Mode case 203: - { value = Value(box->ID == 1); break; - } - // View Position + // View Position case 204: - { value = Value(VariantType::Vector3, TEXT("ViewPos")); break; - } - // View Direction + // View Direction case 205: - { value = Value(VariantType::Vector3, TEXT("ViewDir")); break; - } - // View Far Plane + // View Far Plane case 206: - { value = Value(VariantType::Float, TEXT("ViewFar")); break; - } - // Screen Size + // Screen Size case 207: - { value = Value(VariantType::Vector2, box->ID == 0 ? TEXT("ScreenSize.xy") : TEXT("ScreenSize.zw")); break; - } - // Random Float + // Random Float case 208: - { value = writeLocal(VariantType::Float, TEXT("RAND"), node); break; - } - // Random Vector2 + // Random Vector2 case 209: - { value = writeLocal(VariantType::Vector2, TEXT("RAND2"), node); break; - } - // Random Vector3 + // Random Vector3 case 210: - { value = writeLocal(VariantType::Vector3, TEXT("RAND3"), node); break; - } - // Random Vector4 + // Random Vector4 case 211: - { value = writeLocal(VariantType::Vector4, TEXT("RAND4"), node); break; - } - // Particle Position (world space) + // Particle Position (world space) case 212: - { value = AccessParticleAttribute(node, TEXT("Position"), ParticleAttribute::ValueTypes::Vector3, AccessMode::Read); - if (((ParticleEmitterGraphGPU*)_graphStack.Peek())->SimulationSpace == ParticlesSimulationSpace::Local) - { + if (IsLocalSimulationSpace()) value = writeLocal(VariantType::Vector3, String::Format(TEXT("mul(float4({0}, 1), WorldMatrix).xyz"), value.Value), node); - } break; - } - // Random Float Range + // Random Float Range case 213: { auto& a = node->Values[0].AsFloat; @@ -443,7 +393,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va value = writeLocal(VariantType::Float, String::Format(TEXT("lerp({0}, {1}, RAND)"), a, b), node); break; } - // Random Vector2 Range + // Random Vector2 Range case 214: { auto& a = node->Values[0].AsVector2(); @@ -451,7 +401,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va value = writeLocal(VariantType::Vector2, String::Format(TEXT("float2(lerp({0}, {1}, RAND), lerp({2}, {3}, RAND))"), a.X, b.X, a.Y, b.Y), node); break; } - // Random Vector3 Range + // Random Vector3 Range case 215: { auto& a = node->Values[0].AsVector3(); @@ -459,7 +409,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va value = writeLocal(VariantType::Vector3, String::Format(TEXT("float3(lerp({0}, {1}, RAND), lerp({2}, {3}, RAND), lerp({4}, {5}, RAND))"), a.X, b.X, a.Y, b.Y, a.Z, b.Z), node); break; } - // Random Vector4 Range + // Random Vector4 Range case 216: { auto& a = node->Values[0].AsVector4(); @@ -467,7 +417,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va value = writeLocal(VariantType::Vector4, String::Format(TEXT("float4(lerp({0}, {1}, RAND), lerp({2}, {3}, RAND), lerp({4}, {5}, RAND), lerp({6}, {7}, RAND))"), a.X, b.X, a.Y, b.Y, a.Z, b.Z, a.W, b.W), node); break; } - // Particle Emitter Function + // Particle Emitter Function case 300: { // Load function asset @@ -522,11 +472,11 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va _graphStack.Pop(); break; } - // Particle Index + // Particle Index case 301: value = Value(VariantType::Uint, TEXT("context.ParticleIndex")); break; - // Particles Count + // Particles Count case 302: value = Value(VariantType::Uint, TEXT("context.ParticlesCount")); break; @@ -539,7 +489,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupFunction(Box* box, Node* node, Val { switch (node->TypeID) { - // Function Input + // Function Input case 1: { // Find the function call @@ -599,7 +549,6 @@ void ParticleEmitterGPUGenerator::ProcessGroupFunction(Box* box, Node* node, Val } break; } - default: break; } diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp index 8f2c76b64..46a25560f 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp @@ -120,7 +120,7 @@ bool ParticleEmitterGPUGenerator::sampleSceneTexture(Node* caller, Box* box, con result = Value(VariantType::Float, valueBox->Cache.Value + _subs[3]); break; default: - CRASH; + CRASH; break; } @@ -151,7 +151,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupTextures(Box* box, Node* node, Val { switch (node->TypeID) { - // Scene Texture + // Scene Texture case 6: { // Get texture type @@ -255,11 +255,11 @@ void ParticleEmitterGPUGenerator::ProcessGroupTextures(Box* box, Node* node, Val } break; } - // Scene Depth + // Scene Depth case 8: sampleSceneDepth(node, value, box); break; - // Texture + // Texture case 11: { // Check if texture has been selected @@ -276,7 +276,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupTextures(Box* box, Node* node, Val } break; } - // Load Texture + // Load Texture case 13: { // Get input texture @@ -303,6 +303,15 @@ void ParticleEmitterGPUGenerator::ProcessGroupTextures(Box* box, Node* node, Val loadTexture(node, box, copy, value); break; } + // Sample Global SDF + case 14: + { + auto param = findOrAddGlobalSDF(); + Value worldPosition = tryGetValue(node->GetBox(1), Value(VariantType::Vector3, TEXT("input.WorldPosition.xyz"))).Cast(VariantType::Vector3); + value = writeLocal(VariantType::Float, String::Format(TEXT("SampleGlobalSDF({0}, {0}_Tex, {1})"), param.ShaderName, worldPosition.Value), node); + _includes.Add(TEXT("./Flax/GlobalSignDistanceField.hlsl")); + break; + } default: break; } diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp index d1c55021b..2b0e5b8e6 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp @@ -208,7 +208,7 @@ bool ParticleEmitterGPUGenerator::Generate(WriteStream& source, BytesContainer& typeName = TEXT("uint"); break; default: - CRASH; + CRASH; } _writer.Write(TEXT("// {0:^6} | {1:^6} | {2}\n"), a.Offset, typeName, a.Name); } diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h index 6e35f96af..988883965 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h @@ -156,6 +156,16 @@ private: { return box->HasConnection() ? eatBox(box->GetParent(), box->FirstConnection()) : Value::Zero; } + + bool IsLocalSimulationSpace() const + { + return ((ParticleEmitterGraphGPU*)_graphStack.Peek())->SimulationSpace == ParticlesSimulationSpace::Local; + } + + bool IsWorldSimulationSpace() const + { + return ((ParticleEmitterGraphGPU*)_graphStack.Peek())->SimulationSpace == ParticlesSimulationSpace::World; + } }; #endif diff --git a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h index 8c803726e..d03d7ebea 100644 --- a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h +++ b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h @@ -355,7 +355,7 @@ public: USE_ATTRIBUTE(Mass, Float, 2); break; } - // Position (plane/box surface/box volume/cylinder/line/sphere/circle/disc/torus) + // Position (plane/box surface/box volume/cylinder/line/sphere/circle/disc/torus/Global SDF) case GRAPH_NODE_MAKE_TYPE(15, 202): case GRAPH_NODE_MAKE_TYPE(15, 203): case GRAPH_NODE_MAKE_TYPE(15, 204): @@ -366,6 +366,7 @@ public: case GRAPH_NODE_MAKE_TYPE(15, 209): case GRAPH_NODE_MAKE_TYPE(15, 210): case GRAPH_NODE_MAKE_TYPE(15, 211): + case GRAPH_NODE_MAKE_TYPE(15, 215): { USE_ATTRIBUTE(Position, Vector3, 0); break; @@ -415,6 +416,7 @@ public: #undef CASE_SET_PARTICLE_ATTRIBUTE // Conform to Sphere case GRAPH_NODE_MAKE_TYPE(15, 305): + case GRAPH_NODE_MAKE_TYPE(15, 335): // Conform to Global SDF { USE_ATTRIBUTE(Position, Vector3, 0); USE_ATTRIBUTE(Velocity, Vector3, 1); @@ -428,12 +430,13 @@ public: USE_ATTRIBUTE(Position, Vector3, 0); break; } - // Collision (plane/sphere/box/cylinder/depth) + // Collision (plane/sphere/box/cylinder/depth/Global SDF) case GRAPH_NODE_MAKE_TYPE(15, 330): case GRAPH_NODE_MAKE_TYPE(15, 331): case GRAPH_NODE_MAKE_TYPE(15, 332): case GRAPH_NODE_MAKE_TYPE(15, 333): case GRAPH_NODE_MAKE_TYPE(15, 334): + case GRAPH_NODE_MAKE_TYPE(15, 336): { USE_ATTRIBUTE(Position, Vector3, 0); USE_ATTRIBUTE(Velocity, Vector3, 1); diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 4b088951b..5d32aceff 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -312,7 +312,7 @@ void ParticleEffect::UpdateBounds() _box = bounds; BoundingSphere::FromBox(bounds, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void ParticleEffect::Sync() @@ -494,15 +494,12 @@ bool ParticleEffect::HasContentLoaded() const void ParticleEffect::Draw(RenderContext& renderContext) { + if (renderContext.View.Pass == DrawPass::GlobalSDF || renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) + return; _lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(GetPosition(), renderContext.View.Position)); Particles::DrawParticles(renderContext, this); } -void ParticleEffect::DrawGeneric(RenderContext& renderContext) -{ - Draw(renderContext); -} - #if USE_EDITOR #include "Engine/Debug/DebugDraw.h" @@ -520,7 +517,7 @@ void ParticleEffect::OnDebugDrawSelected() void ParticleEffect::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void ParticleEffect::Serialize(SerializeStream& stream, const void* otherObj) @@ -699,7 +696,7 @@ void ParticleEffect::EndPlay() void ParticleEffect::OnEnable() { GetScene()->Ticking.Update.AddTick(this); - _sceneRenderingKey = GetSceneRendering()->AddGeometry(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); GetScene()->Ticking.Update.AddTickExecuteInEditor(this); @@ -715,7 +712,7 @@ void ParticleEffect::OnDisable() GetScene()->Ticking.Update.RemoveTickExecuteInEditor(this); GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveGeometry(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); GetScene()->Ticking.Update.RemoveTick(this); // Base diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h index c0c13b642..454eba474 100644 --- a/Source/Engine/Particles/ParticleEffect.h +++ b/Source/Engine/Particles/ParticleEffect.h @@ -385,7 +385,6 @@ public: // [Actor] bool HasContentLoaded() const override; void Draw(RenderContext& renderContext) override; - void DrawGeneric(RenderContext& renderContext) override; #if USE_EDITOR void OnDebugDrawSelected() override; #endif diff --git a/Source/Engine/Particles/ParticleSystem.cpp b/Source/Engine/Particles/ParticleSystem.cpp index 6a1f32327..745bca545 100644 --- a/Source/Engine/Particles/ParticleSystem.cpp +++ b/Source/Engine/Particles/ParticleSystem.cpp @@ -461,7 +461,7 @@ void ParticleSystem::unload(bool isReloading) FramesPerSecond = 0.0f; DurationFrames = 0; Emitters.Resize(0); - EmittersParametersOverrides.Cleanup(); + EmittersParametersOverrides.SetCapacity(0); Tracks.Resize(0); } diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index a0972d867..174a6c840 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -176,8 +176,12 @@ void GetWindowsVersion(String& windowsName, int32& versionMajor, int32& versionM VersionMajor = 6; VersionMinor = 2; } - - if (VersionMajor == 0 && VersionMinor == 0) + else if (VersionMajor >= 10 && VersionBuild >= 22000) + { + // Windows 11 + windowsName.Replace(TEXT("10"), TEXT("11")); + } + else if (VersionMajor == 0 && VersionMinor == 0) { String windowsVersion; GetStringRegKey(hKey, TEXT("CurrentVersion"), windowsVersion, TEXT("")); diff --git a/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp b/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp index d5f63f5cf..0e6489fc4 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp @@ -6,7 +6,6 @@ #if USE_EDITOR -#include "Engine/Core/Log.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Engine/Engine.h" #include "Engine/Platform/IGuiData.h" @@ -575,27 +574,15 @@ public: int64 ExitFlag = 0; -public: - // [ThreadPoolTask] bool Run() override { - const uint64 beginFrame = Engine::FrameCount; - Scripting::GetScriptsDomain()->Dispatch(); - while (Platform::AtomicRead(&ExitFlag) == 0) { - // Render single frame Engine::OnDraw(); - - // Wait for a while Platform::Sleep(20); } - - const uint64 endFrame = Engine::FrameCount; - LOG(Info, "Rendered {0} frames during DoDragDrop stall.", endFrame - beginFrame); - return false; } }; diff --git a/Source/Engine/Profiler/ProfilerCPU.h b/Source/Engine/Profiler/ProfilerCPU.h index b9e070bb0..e4d40a444 100644 --- a/Source/Engine/Profiler/ProfilerCPU.h +++ b/Source/Engine/Profiler/ProfilerCPU.h @@ -403,9 +403,11 @@ struct TIsPODType #ifdef TRACY_ENABLE #define PROFILE_CPU_SRC_LOC(srcLoc) tracy::ScopedZone ___tracy_scoped_zone( (tracy::SourceLocationData*)&(srcLoc) ); ScopeProfileBlockCPU ProfileBlockCPU((srcLoc).name) #define PROFILE_CPU_ASSET(asset) ZoneScoped; const StringView __tracy_asset_name((asset)->GetPath()); ZoneName(*__tracy_asset_name, __tracy_asset_name.Length()) +#define PROFILE_CPU_ACTOR(actor) ZoneScoped; const StringView __tracy_actor_name((actor)->GetName()); ZoneName(*__tracy_actor_name, __tracy_actor_name.Length()) #else #define PROFILE_CPU_SRC_LOC(srcLoc) ScopeProfileBlockCPU ProfileBlockCPU((srcLoc).name) #define PROFILE_CPU_ASSET(asset) +#define PROFILE_CPU_ACTOR(actor) #endif #else @@ -415,5 +417,6 @@ struct TIsPODType #define PROFILE_CPU_NAMED(name) #define PROFILE_CPU_SRC_LOC(srcLoc) #define PROFILE_CPU_ASSET(asset) +#define PROFILE_CPU_ACTOR(actor) #endif diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index e3b49b41f..16f2dd590 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -322,13 +322,13 @@ struct TIsPODType #define GEOMETRY_DRAW_STATE_EVENT_BEGIN(drawState, worldMatrix) \ const auto frame = Engine::FrameCount; \ - if (drawState.PrevFrame + 1 < frame) \ + if (drawState.PrevFrame + 1 < frame && !renderContext.View.IsSingleFrame) \ { \ drawState.PrevWorld = worldMatrix; \ } #define GEOMETRY_DRAW_STATE_EVENT_END(drawState, worldMatrix) \ - if (drawState.PrevFrame != frame) \ + if (drawState.PrevFrame != frame && !renderContext.View.IsSingleFrame) \ { \ drawState.PrevWorld = worldMatrix; \ drawState.PrevFrame = frame; \ diff --git a/Source/Engine/Renderer/Editor/LODPreview.cpp b/Source/Engine/Renderer/Editor/LODPreview.cpp index 57b79083d..612de21a8 100644 --- a/Source/Engine/Renderer/Editor/LODPreview.cpp +++ b/Source/Engine/Renderer/Editor/LODPreview.cpp @@ -22,6 +22,11 @@ const MaterialInfo& LODPreviewMaterialShader::GetInfo() const return _material->GetInfo(); } +GPUShader* LODPreviewMaterialShader::GetShader() const +{ + return _material->GetShader(); +} + bool LODPreviewMaterialShader::IsReady() const { return _material && _material->IsReady(); diff --git a/Source/Engine/Renderer/Editor/LODPreview.h b/Source/Engine/Renderer/Editor/LODPreview.h index c8df9cadd..8de84a7cf 100644 --- a/Source/Engine/Renderer/Editor/LODPreview.h +++ b/Source/Engine/Renderer/Editor/LODPreview.h @@ -30,6 +30,7 @@ public: // [IMaterial] const MaterialInfo& GetInfo() const override; + GPUShader* GetShader() const override; bool IsReady() const override; bool CanUseInstancing(InstancingHandler& handler) const override; DrawPass GetDrawModes() const override; diff --git a/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp b/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp index 6b7ac9b5f..d5a6d11e8 100644 --- a/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp +++ b/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp @@ -53,6 +53,11 @@ const MaterialInfo& LightmapUVsDensityMaterialShader::GetInfo() const return _info; } +GPUShader* LightmapUVsDensityMaterialShader::GetShader() const +{ + return _shader->GetShader(); +} + bool LightmapUVsDensityMaterialShader::IsReady() const { return _shader && _shader->IsLoaded(); diff --git a/Source/Engine/Renderer/Editor/LightmapUVsDensity.h b/Source/Engine/Renderer/Editor/LightmapUVsDensity.h index a8905094a..9c293ca57 100644 --- a/Source/Engine/Renderer/Editor/LightmapUVsDensity.h +++ b/Source/Engine/Renderer/Editor/LightmapUVsDensity.h @@ -40,6 +40,7 @@ public: // [IMaterial] const MaterialInfo& GetInfo() const override; + GPUShader* GetShader() const override; bool IsReady() const override; DrawPass GetDrawModes() const override; void Bind(BindParameters& params) override; diff --git a/Source/Engine/Renderer/Editor/MaterialComplexity.cpp b/Source/Engine/Renderer/Editor/MaterialComplexity.cpp index 81c125d43..e8e1feb97 100644 --- a/Source/Engine/Renderer/Editor/MaterialComplexity.cpp +++ b/Source/Engine/Renderer/Editor/MaterialComplexity.cpp @@ -29,6 +29,11 @@ const MaterialInfo& MaterialComplexityMaterialShader::WrapperShader::GetInfo() c return Info; } +GPUShader* MaterialComplexityMaterialShader::WrapperShader::GetShader() const +{ + return MaterialAsset->GetShader(); +} + bool MaterialComplexityMaterialShader::WrapperShader::IsReady() const { return MaterialAsset && MaterialAsset->IsReady(); diff --git a/Source/Engine/Renderer/Editor/MaterialComplexity.h b/Source/Engine/Renderer/Editor/MaterialComplexity.h index 292f736a5..716964f0d 100644 --- a/Source/Engine/Renderer/Editor/MaterialComplexity.h +++ b/Source/Engine/Renderer/Editor/MaterialComplexity.h @@ -25,6 +25,7 @@ private: MaterialDomain Domain; AssetReference MaterialAsset; const MaterialInfo& GetInfo() const override; + GPUShader* GetShader() const override; bool IsReady() const override; bool CanUseInstancing(InstancingHandler& handler) const override; DrawPass GetDrawModes() const override; diff --git a/Source/Engine/Renderer/Editor/VertexColors.cpp b/Source/Engine/Renderer/Editor/VertexColors.cpp index 965ba3fd9..4b4a8fc3c 100644 --- a/Source/Engine/Renderer/Editor/VertexColors.cpp +++ b/Source/Engine/Renderer/Editor/VertexColors.cpp @@ -41,6 +41,11 @@ const MaterialInfo& VertexColorsMaterialShader::GetInfo() const return _info; } +GPUShader* VertexColorsMaterialShader::GetShader() const +{ + return _shader->GetShader(); +} + bool VertexColorsMaterialShader::IsReady() const { return _shader && _shader->IsLoaded(); diff --git a/Source/Engine/Renderer/Editor/VertexColors.h b/Source/Engine/Renderer/Editor/VertexColors.h index ffeca3245..79ea22ae0 100644 --- a/Source/Engine/Renderer/Editor/VertexColors.h +++ b/Source/Engine/Renderer/Editor/VertexColors.h @@ -38,6 +38,7 @@ public: // [IMaterial] const MaterialInfo& GetInfo() const override; + GPUShader* GetShader() const override; bool IsReady() const override; DrawPass GetDrawModes() const override; void Bind(BindParameters& params) override; diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp index 682dd9b6a..e8b753b09 100644 --- a/Source/Engine/Renderer/GBufferPass.cpp +++ b/Source/Engine/Renderer/GBufferPass.cpp @@ -249,7 +249,7 @@ bool SortDecal(Decal* const& a, Decal* const& b) void GBufferPass::RenderDebug(RenderContext& renderContext) { // Check if has resources loaded - if (setupResources()) + if (checkIfSkipPass()) return; // Cache data diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp new file mode 100644 index 000000000..8803c7e5b --- /dev/null +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -0,0 +1,1018 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "GlobalSurfaceAtlasPass.h" +#include "../GlobalSignDistanceFieldPass.h" +#include "../RenderList.h" +#include "../ShadowsPass.h" +#include "Engine/Core/Math/Matrix3x3.h" +#include "Engine/Core/Math/OrientedBoundingBox.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Content/Content.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderTargetPool.h" +#include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Level/Actors/StaticModel.h" +#include "Engine/Level/Scene/SceneRendering.h" +#include "Engine/Utilities/RectPack.h" + +// This must match HLSL +#define GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION 40 // Amount of chunks (in each direction) to split atlas draw distance for objects culling +#define GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE 4 +#define GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE 6 // Amount of float4s per-object +#define GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE 5 // Amount of float4s per-tile +#define GLOBAL_SURFACE_ATLAS_TILE_PADDING 1 // 1px padding to prevent color bleeding between tiles +#define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes) +#define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES 0 // Forces to redraw all object tiles every frame +#define GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS 0 // Debug draws object bounds on redraw (and tile draw projection locations) +#define GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_CHUNKS 0 // Debug draws culled chunks bounds (non-empty + +#if GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS || GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_CHUNKS +#include "Engine/Debug/DebugDraw.h" +#endif + +PACK_STRUCT(struct Data0 + { + Vector3 ViewWorldPos; + float ViewNearPlane; + float Padding00; + uint32 CulledObjectsCapacity; + float LightShadowsStrength; + float ViewFarPlane; + Vector4 ViewFrustumWorldRays[4]; + GlobalSignDistanceFieldPass::ConstantsData GlobalSDF; + GlobalSurfaceAtlasPass::ConstantsData GlobalSurfaceAtlas; + LightData Light; + }); + +PACK_STRUCT(struct AtlasTileVertex + { + Half2 Position; + Half2 TileUV; + uint32 TileAddress; + }); + +struct GlobalSurfaceAtlasTile : RectPack +{ + Vector3 ViewDirection; + Vector3 ViewPosition; + Vector3 ViewBoundsSize; + Matrix ViewMatrix; + uint32 Address; + uint32 ObjectAddressOffset; + + GlobalSurfaceAtlasTile(uint16 x, uint16 y, uint16 width, uint16 height) + : RectPack(x, y, width, height) + { + } + + void OnInsert(class GlobalSurfaceAtlasCustomBuffer* buffer, void* actorObject, int32 tileIndex); + + void OnFree() + { + } +}; + +struct GlobalSurfaceAtlasObject +{ + uint64 LastFrameUsed; + uint64 LastFrameDirty; + Actor* Actor; + GlobalSurfaceAtlasTile* Tiles[6]; + float Radius; + OrientedBoundingBox Bounds; + + GlobalSurfaceAtlasObject() + { + Platform::MemoryClear(this, sizeof(GlobalSurfaceAtlasObject)); + } + + GlobalSurfaceAtlasObject(const GlobalSurfaceAtlasObject& other) + { + Platform::MemoryCopy(this, &other, sizeof(GlobalSurfaceAtlasObject)); + } + + GlobalSurfaceAtlasObject(GlobalSurfaceAtlasObject&& other) noexcept + { + Platform::MemoryCopy(this, &other, sizeof(GlobalSurfaceAtlasObject)); + } + + GlobalSurfaceAtlasObject& operator=(const GlobalSurfaceAtlasObject& other) + { + Platform::MemoryCopy(this, &other, sizeof(GlobalSurfaceAtlasObject)); + return *this; + } + + GlobalSurfaceAtlasObject& operator=(GlobalSurfaceAtlasObject&& other) noexcept + { + Platform::MemoryCopy(this, &other, sizeof(GlobalSurfaceAtlasObject)); + return *this; + } +}; + +class GlobalSurfaceAtlasCustomBuffer : public RenderBuffers::CustomBuffer +{ +public: + int32 Resolution = 0; + uint64 LastFrameAtlasInsertFail = 0; + uint64 LastFrameAtlasDefragmentation = 0; + GPUTexture* AtlasDepth = nullptr; + GPUTexture* AtlasEmissive = nullptr; + GPUTexture* AtlasGBuffer0 = nullptr; + GPUTexture* AtlasGBuffer1 = nullptr; + GPUTexture* AtlasGBuffer2 = nullptr; + GPUTexture* AtlasDirectLight = nullptr; + GPUBuffer* ChunksBuffer = nullptr; + GPUBuffer* CulledObjectsBuffer = nullptr; + int32 CulledObjectsCounterIndex = -1; + GlobalSurfaceAtlasPass::BindingData Result; + GlobalSurfaceAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles + Dictionary Objects; + + // Cached data to be reused during RasterizeActor + uint64 CurrentFrame; + float ResolutionInv; + Vector3 ViewPosition; + float TileTexelsPerWorldUnit; + float DistanceScalingStart; + float DistanceScalingEnd; + float DistanceScaling; + + FORCE_INLINE void ClearObjects() + { + CulledObjectsCounterIndex = -1; + LastFrameAtlasDefragmentation = Engine::FrameCount; + SAFE_DELETE(AtlasTiles); + Objects.Clear(); + } + + FORCE_INLINE void Clear() + { + RenderTargetPool::Release(AtlasDepth); + RenderTargetPool::Release(AtlasEmissive); + RenderTargetPool::Release(AtlasGBuffer0); + RenderTargetPool::Release(AtlasGBuffer1); + RenderTargetPool::Release(AtlasGBuffer2); + RenderTargetPool::Release(AtlasDirectLight); + ClearObjects(); + } + + ~GlobalSurfaceAtlasCustomBuffer() + { + SAFE_DELETE_GPU_RESOURCE(ChunksBuffer); + SAFE_DELETE_GPU_RESOURCE(CulledObjectsBuffer); + Clear(); + } +}; + +void GlobalSurfaceAtlasTile::OnInsert(GlobalSurfaceAtlasCustomBuffer* buffer, void* actorObject, int32 tileIndex) +{ + buffer->Objects[actorObject].Tiles[tileIndex] = this; +} + +String GlobalSurfaceAtlasPass::ToString() const +{ + return TEXT("GlobalSurfaceAtlasPass"); +} + +bool GlobalSurfaceAtlasPass::Init() +{ + // Check platform support + const auto device = GPUDevice::Instance; + _supported = device->GetFeatureLevel() >= FeatureLevel::SM5 && device->Limits.HasCompute && device->Limits.HasTypedUAVLoad; + return false; +} + +bool GlobalSurfaceAtlasPass::setupResources() +{ + if (!_supported) + return true; + + // Load shader + if (!_shader) + { + _shader = Content::LoadAsyncInternal(TEXT("Shaders/GI/GlobalSurfaceAtlas")); + if (_shader == nullptr) + return true; +#if COMPILE_WITH_DEV_ENV + _shader.Get()->OnReloading.Bind(this); +#endif + } + if (!_shader->IsLoaded()) + return true; + + const auto device = GPUDevice::Instance; + const auto shader = _shader->GetShader(); + _cb0 = shader->GetCB(0); + if (!_cb0) + return true; + _csCullObjects = shader->GetCS("CS_CullObjects"); + + // Create pipeline state + GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; + if (!_psDebug) + { + _psDebug = device->CreatePipelineState(); + psDesc.PS = shader->GetPS("PS_Debug"); + if (_psDebug->Init(psDesc)) + return true; + } + if (!_psClear) + { + _psClear = device->CreatePipelineState(); + psDesc.DepthTestEnable = true; + psDesc.DepthWriteEnable = true; + psDesc.DepthFunc = ComparisonFunc::Always; + psDesc.VS = shader->GetVS("VS_Atlas"); + psDesc.PS = shader->GetPS("PS_Clear"); + if (_psClear->Init(psDesc)) + return true; + } + if (!_psDirectLighting0) + { + _psDirectLighting0 = device->CreatePipelineState(); + psDesc.DepthTestEnable = false; + psDesc.DepthWriteEnable = false; + psDesc.DepthFunc = ComparisonFunc::Never; + psDesc.BlendMode = BlendingMode::Add; + psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB; + psDesc.PS = shader->GetPS("PS_DirectLighting", 0); + if (_psDirectLighting0->Init(psDesc)) + return true; + _psDirectLighting1 = device->CreatePipelineState(); + psDesc.PS = shader->GetPS("PS_DirectLighting", 1); + if (_psDirectLighting1->Init(psDesc)) + return true; + } + + return false; +} + +#if COMPILE_WITH_DEV_ENV + +void GlobalSurfaceAtlasPass::OnShaderReloading(Asset* obj) +{ + SAFE_DELETE_GPU_RESOURCE(_psClear); + SAFE_DELETE_GPU_RESOURCE(_psDirectLighting0); + SAFE_DELETE_GPU_RESOURCE(_psDirectLighting1); + SAFE_DELETE_GPU_RESOURCE(_psDebug); + invalidateResources(); +} + +#endif + +void GlobalSurfaceAtlasPass::Dispose() +{ + RendererPass::Dispose(); + + // Cleanup + SAFE_DELETE(_vertexBuffer); + SAFE_DELETE(_objectsBuffer); + SAFE_DELETE_GPU_RESOURCE(_culledObjectsSizeBuffer); + SAFE_DELETE_GPU_RESOURCE(_psClear); + SAFE_DELETE_GPU_RESOURCE(_psDirectLighting0); + SAFE_DELETE_GPU_RESOURCE(_psDirectLighting1); + SAFE_DELETE_GPU_RESOURCE(_psDebug); + _cb0 = nullptr; + _shader = nullptr; +} + +bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* context, BindingData& result) +{ + // Skip if not supported + if (checkIfSkipPass()) + return true; + if (renderContext.List->Scenes.Count() == 0) + return true; + auto& surfaceAtlasData = *renderContext.Buffers->GetCustomBuffer(TEXT("GlobalSurfaceAtlas")); + + // Render Global SDF (used for direct shadowing) + GlobalSignDistanceFieldPass::BindingData bindingDataSDF; + if (GlobalSignDistanceFieldPass::Instance()->Render(renderContext, context, bindingDataSDF)) + return true; + + // Skip if already done in the current frame + const auto currentFrame = Engine::FrameCount; + if (surfaceAtlasData.LastFrameUsed == currentFrame) + { + result = surfaceAtlasData.Result; + return false; + } + surfaceAtlasData.LastFrameUsed = currentFrame; + PROFILE_GPU_CPU("Global Surface Atlas"); + + // TODO: configurable via graphics settings + const int32 resolution = 2048; + const float resolutionInv = 1.0f / resolution; + // TODO: configurable via postFx settings (maybe use Global SDF distance?) + const float distance = 20000; + + // Initialize buffers + bool noCache = surfaceAtlasData.Resolution != resolution; + if (noCache) + { + surfaceAtlasData.Clear(); + + auto desc = GPUTextureDescription::New2D(resolution, resolution, PixelFormat::Unknown); + uint64 memUsage = 0; + // TODO: try using BC4/BC5/BC7 block compression for Surface Atlas (eg. for Tiles material properties) +#define INIT_ATLAS_TEXTURE(texture, format) desc.Format = format; surfaceAtlasData.texture = RenderTargetPool::Get(desc); if (!surfaceAtlasData.texture) return true; memUsage += surfaceAtlasData.texture->GetMemoryUsage() + INIT_ATLAS_TEXTURE(AtlasEmissive, LIGHT_BUFFER_FORMAT); + INIT_ATLAS_TEXTURE(AtlasGBuffer0, GBUFFER0_FORMAT); + INIT_ATLAS_TEXTURE(AtlasGBuffer1, GBUFFER1_FORMAT); + INIT_ATLAS_TEXTURE(AtlasGBuffer2, GBUFFER2_FORMAT); + INIT_ATLAS_TEXTURE(AtlasDirectLight, LIGHT_BUFFER_FORMAT); + desc.Flags = GPUTextureFlags::DepthStencil | GPUTextureFlags::ShaderResource; + INIT_ATLAS_TEXTURE(AtlasDepth, PixelFormat::D16_UNorm); +#undef INIT_ATLAS_TEXTURE + surfaceAtlasData.Resolution = resolution; + if (!surfaceAtlasData.ChunksBuffer) + { + surfaceAtlasData.ChunksBuffer = GPUDevice::Instance->CreateBuffer(TEXT("GlobalSurfaceAtlas.ChunksBuffer")); + if (surfaceAtlasData.ChunksBuffer->Init(GPUBufferDescription::Raw(sizeof(uint32) * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION, GPUBufferFlags::ShaderResource | GPUBufferFlags::UnorderedAccess))) + return true; + memUsage += surfaceAtlasData.ChunksBuffer->GetMemoryUsage(); + } + LOG(Info, "Global Surface Atlas resolution: {0}, memory usage: {1} MB", resolution, memUsage / 1024 / 1024); + } + else + { + // Perform atlas defragmentation if needed + // TODO: track atlas used vs free ratio to skip defragmentation if it's nearly full (then maybe auto resize up?) + if (currentFrame - surfaceAtlasData.LastFrameAtlasInsertFail < 10 && + currentFrame - surfaceAtlasData.LastFrameAtlasDefragmentation > 60) + { + surfaceAtlasData.ClearObjects(); + } + } + if (!surfaceAtlasData.AtlasTiles) + surfaceAtlasData.AtlasTiles = New(0, 0, resolution, resolution); + if (!_vertexBuffer) + _vertexBuffer = New(0u, (uint32)sizeof(AtlasTileVertex), TEXT("GlobalSurfaceAtlas.VertexBuffer")); + if (!_objectsBuffer) + _objectsBuffer = New(256 * (GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE + GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE * 3 / 4), PixelFormat::R32G32B32A32_Float, false, TEXT("GlobalSurfaceAtlas.ObjectsBuffer")); + + // Utility for writing into tiles vertex buffer + const Vector2 posToClipMul(2.0f * resolutionInv, -2.0f * resolutionInv); + const Vector2 posToClipAdd(-1.0f, 1.0f); +#define VB_WRITE_TILE_POS_ONLY(tile) \ + Vector2 minPos((float)tile->X, (float)tile->Y), maxPos((float)(tile->X + tile->Width), (float)(tile->Y + tile->Height)); \ + Half2 min(minPos * posToClipMul + posToClipAdd), max(maxPos * posToClipMul + posToClipAdd); \ + auto* quad = _vertexBuffer->WriteReserve(6); \ + quad[0].Position = max; \ + quad[1].Position = { min.X, max.Y }; \ + quad[2].Position = min; \ + quad[3].Position = quad[2].Position; \ + quad[4].Position = { max.X, min.Y }; \ + quad[5].Position = quad[0].Position +#define VB_WRITE_TILE(tile) \ + Vector2 minPos((float)tile->X, (float)tile->Y), maxPos((float)(tile->X + tile->Width), (float)(tile->Y + tile->Height)); \ + Half2 min(minPos * posToClipMul + posToClipAdd), max(maxPos * posToClipMul + posToClipAdd); \ + Vector2 minUV(0, 0), maxUV(1, 1); \ + auto* quad = _vertexBuffer->WriteReserve(6); \ + quad[0] = { { max }, { maxUV }, tile->Address }; \ + quad[1] = { { min.X, max.Y }, { minUV.X, maxUV.Y }, tile->Address }; \ + quad[2] = { { min }, { minUV }, tile->Address }; \ + quad[3] = quad[2]; \ + quad[4] = { { max.X, min.Y }, { maxUV.X, minUV.Y }, tile->Address }; \ + quad[5] = quad[0] +#define VB_DRAW() \ + _vertexBuffer->Flush(context); \ + auto vb = _vertexBuffer->GetBuffer(); \ + context->BindVB(ToSpan(&vb, 1)); \ + context->DrawInstanced(_vertexBuffer->Data.Count() / sizeof(AtlasTileVertex), 1); + + // Add objects into the atlas + { + PROFILE_CPU_NAMED("Draw"); + _objectsBuffer->Clear(); + _dirtyObjectsBuffer.Clear(); + _surfaceAtlasData = &surfaceAtlasData; + renderContext.View.Pass = DrawPass::GlobalSurfaceAtlas; + surfaceAtlasData.CurrentFrame = currentFrame; + surfaceAtlasData.ResolutionInv = resolutionInv; + surfaceAtlasData.ViewPosition = renderContext.View.Position; + surfaceAtlasData.TileTexelsPerWorldUnit = 1.0f / 10.0f; // Scales the tiles resolution + surfaceAtlasData.DistanceScalingStart = 2000.0f; // Distance from camera at which the tiles resolution starts to be scaled down + surfaceAtlasData.DistanceScalingEnd = 5000.0f; // Distance from camera at which the tiles resolution end to be scaled down + surfaceAtlasData.DistanceScaling = 0.1f; // The scale for tiles at distanceScalingEnd and further away + // TODO: add DetailsScale param to adjust quality of scene details in Global Surface Atlas + const uint32 viewMask = renderContext.View.RenderLayersMask; + const Vector3 viewPosition = renderContext.View.Position; + const float minObjectRadius = 20.0f; // Skip too small objects + for (auto* scene : renderContext.List->Scenes) + { + for (auto& e : scene->Actors) + { + if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::DistanceSpherePoint(e.Bounds, viewPosition) < distance) + { + e.Actor->Draw(renderContext); + } + } + } + } + + // Remove unused objects + for (auto it = surfaceAtlasData.Objects.Begin(); it.IsNotEnd(); ++it) + { + if (it->Value.LastFrameUsed != currentFrame) + { + for (auto& tile : it->Value.Tiles) + { + if (tile) + tile->Free(); + } + surfaceAtlasData.Objects.Remove(it); + } + } + + // Rasterize world geometry material properties into Global Surface Atlas + if (_dirtyObjectsBuffer.Count() != 0) + { + PROFILE_GPU_CPU("Rasterize Tiles"); + + RenderContext renderContextTiles = renderContext; + renderContextTiles.List = RenderList::GetFromPool(); + renderContextTiles.View.Pass = DrawPass::GBuffer; + renderContextTiles.View.Mode = ViewMode::Default; + renderContextTiles.View.ModelLODBias += 100000; + renderContextTiles.View.ShadowModelLODBias += 100000; + renderContextTiles.View.IsSingleFrame = true; + renderContextTiles.View.IsCullingDisabled = true; + renderContextTiles.View.Near = 0.0f; + renderContextTiles.View.Prepare(renderContextTiles); + + GPUTextureView* depthBuffer = surfaceAtlasData.AtlasDepth->View(); + GPUTextureView* targetBuffers[4] = + { + surfaceAtlasData.AtlasEmissive->View(), + surfaceAtlasData.AtlasGBuffer0->View(), + surfaceAtlasData.AtlasGBuffer1->View(), + surfaceAtlasData.AtlasGBuffer2->View(), + }; + context->SetRenderTarget(depthBuffer, ToSpan(targetBuffers, ARRAY_COUNT(targetBuffers))); + { + PROFILE_GPU_CPU("Clear"); + if (noCache || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES) + { + // Full-atlas hardware clear + context->ClearDepth(depthBuffer); + context->Clear(targetBuffers[0], Color::Transparent); + context->Clear(targetBuffers[1], Color::Transparent); + context->Clear(targetBuffers[2], Color::Transparent); + context->Clear(targetBuffers[3], Color(1, 0, 0, 0)); + } + else + { + // Per-tile clear (with a single draw call) + _vertexBuffer->Clear(); + _vertexBuffer->Data.EnsureCapacity(_dirtyObjectsBuffer.Count() * 6 * sizeof(AtlasTileVertex)); + for (void* actorObject : _dirtyObjectsBuffer) + { + const auto& object = ((const Dictionary&)surfaceAtlasData.Objects)[actorObject]; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile) + continue; + VB_WRITE_TILE_POS_ONLY(tile); + } + } + context->SetState(_psClear); + context->SetViewportAndScissors(Viewport(0, 0, resolution, resolution)); + VB_DRAW(); + } + } + // TODO: limit dirty objects count on a first frame (eg. collect overflown objects to be redirty next frame) + auto& drawCallsListGBuffer = renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBuffer]; + auto& drawCallsListGBufferNoDecals = renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBufferNoDecals]; + drawCallsListGBuffer.CanUseInstancing = false; + drawCallsListGBufferNoDecals.CanUseInstancing = false; + for (void* actorObject : _dirtyObjectsBuffer) + { + const auto& object = ((const Dictionary&)surfaceAtlasData.Objects)[actorObject]; + + // Clear draw calls list + renderContextTiles.List->DrawCalls.Clear(); + renderContextTiles.List->BatchedDrawCalls.Clear(); + drawCallsListGBuffer.Indices.Clear(); + drawCallsListGBuffer.PreBatchedDrawCalls.Clear(); + drawCallsListGBufferNoDecals.Indices.Clear(); + drawCallsListGBufferNoDecals.PreBatchedDrawCalls.Clear(); + + // Fake projection matrix to disable Screen Size culling based on RenderTools::ComputeBoundsScreenRadiusSquared + renderContextTiles.View.Projection.Values[0][0] = 10000.0f; + + // Collect draw calls for the object + object.Actor->Draw(renderContextTiles); + + // Render all tiles into the atlas +#if GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS + DebugDraw::DrawBox(object.Bounds, Color::Red.AlphaMultiplied(0.4f)); +#endif + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile) + continue; + const float tileWidth = (float)tile->Width - GLOBAL_SURFACE_ATLAS_TILE_PADDING; + const float tileHeight = (float)tile->Height - GLOBAL_SURFACE_ATLAS_TILE_PADDING; + + // Setup projection to capture object from the side + renderContextTiles.View.Position = tile->ViewPosition; + renderContextTiles.View.Direction = tile->ViewDirection; + renderContextTiles.View.Near = -GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET; + renderContextTiles.View.Far = tile->ViewBoundsSize.Z + 2 * GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET; + Matrix projectionMatrix; + Matrix::Ortho(tile->ViewBoundsSize.X, tile->ViewBoundsSize.Y, renderContextTiles.View.Near, renderContextTiles.View.Far, projectionMatrix); + renderContextTiles.View.SetUp(tile->ViewMatrix, projectionMatrix); +#if GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS + DebugDraw::DrawLine(renderContextTiles.View.Position, renderContextTiles.View.Position + renderContextTiles.View.Direction * 20.0f, Color::Orange); + DebugDraw::DrawWireSphere(BoundingSphere(renderContextTiles.View.Position, 10.0f), Color::Green); +#endif + + // Draw + context->SetViewportAndScissors(Viewport(tile->X, tile->Y, tileWidth, tileHeight)); + renderContextTiles.List->ExecuteDrawCalls(renderContextTiles, drawCallsListGBuffer); + renderContextTiles.List->ExecuteDrawCalls(renderContextTiles, drawCallsListGBufferNoDecals); + } + } + context->ResetRenderTarget(); + RenderList::ReturnToPool(renderContextTiles.List); + } + + // Send objects data to the GPU + { + PROFILE_GPU_CPU("Update Objects"); + _objectsBuffer->Flush(context); + } + + // Init constants + result.Constants.ViewPos = renderContext.View.Position; + result.Constants.Resolution = (float)resolution; + result.Constants.ChunkSize = distance / (float)GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION; + result.Constants.ObjectsCount = surfaceAtlasData.Objects.Count(); + + // Cull objects into chunks (for faster Atlas sampling) + if (surfaceAtlasData.Objects.Count() != 0) + { + // Each chunk (ChunksBuffer) contains uint with address of the culled objects data start in CulledObjectsBuffer. + // If chunk has address=0 then it's unused/empty. + // Chunk [0,0,0] is unused and it's address=0 is used for atomic counter for writing into CulledObjectsBuffer. + // Each chunk data contains objects count + all objects with tiles copied into buffer. + // This allows to quickly convert world-space position into chunk, then read chunk data start and loop over culled objects (less objects and data already in place). + PROFILE_GPU_CPU("Cull Objects"); + uint32 objectsBufferCapacity = (uint32)((float)_objectsBuffer->Data.Count() * 1.3f); + + // Copy counter from ChunksBuffer into staging buffer to access current chunks memory usage to adapt dynamically to the scene complexity + if (surfaceAtlasData.ChunksBuffer) + { + if (!_culledObjectsSizeBuffer) + { + Platform::MemoryClear(_culledObjectsSizeFrames, sizeof(_culledObjectsSizeFrames)); + _culledObjectsSizeBuffer = GPUDevice::Instance->CreateBuffer(TEXT("GlobalSurfaceAtlas.CulledObjectsSizeBuffer")); + const GPUBufferDescription desc = GPUBufferDescription::Buffer(ARRAY_COUNT(_culledObjectsSizeFrames) * sizeof(uint32), GPUBufferFlags::None, PixelFormat::R32_UInt, _culledObjectsSizeFrames, sizeof(uint32), GPUResourceUsage::StagingReadback); + if (_culledObjectsSizeBuffer->Init(desc)) + return true; + } + if (surfaceAtlasData.CulledObjectsCounterIndex != -1) + { + // Get the last counter value (accept staging readback delay) + auto data = (uint32*)_culledObjectsSizeBuffer->Map(GPUResourceMapMode::Read); + if (data) + { + uint32 counter = data[surfaceAtlasData.CulledObjectsCounterIndex]; + _culledObjectsSizeBuffer->Unmap(); + if (counter > 0) + objectsBufferCapacity = counter * sizeof(Vector4); + } + } + if (surfaceAtlasData.CulledObjectsCounterIndex == -1) + { + // Find a free timer slot + for (int32 i = 0; i < ARRAY_COUNT(_culledObjectsSizeFrames); i++) + { + if (currentFrame - _culledObjectsSizeFrames[i] > GPU_ASYNC_LATENCY) + { + surfaceAtlasData.CulledObjectsCounterIndex = i; + break; + } + } + } + if (surfaceAtlasData.CulledObjectsCounterIndex != -1) + { + // Copy current counter value + _culledObjectsSizeFrames[surfaceAtlasData.CulledObjectsCounterIndex] = currentFrame; + context->CopyBuffer(_culledObjectsSizeBuffer, surfaceAtlasData.ChunksBuffer, sizeof(uint32), surfaceAtlasData.CulledObjectsCounterIndex * sizeof(uint32), 0); + } + } + + // Allocate buffer for culled objects (estimated size) + objectsBufferCapacity = Math::Min(Math::AlignUp(objectsBufferCapacity, 4096u), (uint32)MAX_int32); + if (!surfaceAtlasData.CulledObjectsBuffer) + surfaceAtlasData.CulledObjectsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("GlobalSurfaceAtlas.CulledObjectsBuffer")); + if (surfaceAtlasData.CulledObjectsBuffer->GetSize() < objectsBufferCapacity) + { + const GPUBufferDescription desc = GPUBufferDescription::Buffer(objectsBufferCapacity, GPUBufferFlags::UnorderedAccess | GPUBufferFlags::ShaderResource, PixelFormat::R32G32B32A32_Float, nullptr, sizeof(Vector4)); + if (surfaceAtlasData.CulledObjectsBuffer->Init(desc)) + return true; + } + + // Clear chunks counter (chunk at 0 is used for a counter so chunks buffer is aligned) + uint32 counter = 1; // Indicate that 1st float4 is used so value 0 can be used as invalid chunk address + context->UpdateBuffer(surfaceAtlasData.ChunksBuffer, &counter, sizeof(counter), 0); + + // Cull objects into chunks (1 thread per chunk) + Data0 data; + data.ViewWorldPos = renderContext.View.Position; + data.ViewNearPlane = renderContext.View.Near; + data.ViewFarPlane = renderContext.View.Far; + data.CulledObjectsCapacity = objectsBufferCapacity; + data.GlobalSurfaceAtlas = result.Constants; + context->UpdateCB(_cb0, &data); + context->BindCB(0, _cb0); + static_assert(GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION % GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE == 0, "Invalid chunks resolution/groups setting."); + const int32 chunkDispatchGroups = GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION / GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE; + context->BindSR(0, _objectsBuffer->GetBuffer()->View()); + context->BindUA(0, surfaceAtlasData.ChunksBuffer->View()); + context->BindUA(1, surfaceAtlasData.CulledObjectsBuffer->View()); + context->Dispatch(_csCullObjects, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + context->ResetUA(); + +#if GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_CHUNKS + // Debug draw tiles that have any objects inside + for (int32 z = 0; z < GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION; z++) + { + for (int32 y = 0; y < GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION; y++) + { + for (int32 x = 0; x < GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION; x++) + { + Vector3 chunkCoord(x, y, z); + Vector3 chunkMin = result.GlobalSurfaceAtlas.ViewPos + (chunkCoord - (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * 0.5f)) * result.GlobalSurfaceAtlas.ChunkSize; + Vector3 chunkMax = chunkMin + result.GlobalSurfaceAtlas.ChunkSize; + BoundingBox chunkBounds(chunkMin, chunkMax); + if (Vector3::Distance(chunkBounds.GetCenter(), result.GlobalSurfaceAtlas.ViewPos) >= 2000.0f) + continue; + + int32 count = 0; + for (auto& e : surfaceAtlasData.Objects) + { + BoundingSphere objectBounds(e.Value.Bounds.GetCenter(), e.Value.Radius); + if (chunkBounds.Intersects(objectBounds)) + count++; + } + if (count != 0) + { + DebugDraw::DrawText(String::Format(TEXT("{} Objects"), count), chunkBounds.GetCenter(), Color::Green); + DebugDraw::DrawWireBox(chunkBounds, Color::Green); + } + } + } + } +#endif + } + + // Copy results + result.Atlas[0] = surfaceAtlasData.AtlasDepth; + result.Atlas[1] = surfaceAtlasData.AtlasGBuffer0; + result.Atlas[2] = surfaceAtlasData.AtlasGBuffer1; + result.Atlas[3] = surfaceAtlasData.AtlasGBuffer2; + result.Atlas[4] = surfaceAtlasData.AtlasDirectLight; + result.Chunks = surfaceAtlasData.ChunksBuffer; + result.CulledObjects = surfaceAtlasData.CulledObjectsBuffer; + surfaceAtlasData.Result = result; + + // Render direct lighting into atlas + if (surfaceAtlasData.Objects.Count() != 0) + { + PROFILE_GPU_CPU("Direct Lighting"); + + // Copy emissive light into the final direct lighting atlas + // TODO: test perf diff when manually copying only dirty object tiles and dirty light tiles + { + PROFILE_GPU_CPU("Copy Emissive"); + context->CopyTexture(surfaceAtlasData.AtlasDirectLight, 0, 0, 0, 0, surfaceAtlasData.AtlasEmissive, 0); + } + + context->SetViewportAndScissors(Viewport(0, 0, resolution, resolution)); + context->SetRenderTarget(surfaceAtlasData.AtlasDirectLight->View()); + context->BindSR(0, surfaceAtlasData.AtlasGBuffer0->View()); + context->BindSR(1, surfaceAtlasData.AtlasGBuffer1->View()); + context->BindSR(2, surfaceAtlasData.AtlasGBuffer2->View()); + context->BindSR(3, surfaceAtlasData.AtlasDepth->View()); + context->BindSR(4, _objectsBuffer->GetBuffer()->View()); + for (int32 i = 0; i < 4; i++) + { + context->BindSR(i + 5, bindingDataSDF.Cascades[i]->ViewVolume()); + context->BindSR(i + 9, bindingDataSDF.CascadeMips[i]->ViewVolume()); + } + context->BindCB(0, _cb0); + Data0 data; + data.ViewWorldPos = renderContext.View.Position; + data.GlobalSDF = bindingDataSDF.Constants; + data.GlobalSurfaceAtlas = result.Constants; + + // Shade object tiles influenced by lights to calculate direct lighting + // TODO: reduce redraw frequency for static lights (StaticFlags::Lightmap) + for (auto& light : renderContext.List->DirectionalLights) + { + // Collect tiles to shade + _vertexBuffer->Clear(); + for (const auto& e : surfaceAtlasData.Objects) + { + const auto& object = e.Value; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile || Vector3::Dot(tile->ViewDirection, light.Direction) < ZeroTolerance) + continue; + VB_WRITE_TILE(tile); + } + } + + // Draw draw light + const bool useShadow = CanRenderShadow(renderContext.View, light); + // TODO: test perf/quality when using Shadow Map for directional light (ShadowsPass::Instance()->LastDirLightShadowMap) instead of Global SDF trace + light.SetupLightData(&data.Light, useShadow); + data.LightShadowsStrength = 1.0f - light.ShadowsStrength; + context->UpdateCB(_cb0, &data); + context->SetState(_psDirectLighting0); + VB_DRAW(); + } + for (auto& light : renderContext.List->PointLights) + { + // Collect tiles to shade + _vertexBuffer->Clear(); + for (const auto& e : surfaceAtlasData.Objects) + { + const auto& object = e.Value; + Vector3 lightToObject = object.Bounds.GetCenter() - light.Position; + if (lightToObject.LengthSquared() >= Math::Square(object.Radius + light.Radius)) + continue; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile) + continue; + VB_WRITE_TILE(tile); + } + } + + // Draw draw light + const bool useShadow = CanRenderShadow(renderContext.View, light); + light.SetupLightData(&data.Light, useShadow); + data.LightShadowsStrength = 1.0f - light.ShadowsStrength; + context->UpdateCB(_cb0, &data); + context->SetState(_psDirectLighting1); + VB_DRAW(); + } + for (auto& light : renderContext.List->SpotLights) + { + // Collect tiles to shade + _vertexBuffer->Clear(); + for (const auto& e : surfaceAtlasData.Objects) + { + const auto& object = e.Value; + Vector3 lightToObject = object.Bounds.GetCenter() - light.Position; + if (lightToObject.LengthSquared() >= Math::Square(object.Radius + light.Radius)) + continue; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile || Vector3::Dot(tile->ViewDirection, light.Direction) < ZeroTolerance) + continue; + VB_WRITE_TILE(tile); + } + } + + // Draw draw light + const bool useShadow = CanRenderShadow(renderContext.View, light); + light.SetupLightData(&data.Light, useShadow); + data.LightShadowsStrength = 1.0f - light.ShadowsStrength; + context->UpdateCB(_cb0, &data); + context->SetState(_psDirectLighting1); + VB_DRAW(); + } + + context->ResetRenderTarget(); + } + + // TODO: indirect lighting apply to get infinite bounces for GI + + // TODO: explore atlas tiles optimization with feedback from renderer (eg. when tile is sampled by GI/Reflections mark it as used, then sort tiles by importance and prioritize updates for ones frequently used) + +#undef WRITE_TILE + return false; +} + +void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output) +{ + GlobalSignDistanceFieldPass::BindingData bindingDataSDF; + BindingData bindingData; + if (GlobalSignDistanceFieldPass::Instance()->Render(renderContext, context, bindingDataSDF) || Render(renderContext, context, bindingData)) + { + context->Draw(output, renderContext.Buffers->GBuffer0); + return; + } + + PROFILE_GPU_CPU("Global Surface Atlas Debug"); + const Vector2 outputSize(output->Size()); + { + Data0 data; + data.ViewWorldPos = renderContext.View.Position; + data.ViewNearPlane = renderContext.View.Near; + data.ViewFarPlane = renderContext.View.Far; + for (int32 i = 0; i < 4; i++) + data.ViewFrustumWorldRays[i] = Vector4(renderContext.List->FrustumCornersWs[i + 4], 0); + data.GlobalSDF = bindingDataSDF.Constants; + data.GlobalSurfaceAtlas = bindingData.Constants; + context->UpdateCB(_cb0, &data); + context->BindCB(0, _cb0); + } + for (int32 i = 0; i < 4; i++) + { + context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); + context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); + } + context->BindSR(8, bindingData.Chunks ? bindingData.Chunks->View() : nullptr); + context->BindSR(9, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr); + context->BindSR(10, bindingData.AtlasDepth->View()); + context->SetState(_psDebug); + context->SetRenderTarget(output->View()); + { + Vector2 outputSizeThird = outputSize * 0.333f; + Vector2 outputSizeTwoThird = outputSize * 0.666f; + + // Full screen - direct light + context->BindSR(11, bindingData.AtlasLighting->View()); + context->SetViewport(outputSize.X, outputSize.Y); + context->SetScissor(Rectangle(0, 0, outputSizeTwoThird.X, outputSize.Y)); + context->DrawFullscreenTriangle(); + + // Bottom left - diffuse + context->BindSR(11, bindingData.AtlasGBuffer0->View()); + context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, 0, outputSizeThird.X, outputSizeThird.Y)); + context->DrawFullscreenTriangle(); + + // Bottom middle - normals + context->BindSR(11, bindingData.AtlasGBuffer1->View()); + context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, outputSizeThird.Y, outputSizeThird.X, outputSizeThird.Y)); + context->DrawFullscreenTriangle(); + + // Bottom right - roughness/metalness/ao + context->BindSR(11, bindingData.AtlasGBuffer2->View()); + context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, outputSizeTwoThird.Y, outputSizeThird.X, outputSizeThird.Y)); + context->DrawFullscreenTriangle(); + } +} + +void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, void* actorObject, const BoundingSphere& actorObjectBounds, const Matrix& localToWorld, const BoundingBox& localBounds, uint32 tilesMask) +{ + GlobalSurfaceAtlasCustomBuffer& surfaceAtlasData = *_surfaceAtlasData; + Vector3 boundsSize = localBounds.GetSize() * actor->GetScale(); + const float distanceScale = Math::Lerp(1.0f, surfaceAtlasData.DistanceScaling, Math::InverseLerp(surfaceAtlasData.DistanceScalingStart, surfaceAtlasData.DistanceScalingEnd, CollisionsHelper::DistanceSpherePoint(actorObjectBounds, surfaceAtlasData.ViewPosition))); + const float tilesScale = surfaceAtlasData.TileTexelsPerWorldUnit * distanceScale; + GlobalSurfaceAtlasObject* object = surfaceAtlasData.Objects.TryGet(actorObject); + bool anyTile = false, dirty = false; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + if (((1 << tileIndex) & tilesMask) == 0) + continue; + + // Calculate optimal tile resolution for the object side + Vector3 boundsSizeTile = boundsSize; + boundsSizeTile.Raw[tileIndex / 2] = MAX_float; // Ignore depth size + boundsSizeTile.Absolute(); + uint16 tileResolution = (uint16)(boundsSizeTile.MinValue() * tilesScale); + if (tileResolution < 4) + { + // Skip too small surfaces + if (object && object->Tiles[tileIndex]) + { + object->Tiles[tileIndex]->Free(); + object->Tiles[tileIndex] = nullptr; + } + continue; + } + + // Clamp tile resolution (in pixels) + static_assert(GLOBAL_SURFACE_ATLAS_TILE_PADDING < 8, "Invalid tile size configuration. Minimum tile size must be larger than padding."); + tileResolution = Math::Clamp(tileResolution, 8, 128); + + // Snap tiles resolution (down) which allows to reuse atlas slots once object gets resizes/replaced by other object + tileResolution = Math::AlignDown(tileResolution, 8); + + // Reuse current tile (refit only on a significant resolution change) + if (object && object->Tiles[tileIndex]) + { + const uint16 tileRefitResolutionStep = 32; + const uint16 currentSize = object->Tiles[tileIndex]->Width; + if (Math::Abs(tileResolution - currentSize) < tileRefitResolutionStep) + { + anyTile = true; + continue; + } + object->Tiles[tileIndex]->Free(); + } + + // Insert tile into atlas + auto* tile = surfaceAtlasData.AtlasTiles->Insert(tileResolution, tileResolution, 0, &surfaceAtlasData, actorObject, tileIndex); + if (tile) + { + if (!object) + object = &surfaceAtlasData.Objects[actorObject]; + object->Tiles[tileIndex] = tile; + anyTile = true; + dirty = true; + } + else + { + if (object) + object->Tiles[tileIndex] = nullptr; + surfaceAtlasData.LastFrameAtlasInsertFail = surfaceAtlasData.CurrentFrame; + } + } + if (!anyTile) + return; + + // Redraw objects from time-to-time (dynamic objects can be animated, static objects can have textures streamed) + uint32 redrawFramesCount = actor->HasStaticFlag(StaticFlags::Lightmap) ? 120 : 4; + if (surfaceAtlasData.CurrentFrame - object->LastFrameDirty >= (redrawFramesCount + (actor->GetID().D & redrawFramesCount))) + dirty = true; + + // Mark object as used + object->Actor = actor; + object->LastFrameUsed = surfaceAtlasData.CurrentFrame; + object->Bounds = OrientedBoundingBox(localBounds); + object->Bounds.Transform(localToWorld); + object->Radius = actorObjectBounds.Radius; + if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES) + { + object->LastFrameDirty = surfaceAtlasData.CurrentFrame; + _dirtyObjectsBuffer.Add(actorObject); + } + + // Write to objects buffer (this must match unpacking logic in HLSL) + Matrix worldToLocalBounds; + Matrix::Invert(object->Bounds.Transformation, worldToLocalBounds); + uint32 objectAddress = _objectsBuffer->Data.Count() / sizeof(Vector4); + auto* objectData = _objectsBuffer->WriteReserve(GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE); + objectData[0] = *(Vector4*)&actorObjectBounds; + objectData[1] = Vector4::Zero; // w unused + objectData[2] = Vector4(worldToLocalBounds.M11, worldToLocalBounds.M12, worldToLocalBounds.M13, worldToLocalBounds.M41); + objectData[3] = Vector4(worldToLocalBounds.M21, worldToLocalBounds.M22, worldToLocalBounds.M23, worldToLocalBounds.M42); + objectData[4] = Vector4(worldToLocalBounds.M31, worldToLocalBounds.M32, worldToLocalBounds.M33, worldToLocalBounds.M43); + objectData[5] = Vector4(object->Bounds.Extents, 0.0f); // w unused + auto tileOffsets = reinterpret_cast(&objectData[1]); // xyz used for tile offsets packed into uint16 + auto objectDataSize = reinterpret_cast(&objectData[1].W); // w used for object size (count of Vector4s for object+tiles) + *objectDataSize = GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object->Tiles[tileIndex]; + if (!tile) + continue; + tile->ObjectAddressOffset = *objectDataSize; + tile->Address = objectAddress + tile->ObjectAddressOffset; + tileOffsets[tileIndex] = tile->ObjectAddressOffset; + *objectDataSize += GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE; + + // Setup view to render object from the side + Vector3 xAxis, yAxis, zAxis = Vector3::Zero; + zAxis.Raw[tileIndex / 2] = tileIndex & 1 ? 1.0f : -1.0f; + yAxis = tileIndex == 2 || tileIndex == 3 ? Vector3::Right : Vector3::Up; + Vector3::Cross(yAxis, zAxis, xAxis); + Vector3 localSpaceOffset = -zAxis * object->Bounds.Extents; + Vector3::TransformNormal(xAxis, object->Bounds.Transformation, xAxis); + Vector3::TransformNormal(yAxis, object->Bounds.Transformation, yAxis); + Vector3::TransformNormal(zAxis, object->Bounds.Transformation, zAxis); + xAxis.NormalizeFast(); + yAxis.NormalizeFast(); + zAxis.NormalizeFast(); + Vector3::Transform(localSpaceOffset, object->Bounds.Transformation, tile->ViewPosition); + tile->ViewDirection = zAxis; + + // Create view matrix + tile->ViewMatrix.SetColumn1(Vector4(xAxis, -Vector3::Dot(xAxis, tile->ViewPosition))); + tile->ViewMatrix.SetColumn2(Vector4(yAxis, -Vector3::Dot(yAxis, tile->ViewPosition))); + tile->ViewMatrix.SetColumn3(Vector4(zAxis, -Vector3::Dot(zAxis, tile->ViewPosition))); + tile->ViewMatrix.SetColumn4(Vector4(0, 0, 0, 1)); + + // Calculate object bounds size in the view + OrientedBoundingBox viewBounds(object->Bounds); + viewBounds.Transform(tile->ViewMatrix); + Vector3 viewExtent; + Vector3::TransformNormal(viewBounds.Extents, viewBounds.Transformation, viewExtent); + tile->ViewBoundsSize = viewExtent.GetAbsolute() * 2.0f; + + // Per-tile data + const float tileWidth = (float)tile->Width - GLOBAL_SURFACE_ATLAS_TILE_PADDING; + const float tileHeight = (float)tile->Height - GLOBAL_SURFACE_ATLAS_TILE_PADDING; + auto* tileData = _objectsBuffer->WriteReserve(GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE); + tileData[0] = Vector4(tile->X, tile->Y, tileWidth, tileHeight) * surfaceAtlasData.ResolutionInv; + tileData[1] = Vector4(tile->ViewMatrix.M11, tile->ViewMatrix.M12, tile->ViewMatrix.M13, tile->ViewMatrix.M41); + tileData[2] = Vector4(tile->ViewMatrix.M21, tile->ViewMatrix.M22, tile->ViewMatrix.M23, tile->ViewMatrix.M42); + tileData[3] = Vector4(tile->ViewMatrix.M31, tile->ViewMatrix.M32, tile->ViewMatrix.M33, tile->ViewMatrix.M43); + tileData[4] = Vector4(tile->ViewBoundsSize, 0.0f); // w unused + } +} diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h new file mode 100644 index 000000000..ca33ddc68 --- /dev/null +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h @@ -0,0 +1,97 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "../RendererPass.h" + +/// +/// Global Surface Atlas rendering pass. Captures scene geometry into a single atlas texture which contains surface diffuse color, normal vector, emission light, and calculates direct+indirect lighting. Used by Global Illumination and Reflections. +/// +class FLAXENGINE_API GlobalSurfaceAtlasPass : public RendererPass +{ +public: + // Constant buffer data for Global Surface Atlas access on a GPU. + PACK_STRUCT(struct ConstantsData + { + Vector3 ViewPos; + float Padding0; + float Padding1; + float Resolution; + float ChunkSize; + uint32 ObjectsCount; + }); + + // Binding data for the GPU. + struct BindingData + { + union + { + struct + { + GPUTexture* AtlasDepth; + GPUTexture* AtlasGBuffer0; + GPUTexture* AtlasGBuffer1; + GPUTexture* AtlasGBuffer2; + GPUTexture* AtlasLighting; + }; + GPUTexture* Atlas[5]; + }; + GPUBuffer* Chunks; + GPUBuffer* CulledObjects; + ConstantsData Constants; + }; + +private: + bool _supported = false; + AssetReference _shader; + GPUPipelineState* _psClear = nullptr; + GPUPipelineState* _psDirectLighting0 = nullptr; + GPUPipelineState* _psDirectLighting1 = nullptr; + GPUPipelineState* _psDebug = nullptr; + GPUConstantBuffer* _cb0 = nullptr; + GPUShaderProgramCS* _csCullObjects; + + // Cache + class GPUBuffer* _culledObjectsSizeBuffer = nullptr; + class DynamicTypedBuffer* _objectsBuffer = nullptr; + class DynamicVertexBuffer* _vertexBuffer = nullptr; + class GlobalSurfaceAtlasCustomBuffer* _surfaceAtlasData; + Array _dirtyObjectsBuffer; + uint64 _culledObjectsSizeFrames[8]; + +public: + /// + /// Renders the Global Surface Atlas. + /// + /// The rendering context. + /// The GPU context. + /// The result Global Surface Atlas data for binding to the shaders. + /// True if failed to render (platform doesn't support it, out of video memory, disabled feature or effect is not ready), otherwise false. + bool Render(RenderContext& renderContext, GPUContext* context, BindingData& result); + + /// + /// Renders the debug view. + /// + /// The rendering context. + /// The GPU context. + /// The output buffer. + void RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output); + + // Rasterize actor into the Global Surface Atlas. Call it from actor Draw() method during DrawPass::GlobalSurfaceAtlas. + void RasterizeActor(Actor* actor, void* actorObject, const BoundingSphere& actorObjectBounds, const Matrix& localToWorld, const BoundingBox& localBounds, uint32 tilesMask = MAX_uint32); + +private: +#if COMPILE_WITH_DEV_ENV + void OnShaderReloading(Asset* obj); +#endif + +public: + // [RendererPass] + String ToString() const override; + bool Init() override; + void Dispose() override; + +protected: + // [RendererPass] + bool setupResources() override; +}; diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp new file mode 100644 index 000000000..c94b41b6f --- /dev/null +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -0,0 +1,929 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "GlobalSignDistanceFieldPass.h" +#include "RenderList.h" +#include "Engine/Core/Math/Int3.h" +#include "Engine/Core/Collections/HashSet.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Content/Content.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderTargetPool.h" +#include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Level/Scene/SceneRendering.h" +#include "Engine/Level/Actors/StaticModel.h" + +// Some of those constants must match in shader +// TODO: try using R8 format for Global SDF +#define GLOBAL_SDF_FORMAT PixelFormat::R16_Float +#define GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT 28 // The maximum amount of models to rasterize at once as a batch into Global SDF. +#define GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT 2 // The maximum amount of heightfields to store in a single chunk. +#define GLOBAL_SDF_RASTERIZE_GROUP_SIZE 8 +#define GLOBAL_SDF_RASTERIZE_CHUNK_SIZE 32 // Global SDF chunk size in voxels. +#define GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN 4 // The margin in voxels around objects for culling. Reduces artifacts but reduces performance. +#define GLOBAL_SDF_RASTERIZE_MIP_FACTOR 4 // Global SDF mip resolution downscale factor. +#define GLOBAL_SDF_MIP_GROUP_SIZE 4 +#define GLOBAL_SDF_MIP_FLOODS 5 // Amount of flood fill passes for mip. +#define GLOBAL_SDF_DEBUG_CHUNKS 0 +#define GLOBAL_SDF_ACTOR_IS_STATIC(actor) ((actor->GetStaticFlags() & (StaticFlags::Lightmap | StaticFlags::Transform)) == (int32)(StaticFlags::Lightmap | StaticFlags::Transform)) + +static_assert(GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT % 4 == 0, "Must be multiple of 4 due to data packing for GPU constant buffer."); +#if GLOBAL_SDF_DEBUG_CHUNKS +#include "Engine/Debug/DebugDraw.h" +#endif + +PACK_STRUCT(struct ObjectRasterizeData + { + Matrix WorldToVolume; // TODO: use 3x4 matrix + Matrix VolumeToWorld; // TODO: use 3x4 matrix + Vector3 VolumeToUVWMul; + float MipOffset; + Vector3 VolumeToUVWAdd; + float DecodeMul; + Vector3 VolumeLocalBoundsExtent; + float DecodeAdd; + }); + +PACK_STRUCT(struct Data + { + Vector3 ViewWorldPos; + float ViewNearPlane; + Vector3 Padding00; + float ViewFarPlane; + Vector4 ViewFrustumWorldRays[4]; + GlobalSignDistanceFieldPass::ConstantsData GlobalSDF; + }); + +PACK_STRUCT(struct ModelsRasterizeData + { + Int3 ChunkCoord; + float MaxDistance; + Vector3 CascadeCoordToPosMul; + int ObjectsCount; + Vector3 CascadeCoordToPosAdd; + int32 CascadeResolution; + float Padding0; + float CascadeVoxelSize; + int32 CascadeMipResolution; + int32 CascadeMipFactor; + uint32 Objects[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT]; + }); + +struct RasterizeModel +{ + Matrix WorldToVolume; + Matrix VolumeToWorld; + Vector3 VolumeToUVWMul; + Vector3 VolumeToUVWAdd; + Vector3 VolumeLocalBoundsExtent; + float MipOffset; + const ModelBase::SDFData* SDF; +}; + +struct RasterizeChunk +{ + uint16 ModelsCount; + uint16 HeightfieldsCount : 15; + uint16 Dynamic : 1; + uint16 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT]; + uint16 Heightfields[GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT]; + + RasterizeChunk() + { + ModelsCount = 0; + HeightfieldsCount = 0; + Dynamic = false; + } +}; + +constexpr int32 RasterizeChunkKeyHashResolution = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + +struct RasterizeChunkKey +{ + uint32 Hash; + int32 Layer; + Int3 Coord; + + FORCE_INLINE void NextLayer() + { + Layer++; + Hash += RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution; + } + + friend bool operator==(const RasterizeChunkKey& a, const RasterizeChunkKey& b) + { + return a.Hash == b.Hash && a.Coord == b.Coord && a.Layer == b.Layer; + } +}; + +uint32 GetHash(const RasterizeChunkKey& key) +{ + return key.Hash; +} + +struct CascadeData +{ + GPUTexture* Texture = nullptr; + GPUTexture* Mip = nullptr; + Vector3 Position; + float VoxelSize; + BoundingBox Bounds; + HashSet NonEmptyChunks; + HashSet StaticChunks; + + FORCE_INLINE void OnSceneRenderingDirty(const BoundingBox& objectBounds) + { + if (StaticChunks.IsEmpty() || !Bounds.Intersects(objectBounds)) + return; + + BoundingBox objectBoundsCascade; + const float objectMargin = VoxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN; + Vector3::Clamp(objectBounds.Minimum - objectMargin, Bounds.Minimum, Bounds.Maximum, objectBoundsCascade.Minimum); + Vector3::Subtract(objectBoundsCascade.Minimum, Bounds.Minimum, objectBoundsCascade.Minimum); + Vector3::Clamp(objectBounds.Maximum + objectMargin, Bounds.Minimum, Bounds.Maximum, objectBoundsCascade.Maximum); + Vector3::Subtract(objectBoundsCascade.Maximum, Bounds.Minimum, objectBoundsCascade.Maximum); + const float chunkSize = VoxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + const Int3 objectChunkMin(objectBoundsCascade.Minimum / chunkSize); + const Int3 objectChunkMax(objectBoundsCascade.Maximum / chunkSize); + + // Invalidate static chunks intersecting with dirty bounds + RasterizeChunkKey key; + key.Layer = 0; + for (key.Coord.Z = objectChunkMin.Z; key.Coord.Z <= objectChunkMax.Z; key.Coord.Z++) + { + for (key.Coord.Y = objectChunkMin.Y; key.Coord.Y <= objectChunkMax.Y; key.Coord.Y++) + { + for (key.Coord.X = objectChunkMin.X; key.Coord.X <= objectChunkMax.X; key.Coord.X++) + { + key.Hash = key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X; + StaticChunks.Remove(key); + } + } + } + } + + ~CascadeData() + { + RenderTargetPool::Release(Texture); + RenderTargetPool::Release(Mip); + } +}; + +class GlobalSignDistanceFieldCustomBuffer : public RenderBuffers::CustomBuffer, public ISceneRenderingListener +{ +public: + CascadeData Cascades[4]; + HashSet ObjectTypes; + HashSet SDFTextures; + GlobalSignDistanceFieldPass::BindingData Result; + + ~GlobalSignDistanceFieldCustomBuffer() + { + for (const auto& e : SDFTextures) + { + e.Item->Deleted.Unbind(this); + e.Item->ResidentMipsChanged.Unbind(this); + } + } + + void OnSDFTextureDeleted(ScriptingObject* object) + { + auto* texture = (GPUTexture*)object; + if (SDFTextures.Remove(texture)) + { + texture->Deleted.Unbind(this); + texture->ResidentMipsChanged.Unbind(this); + } + } + + void OnSDFTextureResidentMipsChanged(GPUTexture* texture) + { + // Stop tracking texture streaming once it gets fully loaded + if (texture->ResidentMipLevels() == texture->MipLevels()) + { + OnSDFTextureDeleted(texture); + + // Clear static chunks cache + for (auto& cascade : Cascades) + cascade.StaticChunks.Clear(); + } + } + + FORCE_INLINE void OnSceneRenderingDirty(const BoundingBox& objectBounds) + { + for (auto& cascade : Cascades) + cascade.OnSceneRenderingDirty(objectBounds); + } + + // [ISceneRenderingListener] + void OnSceneRenderingAddActor(Actor* a) override + { + if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) + { + OnSceneRenderingDirty(a->GetBox()); + } + } + + void OnSceneRenderingUpdateActor(Actor* a, const BoundingSphere& prevBounds) override + { + if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) + { + OnSceneRenderingDirty(BoundingBox::FromSphere(prevBounds)); + OnSceneRenderingDirty(a->GetBox()); + } + } + + void OnSceneRenderingRemoveActor(Actor* a) override + { + if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) + { + OnSceneRenderingDirty(a->GetBox()); + } + } + + void OnSceneRenderingClear(SceneRendering* scene) override + { + for (auto& cascade : Cascades) + cascade.StaticChunks.Clear(); + } +}; + +namespace +{ + Dictionary ChunksCache; +} + +String GlobalSignDistanceFieldPass::ToString() const +{ + return TEXT("GlobalSignDistanceFieldPass"); +} + +bool GlobalSignDistanceFieldPass::Init() +{ + // Check platform support + const auto device = GPUDevice::Instance; + _supported = device->GetFeatureLevel() >= FeatureLevel::SM5 && device->Limits.HasCompute && device->Limits.HasTypedUAVLoad + && FORMAT_FEATURES_ARE_SUPPORTED(device->GetFormatFeatures(GLOBAL_SDF_FORMAT).Support, FormatSupport::ShaderSample | FormatSupport::Texture3D); + return false; +} + +bool GlobalSignDistanceFieldPass::setupResources() +{ + if (!_supported) + return true; + + // Load shader + if (!_shader) + { + _shader = Content::LoadAsyncInternal(TEXT("Shaders/GlobalSignDistanceField")); + if (_shader == nullptr) + return true; +#if COMPILE_WITH_DEV_ENV + _shader.Get()->OnReloading.Bind(this); +#endif + } + if (!_shader->IsLoaded()) + return true; + + const auto device = GPUDevice::Instance; + const auto shader = _shader->GetShader(); + + // Check shader + _cb0 = shader->GetCB(0); + _cb1 = shader->GetCB(1); + if (!_cb0 || !_cb1) + return true; + _csRasterizeModel0 = shader->GetCS("CS_RasterizeModel", 0); + _csRasterizeModel1 = shader->GetCS("CS_RasterizeModel", 1); + _csRasterizeHeightfield = shader->GetCS("CS_RasterizeHeightfield"); + _csClearChunk = shader->GetCS("CS_ClearChunk"); + _csGenerateMip0 = shader->GetCS("CS_GenerateMip", 0); + _csGenerateMip1 = shader->GetCS("CS_GenerateMip", 1); + + // Init buffer + if (!_objectsBuffer) + _objectsBuffer = New(64u * (uint32)sizeof(ObjectRasterizeData), (uint32)sizeof(ObjectRasterizeData), false, TEXT("GlobalSDF.ObjectsBuffer")); + + // Create pipeline state + GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; + if (!_psDebug) + { + _psDebug = device->CreatePipelineState(); + psDesc.PS = shader->GetPS("PS_Debug"); + if (_psDebug->Init(psDesc)) + return true; + } + + return false; +} + +#if COMPILE_WITH_DEV_ENV + +void GlobalSignDistanceFieldPass::OnShaderReloading(Asset* obj) +{ + SAFE_DELETE_GPU_RESOURCE(_psDebug); + _csRasterizeModel0 = nullptr; + _csRasterizeModel1 = nullptr; + _csRasterizeHeightfield = nullptr; + _csClearChunk = nullptr; + _csGenerateMip0 = nullptr; + _csGenerateMip1 = nullptr; + _cb0 = nullptr; + _cb1 = nullptr; + invalidateResources(); +} + +#endif + +void GlobalSignDistanceFieldPass::Dispose() +{ + RendererPass::Dispose(); + + // Cleanup + SAFE_DELETE(_objectsBuffer); + _objectsTextures.Resize(0); + SAFE_DELETE_GPU_RESOURCE(_psDebug); + _shader = nullptr; + ChunksCache.Clear(); + ChunksCache.SetCapacity(0); +} + +bool GlobalSignDistanceFieldPass::Get(const RenderBuffers* buffers, BindingData& result) +{ + auto* sdfData = buffers ? buffers->FindCustomBuffer(TEXT("GlobalSignDistanceField")) : nullptr; + if (sdfData && sdfData->LastFrameUsed + 1 >= Engine::FrameCount) // Allow to use SDF from the previous frame (eg. particles in Editor using the Editor viewport in Game viewport - Game render task runs first) + { + result = sdfData->Result; + return false; + } + return true; +} + +bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContext* context, BindingData& result) +{ + // Skip if not supported + if (checkIfSkipPass()) + return true; + if (renderContext.List->Scenes.Count() == 0) + return true; + auto& sdfData = *renderContext.Buffers->GetCustomBuffer(TEXT("GlobalSignDistanceField")); + + // Skip if already done in the current frame + const auto currentFrame = Engine::FrameCount; + if (sdfData.LastFrameUsed == currentFrame) + { + result = sdfData.Result; + return false; + } + sdfData.LastFrameUsed = currentFrame; + PROFILE_GPU_CPU("Global SDF"); + + // TODO: configurable via graphics settings + const int32 resolution = 256; + const int32 resolutionMip = Math::DivideAndRoundUp(resolution, GLOBAL_SDF_RASTERIZE_MIP_FACTOR); + // TODO: configurable via postFx settings + const float distanceExtent = 2000.0f; + const float cascadesDistances[] = { distanceExtent, distanceExtent * 2.0f, distanceExtent * 4.0f, distanceExtent * 8.0f }; + + // Initialize buffers + auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); + bool updated = false; + for (auto& cascade : sdfData.Cascades) + { + GPUTexture*& texture = cascade.Texture; + if (texture && texture->Width() != desc.Width) + { + RenderTargetPool::Release(texture); + texture = nullptr; + } + if (!texture) + { + texture = RenderTargetPool::Get(desc); + if (!texture) + return true; + updated = true; + } + } + desc = GPUTextureDescription::New3D(resolutionMip, resolutionMip, resolutionMip, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); + for (auto& cascade : sdfData.Cascades) + { + GPUTexture*& texture = cascade.Mip; + if (texture && texture->Width() != desc.Width) + { + RenderTargetPool::Release(texture); + texture = nullptr; + } + if (!texture) + { + texture = RenderTargetPool::Get(desc); + if (!texture) + return true; + updated = true; + } + } + GPUTexture* tmpMip = nullptr; + if (updated) + { + PROFILE_GPU_CPU("Init"); + for (auto& cascade : sdfData.Cascades) + { + cascade.NonEmptyChunks.Clear(); + cascade.StaticChunks.Clear(); + context->ClearUA(cascade.Texture, Vector4::One); + context->ClearUA(cascade.Mip, Vector4::One); + } + LOG(Info, "Global SDF memory usage: {0} MB", (sdfData.Cascades[0].Texture->GetMemoryUsage() + sdfData.Cascades[0].Mip->GetMemoryUsage()) * ARRAY_COUNT(sdfData.Cascades) / 1024 / 1024); + } + for (SceneRendering* scene : renderContext.List->Scenes) + sdfData.ListenSceneRendering(scene); + + // Rasterize world geometry into Global SDF + renderContext.View.Pass = DrawPass::GlobalSDF; + uint32 viewMask = renderContext.View.RenderLayersMask; + const bool useCache = !updated; + static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_GROUP_SIZE == 0, "Invalid chunk size for Global SDF rasterization group size."); + const int32 rasterizeChunks = Math::CeilToInt((float)resolution / (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE); + auto& chunks = ChunksCache; + chunks.EnsureCapacity(rasterizeChunks * rasterizeChunks, false); + bool anyDraw = false; + const uint64 cascadeFrequencies[] = { 2, 3, 5, 11 }; + //const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 }; + for (int32 cascade = 0; cascade < 4; cascade++) + for (int32 cascadeIndex = 0; cascadeIndex < 4; cascadeIndex++) + { + // Reduce frequency of the updates + if (useCache && (Engine::FrameCount % cascadeFrequencies[cascadeIndex]) != 0) + continue; + auto& cascade = sdfData.Cascades[cascadeIndex]; + const float distance = cascadesDistances[cascadeIndex]; + const float maxDistance = distance * 2; + const float voxelSize = maxDistance / resolution; + const float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_MIP_FACTOR == 0, "Adjust chunk size to match the mip factor scale."); + const Vector3 center = Vector3::Floor(renderContext.View.Position / chunkSize) * chunkSize; + //const Vector3 center = Vector3::Zero; + BoundingBox cascadeBounds(center - distance, center + distance); + // TODO: add scene detail scale factor to PostFx settings (eg. to increase or decrease scene details and quality) + const float minObjectRadius = Math::Max(20.0f, voxelSize * 0.5f); // Skip too small objects for this cascade + GPUTextureView* cascadeView = cascade.Texture->ViewVolume(); + GPUTextureView* cascadeMipView = cascade.Mip->ViewVolume(); + + // Clear cascade before rasterization + { + PROFILE_CPU_NAMED("Clear"); + chunks.Clear(); + _objectsBuffer->Clear(); + _objectsTextures.Clear(); + } + + // Check if cascade center has been moved + if (!(useCache && Vector3::NearEqual(cascade.Position, center, voxelSize))) + { + // TODO: optimize for moving camera (copy sdf for cached chunks) + cascade.StaticChunks.Clear(); + } + cascade.Position = center; + cascade.VoxelSize = voxelSize; + cascade.Bounds = cascadeBounds; + + // Draw all objects from all scenes into the cascade + _objectsBufferCount = 0; + _voxelSize = voxelSize; + _cascadeBounds = cascadeBounds; + _cascadeIndex = cascadeIndex; + _sdfData = &sdfData; + { + PROFILE_CPU_NAMED("Draw"); + for (SceneRendering* scene : renderContext.List->Scenes) + { + for (const auto& e : scene->Actors) + { + if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::BoxIntersectsSphere(cascadeBounds, e.Bounds)) + { + e.Actor->Draw(renderContext); + } + } + } + } + + // Perform batched chunks rasterization + if (!anyDraw) + { + anyDraw = true; + context->ResetSR(); + tmpMip = RenderTargetPool::Get(desc); + if (!tmpMip) + return true; + } + ModelsRasterizeData data; + data.CascadeCoordToPosMul = cascadeBounds.GetSize() / resolution; + data.CascadeCoordToPosAdd = cascadeBounds.Minimum + voxelSize * 0.5f; + data.MaxDistance = maxDistance; + data.CascadeResolution = resolution; + data.CascadeMipResolution = resolutionMip; + data.CascadeMipFactor = GLOBAL_SDF_RASTERIZE_MIP_FACTOR; + data.CascadeVoxelSize = voxelSize; + context->BindUA(0, cascadeView); + context->BindCB(1, _cb1); + const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE; + bool anyChunkDispatch = false; + { + PROFILE_GPU_CPU("Clear Chunks"); + for (auto it = cascade.NonEmptyChunks.Begin(); it.IsNotEnd(); ++it) + { + auto& key = it->Item; + if (chunks.ContainsKey(key)) + continue; + + // Clear empty chunk + cascade.NonEmptyChunks.Remove(it); + data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + context->UpdateCB(_cb1, &data); + context->Dispatch(_csClearChunk, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + anyChunkDispatch = true; + // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches + } + } + // TODO: rasterize models into global sdf relative to the cascade origin to prevent fp issues on large worlds + { + PROFILE_GPU_CPU("Rasterize Chunks"); + + // Update static chunks + for (auto it = chunks.Begin(); it.IsNotEnd(); ++it) + { + auto& e = *it; + if (e.Key.Layer != 0) + continue; + if (e.Value.Dynamic) + { + // Remove static chunk with dynamic objects + cascade.StaticChunks.Remove(e.Key); + } + else if (cascade.StaticChunks.Contains(e.Key)) + { + // Skip updating static chunk + auto key = e.Key; + while (chunks.Remove(key)) + key.NextLayer(); + } + else + { + // Add to cache (render now but skip next frame) + cascade.StaticChunks.Add(e.Key); + } + } + + // Send models data to the GPU + if (chunks.Count() != 0) + { + PROFILE_GPU_CPU("Update Objects"); + _objectsBuffer->Flush(context); + } + context->BindSR(0, _objectsBuffer->GetBuffer() ? _objectsBuffer->GetBuffer()->View() : nullptr); + + // Rasterize non-empty chunks (first layer so can override existing chunk data) + for (const auto& e : chunks) + { + if (e.Key.Layer != 0) + continue; + auto& chunk = e.Value; + cascade.NonEmptyChunks.Add(e.Key); + + for (int32 i = 0; i < chunk.ModelsCount; i++) + { + auto objectIndex = chunk.Models[i]; + data.Objects[i] = objectIndex; + context->BindSR(i + 1, _objectsTextures[objectIndex]); + } + for (int32 i = chunk.ModelsCount; i < GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT; i++) + context->UnBindSR(i + 1); + data.ChunkCoord = e.Key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + data.ObjectsCount = chunk.ModelsCount; + context->UpdateCB(_cb1, &data); + auto cs = data.ObjectsCount != 0 ? _csRasterizeModel0 : _csClearChunk; // Terrain-only chunk can be quickly cleared + context->Dispatch(cs, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + anyChunkDispatch = true; + // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches (maybe cache per-shader write/read flags for all UAVs?) + + if (chunk.HeightfieldsCount != 0) + { + // Inject heightfield (additive) + for (int32 i = 0; i < chunk.HeightfieldsCount; i++) + { + auto objectIndex = chunk.Heightfields[i]; + data.Objects[i] = objectIndex; + context->BindSR(i + 1, _objectsTextures[objectIndex]); + } + for (int32 i = chunk.HeightfieldsCount; i < GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT; i++) + context->UnBindSR(i + 1); + data.ObjectsCount = chunk.HeightfieldsCount; + context->UpdateCB(_cb1, &data); + context->Dispatch(_csRasterizeHeightfield, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + } + +#if GLOBAL_SDF_DEBUG_CHUNKS + // Debug draw chunk bounds in world space with number of models in it + if (cascadeIndex + 1 == GLOBAL_SDF_DEBUG_CHUNKS) + { + int32 count = chunk.ModelsCount + chunk.HeightfieldsCount; + RasterizeChunkKey tmp = e.Key; + tmp.NextLayer(); + while (chunks.ContainsKey(tmp)) + { + count += chunks[tmp].ModelsCount + chunks[tmp].HeightfieldsCount; + tmp.NextLayer(); + } + Vector3 chunkMin = cascadeBounds.Minimum + Vector3(e.Key.Coord) * chunkSize; + BoundingBox chunkBounds(chunkMin, chunkMin + chunkSize); + DebugDraw::DrawWireBox(chunkBounds, Color::Red, 0, false); + DebugDraw::DrawText(StringUtils::ToString(count), chunkBounds.GetCenter(), Color::Red); + } +#endif + } + + // Rasterize non-empty chunks (additive layers so so need combine with existing chunk data) + for (const auto& e : chunks) + { + if (e.Key.Layer == 0) + continue; + auto& chunk = e.Value; + data.ChunkCoord = e.Key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + + if (chunk.ModelsCount != 0) + { + // Inject models (additive) + for (int32 i = 0; i < chunk.ModelsCount; i++) + { + auto objectIndex = chunk.Models[i]; + data.Objects[i] = objectIndex; + context->BindSR(i + 1, _objectsTextures[objectIndex]); + } + for (int32 i = chunk.ModelsCount; i < GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT; i++) + context->UnBindSR(i + 1); + data.ObjectsCount = chunk.ModelsCount; + context->UpdateCB(_cb1, &data); + context->Dispatch(_csRasterizeModel1, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + } + + if (chunk.HeightfieldsCount != 0) + { + // Inject heightfields (additive) + for (int32 i = 0; i < chunk.HeightfieldsCount; i++) + { + auto objectIndex = chunk.Heightfields[i]; + data.Objects[i] = objectIndex; + context->BindSR(i + 1, _objectsTextures[objectIndex]); + } + for (int32 i = chunk.HeightfieldsCount; i < GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT; i++) + context->UnBindSR(i + 1); + data.ObjectsCount = chunk.HeightfieldsCount; + context->UpdateCB(_cb1, &data); + context->Dispatch(_csRasterizeHeightfield, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + } + anyChunkDispatch = true; + } + } + + // Generate mip out of cascade (empty chunks have distance value 1 which is incorrect so mip will be used as a fallback - lower res) + if (updated || anyChunkDispatch) + { + PROFILE_GPU_CPU("Generate Mip"); + context->UpdateCB(_cb1, &data); + context->ResetUA(); + context->BindSR(0, cascadeView); + context->BindUA(0, cascadeMipView); + const int32 mipDispatchGroups = Math::DivideAndRoundUp(resolutionMip, GLOBAL_SDF_MIP_GROUP_SIZE); + int32 floodFillIterations = chunks.Count() == 0 ? 1 : GLOBAL_SDF_MIP_FLOODS; + context->Dispatch(_csGenerateMip0, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups); + context->UnBindSR(0); + GPUTextureView* tmpMipView = tmpMip->ViewVolume(); + for (int32 i = 1; i < floodFillIterations; i++) + { + context->ResetUA(); + context->BindSR(0, cascadeMipView); + context->BindUA(0, tmpMipView); + context->Dispatch(_csGenerateMip1, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups); + Swap(tmpMipView, cascadeMipView); + } + if (floodFillIterations % 2 == 0) + Swap(tmpMipView, cascadeMipView); + } + } + + RenderTargetPool::Release(tmpMip); + if (anyDraw) + { + context->UnBindCB(1); + context->ResetUA(); + context->FlushState(); + context->ResetSR(); + context->FlushState(); + } + + // Copy results + static_assert(ARRAY_COUNT(result.Cascades) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count."); + static_assert(ARRAY_COUNT(result.CascadeMips) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count."); + static_assert(ARRAY_COUNT(sdfData.Cascades) == 4, "Invalid cascades count."); + for (int32 cascadeIndex = 0; cascadeIndex < 4; cascadeIndex++) + { + auto& cascade = sdfData.Cascades[cascadeIndex]; + const float distance = cascadesDistances[cascadeIndex]; + const float maxDistance = distance * 2; + const float voxelSize = maxDistance / resolution; + const Vector3 center = cascade.Position; + result.Constants.CascadePosDistance[cascadeIndex] = Vector4(center, distance); + result.Constants.CascadeVoxelSize.Raw[cascadeIndex] = voxelSize; + result.Cascades[cascadeIndex] = cascade.Texture; + result.CascadeMips[cascadeIndex] = cascade.Mip; + } + result.Constants.Resolution = (float)resolution; + sdfData.Result = result; + return false; +} + +void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output) +{ + BindingData bindingData; + if (Render(renderContext, context, bindingData)) + { + context->Draw(output, renderContext.Buffers->GBuffer0); + return; + } + + PROFILE_GPU_CPU("Global SDF Debug"); + const Vector2 outputSize(output->Size()); + { + Data data; + data.ViewWorldPos = renderContext.View.Position; + data.ViewNearPlane = renderContext.View.Near; + data.ViewFarPlane = renderContext.View.Far; + for (int32 i = 0; i < 4; i++) + data.ViewFrustumWorldRays[i] = Vector4(renderContext.List->FrustumCornersWs[i + 4], 0); + data.GlobalSDF = bindingData.Constants; + context->UpdateCB(_cb0, &data); + context->BindCB(0, _cb0); + } + for (int32 i = 0; i < 4; i++) + { + context->BindSR(i, bindingData.Cascades[i]->ViewVolume()); + context->BindSR(i + 4, bindingData.CascadeMips[i]->ViewVolume()); + } + context->SetState(_psDebug); + context->SetRenderTarget(output->View()); + context->SetViewportAndScissors(outputSize.X, outputSize.Y); + context->DrawFullscreenTriangle(); +} + +void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds) +{ + if (!sdf.Texture || sdf.Texture->ResidentMipLevels() == 0) + return; + + // Setup object data + BoundingBox objectBoundsCascade; + const float objectMargin = _voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN; + Vector3::Clamp(objectBounds.Minimum - objectMargin, _cascadeBounds.Minimum, _cascadeBounds.Maximum, objectBoundsCascade.Minimum); + Vector3::Subtract(objectBoundsCascade.Minimum, _cascadeBounds.Minimum, objectBoundsCascade.Minimum); + Vector3::Clamp(objectBounds.Maximum + objectMargin, _cascadeBounds.Minimum, _cascadeBounds.Maximum, objectBoundsCascade.Maximum); + Vector3::Subtract(objectBoundsCascade.Maximum, _cascadeBounds.Minimum, objectBoundsCascade.Maximum); + const float chunkSize = _voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + Int3 objectChunkMin(objectBoundsCascade.Minimum / chunkSize); + Int3 objectChunkMax(objectBoundsCascade.Maximum / chunkSize); + Matrix worldToLocal, volumeToWorld; + Matrix::Invert(localToWorld, worldToLocal); + BoundingBox localVolumeBounds(sdf.LocalBoundsMin, sdf.LocalBoundsMax); + Vector3 volumeLocalBoundsExtent = localVolumeBounds.GetSize() * 0.5f; + Matrix worldToVolume = worldToLocal * Matrix::Translation(-(localVolumeBounds.Minimum + volumeLocalBoundsExtent)); + Matrix::Invert(worldToVolume, volumeToWorld); + + // Pick the SDF mip for the cascade + int32 mipLevelIndex = 1; + float worldUnitsPerVoxel = sdf.WorldUnitsPerVoxel * localToWorld.GetScaleVector().MaxValue() * 2; + while (_voxelSize > worldUnitsPerVoxel && mipLevelIndex < sdf.Texture->MipLevels()) + { + mipLevelIndex++; + worldUnitsPerVoxel *= 2.0f; + } + mipLevelIndex--; + + // Volume -> Local -> UVW + Vector3 volumeToUVWMul = sdf.LocalToUVWMul; + Vector3 volumeToUVWAdd = sdf.LocalToUVWAdd + (localVolumeBounds.Minimum + volumeLocalBoundsExtent) * sdf.LocalToUVWMul; + + // Add object data for the GPU buffer + uint16 objectIndex = _objectsBufferCount++; + ObjectRasterizeData objectData; + Matrix::Transpose(worldToVolume, objectData.WorldToVolume); + Matrix::Transpose(volumeToWorld, objectData.VolumeToWorld); + objectData.VolumeLocalBoundsExtent = volumeLocalBoundsExtent; + objectData.VolumeToUVWMul = volumeToUVWMul; + objectData.VolumeToUVWAdd = volumeToUVWAdd; + objectData.MipOffset = (float)mipLevelIndex; + objectData.DecodeMul = 2.0f * sdf.MaxDistance; + objectData.DecodeAdd = -sdf.MaxDistance; + _objectsBuffer->Write(objectData); + _objectsTextures.Add(sdf.Texture->ViewVolume()); + + // Inject object into the intersecting cascade chunks + _sdfData->ObjectTypes.Add(actor->GetTypeHandle()); + RasterizeChunkKey key; + auto& chunks = ChunksCache; + const bool dynamic = !GLOBAL_SDF_ACTOR_IS_STATIC(actor); + for (key.Coord.Z = objectChunkMin.Z; key.Coord.Z <= objectChunkMax.Z; key.Coord.Z++) + { + for (key.Coord.Y = objectChunkMin.Y; key.Coord.Y <= objectChunkMax.Y; key.Coord.Y++) + { + for (key.Coord.X = objectChunkMin.X; key.Coord.X <= objectChunkMax.X; key.Coord.X++) + { + key.Layer = 0; + key.Hash = key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X; + RasterizeChunk* chunk = &chunks[key]; + chunk->Dynamic |= dynamic; + + // Move to the next layer if chunk has overflown + while (chunk->ModelsCount == GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT) + { + key.NextLayer(); + chunk = &chunks[key]; + } + + chunk->Models[chunk->ModelsCount++] = objectIndex; + } + } + } + + // Track streaming for textures used in static chunks to invalidate cache + if (!dynamic && sdf.Texture->ResidentMipLevels() != sdf.Texture->MipLevels() && !_sdfData->SDFTextures.Contains(sdf.Texture)) + { + sdf.Texture->Deleted.Bind(_sdfData); + sdf.Texture->ResidentMipsChanged.Bind(_sdfData); + _sdfData->SDFTextures.Add(sdf.Texture); + } +} + +void GlobalSignDistanceFieldPass::RasterizeHeightfield(Actor* actor, GPUTexture* heightfield, const Matrix& localToWorld, const BoundingBox& objectBounds, const Vector4& localToUV) +{ + if (!heightfield || heightfield->ResidentMipLevels() == 0) + return; + + // Setup object data + BoundingBox objectBoundsCascade; + const float objectMargin = _voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN; + Vector3::Clamp(objectBounds.Minimum - objectMargin, _cascadeBounds.Minimum, _cascadeBounds.Maximum, objectBoundsCascade.Minimum); + Vector3::Subtract(objectBoundsCascade.Minimum, _cascadeBounds.Minimum, objectBoundsCascade.Minimum); + Vector3::Clamp(objectBounds.Maximum + objectMargin, _cascadeBounds.Minimum, _cascadeBounds.Maximum, objectBoundsCascade.Maximum); + Vector3::Subtract(objectBoundsCascade.Maximum, _cascadeBounds.Minimum, objectBoundsCascade.Maximum); + const float chunkSize = _voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + const Int3 objectChunkMin(objectBoundsCascade.Minimum / chunkSize); + const Int3 objectChunkMax(objectBoundsCascade.Maximum / chunkSize); + + // Add object data for the GPU buffer + uint16 objectIndex = _objectsBufferCount++; + ObjectRasterizeData objectData; + Matrix worldToLocal; + Matrix::Invert(localToWorld, worldToLocal); + Matrix::Transpose(worldToLocal, objectData.WorldToVolume); + Matrix::Transpose(localToWorld, objectData.VolumeToWorld); + objectData.VolumeToUVWMul = Vector3(localToUV.X, 1.0f, localToUV.Y); + objectData.VolumeToUVWAdd = Vector3(localToUV.Z, 0.0f, localToUV.W); + objectData.MipOffset = (float)_cascadeIndex * 0.5f; // Use lower-quality mip for far cascades + _objectsBuffer->Write(objectData); + _objectsTextures.Add(heightfield->View()); + + // Inject object into the intersecting cascade chunks + _sdfData->ObjectTypes.Add(actor->GetTypeHandle()); + RasterizeChunkKey key; + auto& chunks = ChunksCache; + const bool dynamic = !GLOBAL_SDF_ACTOR_IS_STATIC(actor); + for (key.Coord.Z = objectChunkMin.Z; key.Coord.Z <= objectChunkMax.Z; key.Coord.Z++) + { + for (key.Coord.Y = objectChunkMin.Y; key.Coord.Y <= objectChunkMax.Y; key.Coord.Y++) + { + for (key.Coord.X = objectChunkMin.X; key.Coord.X <= objectChunkMax.X; key.Coord.X++) + { + key.Layer = 0; + key.Hash = key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X; + RasterizeChunk* chunk = &chunks[key]; + chunk->Dynamic |= dynamic; + + // Move to the next layer if chunk has overflown + while (chunk->HeightfieldsCount == GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT) + { + key.NextLayer(); + chunk = &chunks[key]; + } + + chunk->Heightfields[chunk->HeightfieldsCount++] = objectIndex; + } + } + } + + // Track streaming for textures used in static chunks to invalidate cache + if (!dynamic && heightfield->ResidentMipLevels() != heightfield->MipLevels() && !_sdfData->SDFTextures.Contains(heightfield)) + { + heightfield->Deleted.Bind(_sdfData); + heightfield->ResidentMipsChanged.Bind(_sdfData); + _sdfData->SDFTextures.Add(heightfield); + } +} diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h new file mode 100644 index 000000000..5e1f0461a --- /dev/null +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -0,0 +1,97 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "RendererPass.h" + +/// +/// Global Sign Distance Field (SDF) rendering pass. Composites scene geometry into series of 3D volume textures that cover the world around the camera for global distance field sampling. +/// +class FLAXENGINE_API GlobalSignDistanceFieldPass : public RendererPass +{ +public: + // Constant buffer data for Global SDF access on a GPU. + PACK_STRUCT(struct ConstantsData + { + Vector4 CascadePosDistance[4]; + Vector4 CascadeVoxelSize; + Vector3 Padding; + float Resolution; + }); + + // Binding data for the GPU. + struct BindingData + { + GPUTexture* Cascades[4]; + GPUTexture* CascadeMips[4]; + ConstantsData Constants; + }; + +private: + bool _supported = false; + AssetReference _shader; + GPUPipelineState* _psDebug = nullptr; + GPUShaderProgramCS* _csRasterizeModel0 = nullptr; + GPUShaderProgramCS* _csRasterizeModel1 = nullptr; + GPUShaderProgramCS* _csRasterizeHeightfield = nullptr; + GPUShaderProgramCS* _csClearChunk = nullptr; + GPUShaderProgramCS* _csGenerateMip0 = nullptr; + GPUShaderProgramCS* _csGenerateMip1 = nullptr; + GPUConstantBuffer* _cb0 = nullptr; + GPUConstantBuffer* _cb1 = nullptr; + + // Rasterization cache + class DynamicStructuredBuffer* _objectsBuffer = nullptr; + Array _objectsTextures; + uint16 _objectsBufferCount; + int32 _cascadeIndex; + float _voxelSize; + BoundingBox _cascadeBounds; + class GlobalSignDistanceFieldCustomBuffer* _sdfData; + +public: + /// + /// Gets the Global SDF (only if enabled in Graphics Settings). + /// + /// The rendering context buffers. + /// The result Global SDF data for binding to the shaders. + /// True if there is no valid Global SDF rendered during this frame, otherwise false. + bool Get(const RenderBuffers* buffers, BindingData& result); + + /// + /// Renders the Global SDF. + /// + /// The rendering context. + /// The GPU context. + /// The result Global SDF data for binding to the shaders. + /// True if failed to render (platform doesn't support it, out of video memory, disabled feature or effect is not ready), otherwise false. + bool Render(RenderContext& renderContext, GPUContext* context, BindingData& result); + + /// + /// Renders the debug view. + /// + /// The rendering context. + /// The GPU context. + /// The output buffer. + void RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output); + + // Rasterize Model SDF into the Global SDF. Call it from actor Draw() method during DrawPass::GlobalSDF. + void RasterizeModelSDF(Actor* actor, const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds); + + void RasterizeHeightfield(Actor* actor, GPUTexture* heightfield, const Matrix& localToWorld, const BoundingBox& objectBounds, const Vector4& localToUV); + +private: +#if COMPILE_WITH_DEV_ENV + void OnShaderReloading(Asset* obj); +#endif + +public: + // [RendererPass] + String ToString() const override; + bool Init() override; + void Dispose() override; + +protected: + // [RendererPass] + bool setupResources() override; +}; diff --git a/Source/Engine/Renderer/LightPass.cpp b/Source/Engine/Renderer/LightPass.cpp index 373a3228c..c6ef07f51 100644 --- a/Source/Engine/Renderer/LightPass.cpp +++ b/Source/Engine/Renderer/LightPass.cpp @@ -141,27 +141,6 @@ bool LightPass::setupResources() return false; } -template -bool CanRenderShadow(RenderView& view, const T& light) -{ - bool result = false; - switch ((ShadowsCastingMode)light.ShadowsMode) - { - case ShadowsCastingMode::StaticOnly: - result = view.IsOfflinePass; - break; - case ShadowsCastingMode::DynamicOnly: - result = !view.IsOfflinePass; - break; - case ShadowsCastingMode::All: - result = true; - break; - default: - break; - } - return result && light.ShadowsStrength > ZeroTolerance; -} - void LightPass::Dispose() { // Base @@ -281,9 +260,11 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB // Set shadow mask context->BindSR(5, shadowMaskView); } + else + context->UnBindSR(5); // Pack light properties buffer - light.SetupLightData(&perLight.Light, view, renderShadow); + light.SetupLightData(&perLight.Light, renderShadow); Matrix::Transpose(wvp, perLight.WVP); if (useIES) { @@ -336,9 +317,11 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB // Set shadow mask context->BindSR(5, shadowMaskView); } + else + context->UnBindSR(5); // Pack light properties buffer - light.SetupLightData(&perLight.Light, view, renderShadow); + light.SetupLightData(&perLight.Light, renderShadow); Matrix::Transpose(wvp, perLight.WVP); if (useIES) { @@ -377,9 +360,11 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB // Set shadow mask context->BindSR(5, shadowMaskView); } + else + context->UnBindSR(5); // Pack light properties buffer - light.SetupLightData(&perLight.Light, view, renderShadow); + light.SetupLightData(&perLight.Light, renderShadow); // Calculate lighting context->UpdateCB(cb0, &perLight); @@ -413,7 +398,7 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB Matrix::Multiply(world, view.ViewProjection(), wvp); // Pack light properties buffer - light.SetupLightData(&perLight.Light, view, false); + light.SetupLightData(&perLight.Light, false); Matrix::Transpose(wvp, perLight.WVP); // Bind source image diff --git a/Source/Engine/Renderer/MotionBlurPass.cpp b/Source/Engine/Renderer/MotionBlurPass.cpp index d0ad68691..e00225225 100644 --- a/Source/Engine/Renderer/MotionBlurPass.cpp +++ b/Source/Engine/Renderer/MotionBlurPass.cpp @@ -245,7 +245,7 @@ void MotionBlurPass::RenderDebug(RenderContext& renderContext, GPUTextureView* f { auto context = GPUDevice::Instance->GetMainContext(); const auto motionVectors = renderContext.Buffers->MotionVectors; - if (!motionVectors->IsAllocated() || setupResources()) + if (!motionVectors->IsAllocated() || checkIfSkipPass()) { context->Draw(frame); return; diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index 5df281aae..09f4c21ec 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -41,7 +41,7 @@ namespace Array MemPool; } -void RendererDirectionalLightData::SetupLightData(LightData* data, const RenderView& view, bool useShadow) const +void RendererDirectionalLightData::SetupLightData(LightData* data, bool useShadow) const { data->SpotAngles.X = -2.0f; data->SpotAngles.Y = 1.0f; @@ -58,7 +58,7 @@ void RendererDirectionalLightData::SetupLightData(LightData* data, const RenderV data->RadiusInv = 0; } -void RendererSpotLightData::SetupLightData(LightData* data, const RenderView& view, bool useShadow) const +void RendererSpotLightData::SetupLightData(LightData* data, bool useShadow) const { data->SpotAngles.X = CosOuterCone; data->SpotAngles.Y = InvCosConeDifference; @@ -75,7 +75,7 @@ void RendererSpotLightData::SetupLightData(LightData* data, const RenderView& vi data->RadiusInv = 1.0f / Radius; } -void RendererPointLightData::SetupLightData(LightData* data, const RenderView& view, bool useShadow) const +void RendererPointLightData::SetupLightData(LightData* data, bool useShadow) const { data->SpotAngles.X = -2.0f; data->SpotAngles.Y = 1.0f; @@ -92,7 +92,7 @@ void RendererPointLightData::SetupLightData(LightData* data, const RenderView& v data->RadiusInv = 1.0f / Radius; } -void RendererSkyLightData::SetupLightData(LightData* data, const RenderView& view, bool useShadow) const +void RendererSkyLightData::SetupLightData(LightData* data, bool useShadow) const { data->SpotAngles.X = AdditiveColor.X; data->SpotAngles.Y = AdditiveColor.Y; @@ -392,6 +392,7 @@ void RenderList::Init(RenderContext& renderContext) void RenderList::Clear() { + Scenes.Clear(); DrawCalls.Clear(); BatchedDrawCalls.Clear(); for (auto& list : DrawCallsLists) @@ -783,6 +784,28 @@ DRAW: context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, drawCall.InstanceCount, 0, 0, drawCall.Draw.StartIndex); } } + if (list.Batches.IsEmpty() && list.Indices.Count() != 0) + { + // Draw calls list has nto been batched so execute draw calls separately + for (int32 j = 0; j < list.Indices.Count(); j++) + { + auto& drawCall = DrawCalls[list.Indices[j]]; + bindParams.FirstDrawCall = &drawCall; + drawCall.Material->Bind(bindParams); + + context->BindIB(drawCall.Geometry.IndexBuffer); + context->BindVB(ToSpan(drawCall.Geometry.VertexBuffers, 3), drawCall.Geometry.VertexBuffersOffsets); + + if (drawCall.InstanceCount == 0) + { + context->DrawIndexedInstancedIndirect(drawCall.Draw.IndirectArgsBuffer, drawCall.Draw.IndirectArgsOffset); + } + else + { + context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, drawCall.InstanceCount, 0, 0, drawCall.Draw.StartIndex); + } + } + } } } diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index 7ff47014b..589a1162c 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -11,6 +11,7 @@ enum class StaticFlags; class RenderBuffers; +class SceneRendering; class LightWithShadow; class IPostFxSettingsProvider; class CubeTexture; @@ -41,7 +42,7 @@ struct RendererDirectionalLightData float ContactShadowsLength; ShadowsCastingMode ShadowsMode; - void SetupLightData(LightData* data, const RenderView& view, bool useShadow) const; + void SetupLightData(LightData* data, bool useShadow) const; }; struct RendererSpotLightData @@ -79,7 +80,7 @@ struct RendererSpotLightData GPUTexture* IESTexture; - void SetupLightData(LightData* data, const RenderView& view, bool useShadow) const; + void SetupLightData(LightData* data, bool useShadow) const; }; struct RendererPointLightData @@ -113,7 +114,7 @@ struct RendererPointLightData GPUTexture* IESTexture; - void SetupLightData(LightData* data, const RenderView& view, bool useShadow) const; + void SetupLightData(LightData* data, bool useShadow) const; }; struct RendererSkyLightData @@ -131,7 +132,7 @@ struct RendererSkyLightData CubeTexture* Image; - void SetupLightData(LightData* data, const RenderView& view, bool useShadow) const; + void SetupLightData(LightData* data, bool useShadow) const; }; /// @@ -345,6 +346,11 @@ DECLARE_SCRIPTING_TYPE(RenderList); public: + /// + /// All scenes for rendering. + /// + Array Scenes; + /// /// Draw calls list (for all draw passes). /// diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 7758e824e..056870571 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -21,6 +21,8 @@ #include "VolumetricFogPass.h" #include "HistogramPass.h" #include "AtmospherePreCompute.h" +#include "GlobalSignDistanceFieldPass.h" +#include "GI/GlobalSurfaceAtlasPass.h" #include "Utils/MultiScaler.h" #include "Utils/BitonicSort.h" #include "AntiAliasing/FXAA.h" @@ -28,6 +30,7 @@ #include "AntiAliasing/SMAA.h" #include "Engine/Level/Actor.h" #include "Engine/Level/Level.h" +#include "Engine/Core/Config/GraphicsSettings.h" #if USE_EDITOR #include "Editor/Editor.h" #include "Editor/QuadOverdrawPass.h" @@ -80,6 +83,8 @@ bool RendererService::Init() PassList.Add(TAA::Instance()); PassList.Add(SMAA::Instance()); PassList.Add(HistogramPass::Instance()); + PassList.Add(GlobalSignDistanceFieldPass::Instance()); + PassList.Add(GlobalSurfaceAtlasPass::Instance()); #if USE_EDITOR PassList.Add(QuadOverdrawPass::Instance()); #endif @@ -287,6 +292,7 @@ void Renderer::DrawPostFxMaterial(GPUContext* context, const RenderContext& rend void RenderInner(SceneRenderTask* task, RenderContext& renderContext) { auto context = GPUDevice::Instance->GetMainContext(); + auto* graphicsSettings = GraphicsSettings::Get(); auto& view = renderContext.View; ASSERT(renderContext.Buffers && renderContext.Buffers->GetWidth() > 0); @@ -320,7 +326,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext) renderContext.List->SortDrawCalls(renderContext, false, DrawCallsListType::Distortion); // Get the light accumulation buffer - auto tempDesc = GPUTextureDescription::New2D(renderContext.Buffers->GetWidth(), renderContext.Buffers->GetHeight(), PixelFormat::R11G11B10_Float); + auto tempDesc = GPUTextureDescription::New2D(renderContext.Buffers->GetWidth(), renderContext.Buffers->GetHeight(), LIGHT_BUFFER_FORMAT); auto lightBuffer = RenderTargetPool::Get(tempDesc); #if USE_EDITOR @@ -336,11 +342,25 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext) } #endif + // Global SDF rendering (can be used by materials later on) + if (graphicsSettings->EnableGlobalSDF) + { + GlobalSignDistanceFieldPass::BindingData bindingData; + GlobalSignDistanceFieldPass::Instance()->Render(renderContext, context, bindingData); + } + // Fill GBuffer GBufferPass::Instance()->Fill(renderContext, lightBuffer->View()); - // Check if debug emissive light - if (renderContext.View.Mode == ViewMode::Emissive || renderContext.View.Mode == ViewMode::LightmapUVsDensity) + // Debug drawing + if (renderContext.View.Mode == ViewMode::GlobalSDF) + GlobalSignDistanceFieldPass::Instance()->RenderDebug(renderContext, context, lightBuffer); + else if (renderContext.View.Mode == ViewMode::GlobalSurfaceAtlas) + GlobalSurfaceAtlasPass::Instance()->RenderDebug(renderContext, context, lightBuffer); + if (renderContext.View.Mode == ViewMode::Emissive || + renderContext.View.Mode == ViewMode::LightmapUVsDensity || + renderContext.View.Mode == ViewMode::GlobalSurfaceAtlas || + renderContext.View.Mode == ViewMode::GlobalSDF) { context->ResetRenderTarget(); context->SetRenderTarget(task->GetOutputView()); diff --git a/Source/Engine/Renderer/RendererPass.h b/Source/Engine/Renderer/RendererPass.h index a40e273d6..3a4146ed1 100644 --- a/Source/Engine/Renderer/RendererPass.h +++ b/Source/Engine/Renderer/RendererPass.h @@ -37,7 +37,7 @@ public: /// Each render pass supports proper resources initialization and disposing. /// /// -class RendererPassBase : public Object +class FLAXENGINE_API RendererPassBase : public Object { protected: diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 05f8659c4..a90708b10 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -325,7 +325,7 @@ void ShadowsPass::RenderShadow(RenderContext& renderContext, RendererPointLightD // Setup shader data GBufferPass::SetInputs(view, sperLight.GBuffer); - light.SetupLightData(&sperLight.Light, view, true); + light.SetupLightData(&sperLight.Light, true); sperLight.LightShadow.ShadowMapSize = shadowMapsSizeCube; sperLight.LightShadow.Sharpness = light.ShadowsSharpness; sperLight.LightShadow.Fade = Math::Saturate(light.ShadowsStrength * fade); @@ -427,7 +427,7 @@ void ShadowsPass::RenderShadow(RenderContext& renderContext, RendererSpotLightDa // Setup shader data GBufferPass::SetInputs(view, sperLight.GBuffer); - light.SetupLightData(&sperLight.Light, view, true); + light.SetupLightData(&sperLight.Light, true); sperLight.LightShadow.ShadowMapSize = shadowMapsSizeCube; sperLight.LightShadow.Sharpness = light.ShadowsSharpness; sperLight.LightShadow.Fade = Math::Saturate(light.ShadowsStrength * fade); @@ -719,7 +719,7 @@ void ShadowsPass::RenderShadow(RenderContext& renderContext, RendererDirectional // Setup shader data GBufferPass::SetInputs(view, sperLight.GBuffer); - light.SetupLightData(&sperLight.Light, view, true); + light.SetupLightData(&sperLight.Light, true); sperLight.LightShadow.ShadowMapSize = shadowMapsSizeCSM; sperLight.LightShadow.Sharpness = light.ShadowsSharpness; sperLight.LightShadow.Fade = Math::Saturate(light.ShadowsStrength); diff --git a/Source/Engine/Renderer/ShadowsPass.h b/Source/Engine/Renderer/ShadowsPass.h index 1f9a0db5f..c17757fb4 100644 --- a/Source/Engine/Renderer/ShadowsPass.h +++ b/Source/Engine/Renderer/ShadowsPass.h @@ -14,6 +14,27 @@ /// #define SHADOWS_PASS_SS_RR_FORMAT PixelFormat::R11G11B10_Float +template +bool CanRenderShadow(RenderView& view, const T& light) +{ + bool result = false; + switch ((ShadowsCastingMode)light.ShadowsMode) + { + case ShadowsCastingMode::StaticOnly: + result = view.IsOfflinePass; + break; + case ShadowsCastingMode::DynamicOnly: + result = !view.IsOfflinePass; + break; + case ShadowsCastingMode::All: + result = true; + break; + default: + break; + } + return result && light.ShadowsStrength > ZeroTolerance; +} + /// /// Shadows rendering service. /// diff --git a/Source/Engine/Renderer/VolumetricFogPass.cpp b/Source/Engine/Renderer/VolumetricFogPass.cpp index 8ebce3600..61b544985 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.cpp +++ b/Source/Engine/Renderer/VolumetricFogPass.cpp @@ -306,7 +306,7 @@ void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUConte perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity; perLight.ViewSpaceBoundingSphere = Vector4(viewSpaceLightBoundsOrigin, bounds.Radius); Matrix::Transpose(view.Projection, perLight.ViewToVolumeClip); - light.SetupLightData(&perLight.LocalLight, view, true); + light.SetupLightData(&perLight.LocalLight, true); perLight.LocalLightShadow = shadow; // Upload data @@ -365,7 +365,7 @@ void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUConte perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity; perLight.ViewSpaceBoundingSphere = Vector4(viewSpaceLightBoundsOrigin, bounds.Radius); Matrix::Transpose(renderContext.View.Projection, perLight.ViewToVolumeClip); - light.SetupLightData(&perLight.LocalLight, renderContext.View, withShadow); + light.SetupLightData(&perLight.LocalLight, withShadow); // Upload data context->UpdateCB(cb1, &perLight); @@ -441,7 +441,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext) { const auto shadowPass = ShadowsPass::Instance(); const bool useShadow = dirLight.CastVolumetricShadow && shadowPass->LastDirLightIndex == dirLightIndex; - dirLight.SetupLightData(&_cache.Data.DirectionalLight, view, useShadow); + dirLight.SetupLightData(&_cache.Data.DirectionalLight, useShadow); _cache.Data.DirectionalLight.Color *= brightness; if (useShadow) { diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp index 101ffff5a..b599b7090 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp @@ -151,7 +151,8 @@ namespace case D3D_SIT_TEXTURE: case D3D_SIT_STRUCTURED: case D3D_SIT_BYTEADDRESS: - bindings.UsedSRsMask |= 1 << resDesc.BindPoint; + for (UINT shift = 0; shift < resDesc.BindCount; shift++) + bindings.UsedSRsMask |= 1 << (resDesc.BindPoint + shift); break; // Unordered Access @@ -161,7 +162,8 @@ namespace case D3D_SIT_UAV_APPEND_STRUCTURED: case D3D_SIT_UAV_CONSUME_STRUCTURED: case D3D_SIT_UAV_RWSTRUCTURED_WITH_COUNTER: - bindings.UsedUAsMask |= 1 << resDesc.BindPoint; + for (UINT shift = 0; shift < resDesc.BindCount; shift++) + bindings.UsedUAsMask |= 1 << (resDesc.BindPoint + shift); break; } } diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp index 17ffc61c4..790319507 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp @@ -381,13 +381,19 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD // Shader Resource case D3D_SIT_TEXTURE: - bindings.UsedSRsMask |= 1 << resDesc.BindPoint; - header.SrDimensions[resDesc.BindPoint] = resDesc.Dimension; + for (UINT shift = 0; shift < resDesc.BindCount; shift++) + { + bindings.UsedSRsMask |= 1 << (resDesc.BindPoint + shift); + header.SrDimensions[resDesc.BindPoint + shift] = resDesc.Dimension; + } break; case D3D_SIT_STRUCTURED: case D3D_SIT_BYTEADDRESS: - bindings.UsedSRsMask |= 1 << resDesc.BindPoint; - header.SrDimensions[resDesc.BindPoint] = D3D_SRV_DIMENSION_BUFFER; + for (UINT shift = 0; shift < resDesc.BindCount; shift++) + { + bindings.UsedSRsMask |= 1 << (resDesc.BindPoint + shift); + header.SrDimensions[resDesc.BindPoint + shift] = D3D_SRV_DIMENSION_BUFFER; + } break; // Unordered Access @@ -397,30 +403,10 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD case D3D_SIT_UAV_APPEND_STRUCTURED: case D3D_SIT_UAV_CONSUME_STRUCTURED: case D3D_SIT_UAV_RWSTRUCTURED_WITH_COUNTER: - bindings.UsedUAsMask |= 1 << resDesc.BindPoint; - switch (resDesc.Dimension) + for (UINT shift = 0; shift < resDesc.BindCount; shift++) { - case D3D_SRV_DIMENSION_BUFFER: - header.UaDimensions[resDesc.BindPoint] = 1; // D3D12_UAV_DIMENSION_BUFFER; - break; - case D3D_SRV_DIMENSION_TEXTURE1D: - header.UaDimensions[resDesc.BindPoint] = 2; // D3D12_UAV_DIMENSION_TEXTURE1D; - break; - case D3D_SRV_DIMENSION_TEXTURE1DARRAY: - header.UaDimensions[resDesc.BindPoint] = 3; // D3D12_UAV_DIMENSION_TEXTURE1DARRAY; - break; - case D3D_SRV_DIMENSION_TEXTURE2D: - header.UaDimensions[resDesc.BindPoint] = 4; // D3D12_UAV_DIMENSION_TEXTURE2D; - break; - case D3D_SRV_DIMENSION_TEXTURE2DARRAY: - header.UaDimensions[resDesc.BindPoint] = 5; // D3D12_UAV_DIMENSION_TEXTURE2DARRAY; - break; - case D3D_SRV_DIMENSION_TEXTURE3D: - header.UaDimensions[resDesc.BindPoint] = 8; // D3D12_UAV_DIMENSION_TEXTURE3D; - break; - default: - LOG(Error, "Unknown UAV resource {2} of type {0} at slot {1}", resDesc.Dimension, resDesc.BindPoint, String(resDesc.Name)); - return true; + bindings.UsedUAsMask |= 1 << (resDesc.BindPoint + shift); + header.UaDimensions[resDesc.BindPoint + shift] = (byte)resDesc.Dimension; // D3D_SRV_DIMENSION matches D3D12_UAV_DIMENSION } break; } diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp index 832301b61..afca3c4b5 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp @@ -9,6 +9,7 @@ #include "Engine/Platform/File.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Graphics/RenderTools.h" +#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Threading/Threading.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/MemoryWriteStream.h" @@ -24,7 +25,7 @@ namespace IncludedFiles { String Path; DateTime LastEditTime; - Array Source; + StringAnsi Source; }; CriticalSection Locker; @@ -80,7 +81,7 @@ bool ShaderCompiler::Compile(ShaderCompilationContext* context) _constantBuffers.Add({ meta->CB[i].Slot, false, 0 }); // [Output] Version number - output->WriteInt32(8); + output->WriteInt32(GPU_SHADER_CACHE_VERSION); // [Output] Additional data start const int32 additionalDataStartPos = output->GetPosition(); @@ -221,7 +222,7 @@ bool ShaderCompiler::GetIncludedFileSource(ShaderCompilationContext* context, co result = New(); result->Path = path; result->LastEditTime = FileSystem::GetFileLastEditTime(path); - if (File::ReadAllBytes(result->Path, result->Source)) + if (File::ReadAllText(result->Path, result->Source)) { LOG(Error, "Failed to load shader source file '{0}' included in '{1}' (path: '{2}')", String(includedFile), String(sourceFile), path); Delete(result); @@ -233,8 +234,8 @@ bool ShaderCompiler::GetIncludedFileSource(ShaderCompilationContext* context, co context->Includes.Add(path); // Copy to output - source = (const char*)result->Source.Get(); - sourceLength = result->Source.Count() - 1; + source = result->Source.Get(); + sourceLength = result->Source.Length(); return false; } diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp index 21d564668..9e56367a6 100644 --- a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp +++ b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp @@ -19,6 +19,8 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Content/Asset.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/Assets/Shader.h" #if USE_EDITOR #define COMPILE_WITH_ASSETS_IMPORTER 1 // Hack to use shaders importing in this module #include "Engine/ContentImporters/AssetsImportingManager.h" @@ -56,7 +58,6 @@ using namespace ShadersCompilationImpl; class ShadersCompilationService : public EngineService { public: - ShadersCompilationService() : EngineService(TEXT("Shaders Compilation Service"), -100) { @@ -250,6 +251,7 @@ namespace if (action == FileSystemAction::Delete) return; + // Get list of assets using this shader file Array toReload; { ScopeLock lock(ShaderIncludesMapLocker); @@ -260,6 +262,13 @@ namespace toReload = file->Value; } + // Add any shaders that failed to load (eg. due to error in included header) + for (Asset* asset : Content::GetAssets()) + { + if (asset->LastLoadFailed() && asset->Is() && !toReload.Contains(asset)) + toReload.Add(asset); + } + LOG(Info, "Shader include \'{0}\' has been modified.", path); // Wait a little so app that was editing the file (e.g. Visual Studio, Notepad++) has enough time to flush whole file change diff --git a/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp b/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp index 3de291d23..edf4a0887 100644 --- a/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp +++ b/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp @@ -33,7 +33,6 @@ namespace class Includer : public glslang::TShader::Includer { private: - ShaderCompilationContext* _context; IncludeResult* include(const char* headerName, const char* includerName, int depth) const @@ -46,14 +45,12 @@ private: } public: - Includer(ShaderCompilationContext* context) { _context = context; } public: - // [glslang::TShader::Include] IncludeResult* includeLocal(const char* headerName, const char* includerName, size_t inclusionDepth) override { @@ -210,6 +207,7 @@ struct Descriptor int32 Slot; int32 Binding; int32 Size; + int32 Count; SpirvShaderResourceBindingType BindingType; VkDescriptorType DescriptorType; SpirvShaderResourceType ResourceType; @@ -229,7 +227,7 @@ SpirvShaderResourceType GetTextureType(const glslang::TSampler& sampler) case glslang::EsdCube: return SpirvShaderResourceType::TextureCube; default: - CRASH; + CRASH; return SpirvShaderResourceType::Unknown; } } @@ -244,23 +242,13 @@ bool IsUavType(const glslang::TType& type) class DescriptorsCollector { public: - - int32 Images; - int32 Buffers; - int32 DescriptorsCount; + int32 Images = 0; + int32 Buffers = 0; + int32 TexelBuffers = 0; + int32 DescriptorsCount = 0; Descriptor Descriptors[SpirvShaderDescriptorInfo::MaxDescriptors]; public: - - DescriptorsCollector() - { - Images = 0; - Buffers = 0; - DescriptorsCount = 0; - } - -public: - Descriptor* Add(glslang::TVarEntryInfo& ent) { const glslang::TType& type = ent.symbol->getType(); @@ -374,28 +362,6 @@ public: } } - // Get the output info about shader uniforms usage - switch (descriptorType) - { - case VK_DESCRIPTOR_TYPE_SAMPLER: - case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: - case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: - case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: - Images++; - break; - case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: - case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: - case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: - case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: - case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC: - case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: - Buffers++; - break; - default: - LOG(Warning, "Invalid descriptor type {0} for symbol {1}.", (int32)descriptorType, String(name)); - return nullptr; - } - const auto index = DescriptorsCount++; auto& descriptor = Descriptors[index]; descriptor.Binding = index; @@ -405,6 +371,32 @@ public: descriptor.DescriptorType = descriptorType; descriptor.ResourceType = resourceType; descriptor.Name = name; + descriptor.Count = type.isSizedArray() ? type.getCumulativeArraySize() : 1; + + // Get the output info about shader uniforms usage + switch (descriptorType) + { + case VK_DESCRIPTOR_TYPE_SAMPLER: + case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: + case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: + case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: + Images += descriptor.Count; + break; + case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: + case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: + case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: + case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC: + Buffers += descriptor.Count; + break; + case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: + case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: + TexelBuffers += descriptor.Count; + break; + default: + LOG(Warning, "Invalid descriptor type {0} for symbol {1}.", (int32)descriptorType, String(name)); + return nullptr; + } + return &descriptor; } }; @@ -412,12 +404,10 @@ public: class MyIoMapResolver : public glslang::TDefaultIoResolverBase { private: - int32 _set; DescriptorsCollector* _collector; public: - MyIoMapResolver(int32 set, DescriptorsCollector* collector, const glslang::TIntermediate& intermediate) : TDefaultIoResolverBase(intermediate) , _set(set) @@ -426,7 +416,6 @@ public: } public: - // [glslang::TDefaultIoResolverBase] bool validateBinding(EShLanguage stage, glslang::TVarEntryInfo& ent) override { @@ -455,7 +444,9 @@ public: // Add resource const auto descriptor = _collector->Add(ent); if (descriptor) - return ent.newBinding = reserveSlot(_set, descriptor->Binding); + { + return ent.newBinding = reserveSlot(_set, descriptor->Binding, descriptor->Count); + } return ent.newBinding; } @@ -686,6 +677,7 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat // Process all descriptors header.DescriptorInfo.ImageInfosCount = descriptorsCollector.Images; header.DescriptorInfo.BufferInfosCount = descriptorsCollector.Buffers; + header.DescriptorInfo.TexelBufferViewsCount = descriptorsCollector.TexelBuffers; for (int32 i = 0; i < descriptorsCollector.DescriptorsCount; i++) { auto& descriptor = descriptorsCollector.Descriptors[i]; @@ -697,6 +689,7 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat d.BindingType = descriptor.BindingType; d.DescriptorType = descriptor.DescriptorType; d.ResourceType = descriptor.ResourceType; + d.Count = descriptor.Count; switch (descriptor.BindingType) { @@ -732,6 +725,7 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat // Mark as used and cache some data cc.IsUsed = true; cc.Size = descriptor.Size; + break; } } } diff --git a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp index 169036aa9..4293c267e 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp @@ -152,8 +152,7 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context) auto chunkSize = terrain->GetChunkSize(); const auto heightmap = patch->Heightmap.Get()->GetTexture(); - Matrix world; - chunk->GetWorld(&world); + const Matrix& world = chunk->GetWorld(); Matrix::Transpose(world, shaderData.WorldMatrix); shaderData.LightmapArea = chunk->Lightmap.UVsArea; shaderData.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; diff --git a/Source/Engine/Streaming/StreamableResource.h b/Source/Engine/Streaming/StreamableResource.h index 233663a66..b63a65356 100644 --- a/Source/Engine/Streaming/StreamableResource.h +++ b/Source/Engine/Streaming/StreamableResource.h @@ -2,6 +2,7 @@ #pragma once +#include "Engine/Core/Delegate.h" #include "Engine/Core/Collections/SamplesBuffer.h" class StreamingGroup; @@ -111,6 +112,11 @@ public: }; StreamingCache Streaming; + + /// + /// Event called when current resource residency gets changed (eg. model LOD or texture MIP gets loaded). Usually called from async thread. + /// + Action ResidencyChanged; /// /// Requests the streaming update for this resource during next streaming manager update. diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 4907d0f8a..452b4ca56 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -13,6 +13,8 @@ #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Renderer/GlobalSignDistanceFieldPass.h" +#include "Engine/Renderer/GI/GlobalSurfaceAtlasPass.h" Terrain::Terrain(const SpawnParams& params) : PhysicsColliderActor(params) @@ -47,7 +49,7 @@ void Terrain::UpdateBounds() } BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void Terrain::CacheNeighbors() @@ -505,6 +507,46 @@ void Terrain::Draw(RenderContext& renderContext) DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass); if (drawModes == DrawPass::None) return; + if (renderContext.View.Pass == DrawPass::GlobalSDF) + { + const float chunkSize = TERRAIN_UNITS_PER_VERTEX * (float)_chunkSize; + const float posToUV = 0.25f / chunkSize; + Vector4 localToUV(posToUV, posToUV, 0.0f, 0.0f); + Matrix localToWorld; + for (const TerrainPatch* patch : _patches) + { + if (!patch->Heightmap) + continue; + Transform patchTransform; + patchTransform.Translation = patch->_offset + Vector3(0, patch->_yOffset, 0); + patchTransform.Orientation = Quaternion::Identity; + patchTransform.Scale = Vector3(1.0f, patch->_yHeight, 1.0f); + patchTransform = _transform.LocalToWorld(patchTransform); + patchTransform.GetWorld(localToWorld); + GlobalSignDistanceFieldPass::Instance()->RasterizeHeightfield(this, patch->Heightmap->GetTexture(), localToWorld, patch->_bounds, localToUV); + } + return; + } + if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) + { + for (TerrainPatch* patch : _patches) + { + if (!patch->Heightmap) + continue; + Matrix worldToLocal; + BoundingSphere chunkSphere; + BoundingBox localBounds; + for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++) + { + TerrainChunk* chunk = &patch->Chunks[chunkIndex]; + Matrix::Invert(chunk->GetWorld(), worldToLocal); + BoundingBox::Transform(chunk->GetBounds(), worldToLocal, localBounds); + BoundingSphere::FromBox(chunk->GetBounds(), chunkSphere); + GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, chunk, chunkSphere, chunk->GetWorld(), localBounds, 1 << 2); + } + } + return; + } PROFILE_CPU(); @@ -518,7 +560,7 @@ void Terrain::Draw(RenderContext& renderContext) for (int32 patchIndex = 0; patchIndex < _patches.Count(); patchIndex++) { const auto patch = _patches[patchIndex]; - if (frustum.Intersects(patch->_bounds)) + if (renderContext.View.IsCullingDisabled || frustum.Intersects(patch->_bounds)) { // Skip if has no heightmap or it's not loaded if (patch->Heightmap == nullptr || patch->Heightmap->GetTexture()->ResidentMipLevels() == 0) @@ -529,7 +571,7 @@ void Terrain::Draw(RenderContext& renderContext) { auto chunk = &patch->Chunks[chunkIndex]; chunk->_cachedDrawLOD = 0; - if (frustum.Intersects(chunk->_bounds)) + if (renderContext.View.IsCullingDisabled || frustum.Intersects(chunk->_bounds)) { if (chunk->PrepareDraw(renderContext)) { @@ -557,17 +599,6 @@ void Terrain::Draw(RenderContext& renderContext) } } -void Terrain::DrawGeneric(RenderContext& renderContext) -{ - // Prevent issues if no BeginPlay was called - if (!IsDuringPlay()) - { - CacheNeighbors(); - } - - Draw(renderContext); -} - #if USE_EDITOR //#include "Engine/Debug/DebugDraw.h" @@ -738,6 +769,13 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie } #endif } + + // [Deprecated on 07.02.2022, expires on 07.02.2024] + if (modifier->EngineBuild <= 6330) + DrawModes |= DrawPass::GlobalSDF; + // [Deprecated on 27.04.2022, expires on 27.04.2024] + if (modifier->EngineBuild <= 6331) + DrawModes |= DrawPass::GlobalSurfaceAtlas; } RigidBody* Terrain::GetAttachedRigidBody() const @@ -748,7 +786,7 @@ RigidBody* Terrain::GetAttachedRigidBody() const void Terrain::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddGeometry(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if TERRAIN_USE_PHYSICS_DEBUG GetSceneRendering()->AddPhysicsDebug(this); #endif @@ -759,7 +797,7 @@ void Terrain::OnEnable() void Terrain::OnDisable() { - GetSceneRendering()->RemoveGeometry(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); #if TERRAIN_USE_PHYSICS_DEBUG GetSceneRendering()->RemovePhysicsDebug(this); #endif @@ -800,7 +838,7 @@ void Terrain::OnLayerChanged() UpdateLayerBits(); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void Terrain::OnActiveInTreeChanged() diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index 8a442582e..95a41bf22 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -441,7 +441,6 @@ public: // [PhysicsColliderActor] void Draw(RenderContext& renderContext) override; - void DrawGeneric(RenderContext& renderContext) override; #if USE_EDITOR void OnDebugDrawSelected() override; #endif diff --git a/Source/Engine/Terrain/TerrainChunk.h b/Source/Engine/Terrain/TerrainChunk.h index 611ff46a4..5764844fb 100644 --- a/Source/Engine/Terrain/TerrainChunk.h +++ b/Source/Engine/Terrain/TerrainChunk.h @@ -55,7 +55,6 @@ public: /// /// Gets the x coordinate. /// - /// The x position. FORCE_INLINE int32 GetX() const { return _x; @@ -64,7 +63,6 @@ public: /// /// Gets the z coordinate. /// - /// The z position. FORCE_INLINE int32 GetZ() const { return _z; @@ -73,7 +71,6 @@ public: /// /// Gets the patch. /// - /// The terrain patch, FORCE_INLINE TerrainPatch* GetPatch() const { return _patch; @@ -82,19 +79,17 @@ public: /// /// Gets the chunk world bounds. /// - /// The bounding box. FORCE_INLINE const BoundingBox& GetBounds() const { return _bounds; } /// - /// Gets the model world matrix transform. + /// Gets the chunk world matrix transform. /// - /// The result world matrix. - FORCE_INLINE void GetWorld(Matrix* world) const + FORCE_INLINE const Matrix& GetWorld() const { - *world = _world; + return _world; } /// @@ -109,7 +104,6 @@ public: /// /// Determines whether this chunk has valid lightmap data. /// - /// true if this chunk has valid lightmap data; otherwise, false. FORCE_INLINE bool HasLightmap() const { return Lightmap.TextureIndex != INVALID_INDEX; diff --git a/Source/Engine/Threading/JobSystem.cpp b/Source/Engine/Threading/JobSystem.cpp index de3d1c301..c16a1596d 100644 --- a/Source/Engine/Threading/JobSystem.cpp +++ b/Source/Engine/Threading/JobSystem.cpp @@ -212,6 +212,23 @@ int32 JobSystemThread::Run() #endif +void JobSystem::Execute(const Function& job, int32 jobCount) +{ + // TODO: disable async if called on job thread? or maybe Wait should handle waiting in job thread to do the processing? + if (jobCount > 1) + { + // Async + const int64 jobWaitHandle = Dispatch(job, jobCount); + Wait(jobWaitHandle); + } + else if (jobCount > 0) + { + // Sync + for (int32 i = 0; i < jobCount; i++) + job(i); + } +} + int64 JobSystem::Dispatch(const Function& job, int32 jobCount) { PROFILE_CPU(); diff --git a/Source/Engine/Threading/JobSystem.h b/Source/Engine/Threading/JobSystem.h index 2b6545156..e009e2f29 100644 --- a/Source/Engine/Threading/JobSystem.h +++ b/Source/Engine/Threading/JobSystem.h @@ -9,7 +9,14 @@ /// API_CLASS(Static) class FLAXENGINE_API JobSystem { -DECLARE_SCRIPTING_TYPE_MINIMAL(JobSystem); + DECLARE_SCRIPTING_TYPE_MINIMAL(JobSystem); + + /// + /// Executes the job (utility to call dispatch and wait for the end). + /// + /// The job. Argument is an index of the job execution. + /// The job executions count. + API_FUNCTION() static void Execute(const Function& job, int32 jobCount = 1); /// /// Dispatches the job for the execution. diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp index d36168d1a..def7d7ee8 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp @@ -8,7 +8,7 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value) { switch (node->TypeID) { - // Sample Layer + // Sample Layer case 1: { Guid id = (Guid)node->Values[0]; @@ -141,7 +141,7 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value) value = MaterialValue(VariantType::Void, varName); break; } - // Blend Linear + // Blend Linear case 2: case 5: case 8: @@ -215,8 +215,8 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value) #undef EAT_BOX break; } - // Pack Material Layer (old: without TessellationMultiplier, SubsurfaceColor and WorldDisplacement support) - // [Deprecated on 2018.10.01, expires on 2019.10.01] + // Pack Material Layer (old: without TessellationMultiplier, SubsurfaceColor and WorldDisplacement support) + // [Deprecated on 2018.10.01, expires on 2019.10.01] case 3: { // Create new layer @@ -263,8 +263,8 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value) break; } - // Unpack Material Layer - // Node type 4 -> [Deprecated on 2018.10.01, expires on 2019.10.01] + // Unpack Material Layer + // Node type 4 -> [Deprecated on 2018.10.01, expires on 2019.10.01] case 4: case 7: { @@ -284,7 +284,7 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value) } break; } - // Pack Material Layer + // Pack Material Layer case 6: { // Create new layer diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp index 5809cb267..65718c4b3 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp @@ -9,24 +9,24 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) { switch (node->TypeID) { - // World Position + // World Position case 2: value = Value(VariantType::Vector3, TEXT("input.WorldPosition.xyz")); break; - // View + // View case 3: { switch (box->ID) { - // Position + // Position case 0: value = Value(VariantType::Vector3, TEXT("ViewPos")); break; - // Direction + // Direction case 1: value = Value(VariantType::Vector3, TEXT("ViewDir")); break; - // Far Plane + // Far Plane case 2: value = Value(VariantType::Float, TEXT("ViewFar")); break; @@ -34,15 +34,15 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) } break; } - // Normal + // Normal case 4: value = getNormal; break; - // Camera Vector + // Camera Vector case 5: value = getCameraVector(node); break; - // Screen Position + // Screen Position case 6: { // Position @@ -54,11 +54,11 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) break; } - // Screen Size + // Screen Size case 7: value = Value(VariantType::Vector2, box->ID == 0 ? TEXT("ScreenSize.xy") : TEXT("ScreenSize.zw")); break; - // Custom code + // Custom code case 8: { // Skip if has no code @@ -127,15 +127,15 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = box->Cache; break; } - // Object Position + // Object Position case 9: value = Value(VariantType::Vector3, TEXT("GetObjectPosition(input)")); break; - // Two Sided Sign + // Two Sided Sign case 10: value = Value(VariantType::Float, TEXT("input.TwoSidedSign")); break; - // Camera Depth Fade + // Camera Depth Fade case 11: { auto faeLength = tryGetValue(node->GetBox(0), node->Values[0]).AsFloat(); @@ -152,40 +152,40 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = x5; break; } - // Vertex Color + // Vertex Color case 12: value = getVertexColor; _treeLayer->UsageFlags |= MaterialUsageFlags::UseVertexColor; break; - // Pre-skinned Local Position + // Pre-skinned Local Position case 13: value = _treeType == MaterialTreeType::VertexShader ? Value(VariantType::Vector3, TEXT("input.PreSkinnedPosition")) : Value::Zero; break; - // Pre-skinned Local Normal + // Pre-skinned Local Normal case 14: value = _treeType == MaterialTreeType::VertexShader ? Value(VariantType::Vector3, TEXT("input.PreSkinnedNormal")) : Value::Zero; break; - // Depth + // Depth case 15: value = writeLocal(VariantType::Float, TEXT("distance(ViewPos, input.WorldPosition)"), node); break; - // Tangent + // Tangent case 16: value = Value(VariantType::Vector3, TEXT("input.TBN[0]")); break; - // Bitangent + // Bitangent case 17: value = Value(VariantType::Vector3, TEXT("input.TBN[1]")); break; - // Camera Position + // Camera Position case 18: value = Value(VariantType::Vector3, TEXT("ViewPos")); break; - // Per Instance Random + // Per Instance Random case 19: value = Value(VariantType::Float, TEXT("GetPerInstanceRandom(input)")); break; - // Interpolate VS To PS + // Interpolate VS To PS case 20: { const auto input = node->GetBox(0); @@ -219,7 +219,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) _vsToPsInterpolants.Add(input); break; } - // Terrain Holes Mask + // Terrain Holes Mask case 21: { MaterialLayer* baseLayer = GetRootLayer(); @@ -229,7 +229,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = Value::One; break; } - // Terrain Layer Weight + // Terrain Layer Weight case 22: { MaterialLayer* baseLayer = GetRootLayer(); @@ -252,7 +252,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = Value(VariantType::Float, String::Format(TEXT("input.Layers[{0}][{1}]"), slotIndex, componentIndex)); break; } - // Depth Fade + // Depth Fade case 23: { // Calculate screen-space UVs @@ -278,7 +278,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = writeLocal(VariantType::Float, String::Format(TEXT("saturate({0} / {1})"), depthDiff.Value, fadeDistance.Value), node); break; } - // Material Function + // Material Function case 24: { // Load function asset @@ -333,11 +333,11 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) _graphStack.Pop(); break; } - // Object Size + // Object Size case 25: value = Value(VariantType::Vector3, TEXT("GetObjectSize(input)")); break; - // Blend Normals + // Blend Normals case 26: { const auto baseNormal = tryGetValue(node->GetBox(0), getNormalZero).AsVector3(); @@ -350,7 +350,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = writeLocal(ValueType::Vector3, text2, node); break; } - // Rotator + // Rotator case 27: { const auto uv = tryGetValue(node->GetBox(0), getUVs).AsVector2(); @@ -366,7 +366,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = writeLocal(ValueType::Vector2, String::Format(TEXT("{3} + float2(dot({0},{1}), dot({0},{2}))"), x1.Value, dotB1.Value, dotB2.Value, center.Value), node); break; } - // Sphere Mask + // Sphere Mask case 28: { const auto a = tryGetValue(node->GetBox(0), 0, Value::Zero); @@ -384,7 +384,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = writeLocal(ValueType::Float, String::Format(TEXT("{0} ? (1 - {1}) : {1}"), invert.Value, x2.Value), node); break; } - // Tiling & Offset + // Tiling & Offset case 29: { const auto uv = tryGetValue(node->GetBox(0), getUVs).AsVector2(); @@ -394,42 +394,42 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = writeLocal(ValueType::Vector2, String::Format(TEXT("{0} * {1} + {2}"), uv.Value, tiling.Value, offset.Value), node); break; } - // DDX + // DDX case 30: { const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); value = writeLocal(inValue.Type, String::Format(TEXT("ddx({0})"), inValue.Value), node); break; } - // DDY + // DDY case 31: { const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); value = writeLocal(inValue.Type, String::Format(TEXT("ddy({0})"), inValue.Value), node); break; } - // Sign + // Sign case 32: { const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); value = writeLocal(ValueType::Float, String::Format(TEXT("sign({0})"), inValue.Value), node); break; } - // Any + // Any case 33: { const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); value = writeLocal(ValueType::Bool, String::Format(TEXT("any({0})"), inValue.Value), node); break; } - // All + // All case 34: { const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); value = writeLocal(ValueType::Bool, String::Format(TEXT("all({0})"), inValue.Value), node); break; } - // Blackbody + // Blackbody case 35: { // Reference: Mitchell Charity, http://www.vendian.org/mncharity/dir3/blackbody/ @@ -451,7 +451,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = writeLocal(ValueType::Vector3, String::Format(TEXT("{1} < 1000.0f ? {0} * {1}/1000.0f : {0}"), color.Value, temperature.Value), node); break; } - // HSVToRGB + // HSVToRGB case 36: { const auto hsv = tryGetValue(node->GetBox(0), node->Values[0]).AsVector3(); @@ -463,7 +463,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = writeLocal(ValueType::Vector3, String::Format(TEXT("{1}.z * lerp(float3(1.0, 1.0, 1.0), {0}, {1}.y)"), x1.Value, color.Value), node); break; } - // RGBToHSV + // RGBToHSV case 37: { // Reference: Ian Taylor, https://www.chilliant.com/rgb2hsv.html @@ -474,8 +474,8 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) auto p = writeLocal(ValueType::Vector4, String::Format(TEXT("({0}.g < {0}.b) ? float4({0}.bg, -1.0f, 2.0f/3.0f) : float4({0}.gb, 0.0f, -1.0f/3.0f)"), rgb.Value), node); auto q = writeLocal(ValueType::Vector4, String::Format(TEXT("({0}.r < {1}.x) ? float4({1}.xyw, {0}.r) : float4({0}.r, {1}.yzx)"), rgb.Value, p.Value), node); auto c = writeLocal(ValueType::Float, String::Format(TEXT("{0}.x - min({0}.w, {0}.y)"), q.Value), node); - auto h = writeLocal(ValueType::Float , String::Format(TEXT("abs(({0}.w - {0}.y) / (6 * {1} + {2}) + {0}.z)"), q.Value, c.Value, epsilon.Value), node); - + auto h = writeLocal(ValueType::Float, String::Format(TEXT("abs(({0}.w - {0}.y) / (6 * {1} + {2}) + {0}.z)"), q.Value, c.Value, epsilon.Value), node); + auto hcv = writeLocal(ValueType::Vector3, String::Format(TEXT("float3({0}, {1}, {2}.x)"), h.Value, c.Value, q.Value), node); value = writeLocal(ValueType::Vector3, String::Format(TEXT("float3({0}.x * 360.0f, {0}.y / ({0}.z + {1}), {0}.z)"), hcv.Value, epsilon.Value), node); break; @@ -489,7 +489,7 @@ void MaterialGenerator::ProcessGroupFunction(Box* box, Node* node, Value& value) { switch (node->TypeID) { - // Function Input + // Function Input case 1: { // Find the function call diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Parameters.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Parameters.cpp index a19b9ea42..67bb3d84d 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Parameters.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Parameters.cpp @@ -8,7 +8,7 @@ void MaterialGenerator::ProcessGroupParameters(Box* box, Node* node, Value& valu { switch (node->TypeID) { - // Get + // Get case 1: { // Get parameter diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Particles.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Particles.cpp index 76c0d9fa9..ec3ea3777 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Particles.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Particles.cpp @@ -99,74 +99,74 @@ void MaterialGenerator::ProcessGroupParticles(Box* box, Node* node, Value& value switch (node->TypeID) { - // Particle Attribute + // Particle Attribute case 100: { value = AccessParticleAttribute(node, (StringView)node->Values[0], static_cast(node->Values[1].AsInt)); break; } - // Particle Attribute (by index) + // Particle Attribute (by index) case 303: { const auto particleIndex = Value::Cast(tryGetValue(node->GetBox(1), Value(VariantType::Uint, TEXT("input.ParticleIndex"))), VariantType::Uint); value = AccessParticleAttribute(node, (StringView)node->Values[0], static_cast(node->Values[1].AsInt), particleIndex.Value.Get()); break; } - // Particle Position + // Particle Position case 101: { value = AccessParticleAttribute(node, TEXT("Position"), ParticleAttributeValueTypes::Vector3, nullptr, ParticleAttributeSpace::LocalPosition); break; } - // Particle Lifetime + // Particle Lifetime case 102: { value = AccessParticleAttribute(node, TEXT("Lifetime"), ParticleAttributeValueTypes::Float); break; } - // Particle Age + // Particle Age case 103: { value = AccessParticleAttribute(node, TEXT("Age"), ParticleAttributeValueTypes::Float); break; } - // Particle Color + // Particle Color case 104: { value = AccessParticleAttribute(node, TEXT("Color"), ParticleAttributeValueTypes::Vector4); break; } - // Particle Velocity + // Particle Velocity case 105: { value = AccessParticleAttribute(node, TEXT("Velocity"), ParticleAttributeValueTypes::Vector3, nullptr, ParticleAttributeSpace::LocalDirection); break; } - // Particle Sprite Size + // Particle Sprite Size case 106: { value = AccessParticleAttribute(node, TEXT("SpriteSize"), ParticleAttributeValueTypes::Vector2); break; } - // Particle Mass + // Particle Mass case 107: { value = AccessParticleAttribute(node, TEXT("Mass"), ParticleAttributeValueTypes::Float); break; } - // Particle Rotation + // Particle Rotation case 108: { value = AccessParticleAttribute(node, TEXT("Rotation"), ParticleAttributeValueTypes::Vector3); break; } - // Particle Angular Velocity + // Particle Angular Velocity case 109: { value = AccessParticleAttribute(node, TEXT("AngularVelocity"), ParticleAttributeValueTypes::Vector3); break; } - // Particle Normalized Age + // Particle Normalized Age case 110: { const auto age = AccessParticleAttribute(node, TEXT("Age"), ParticleAttributeValueTypes::Float); @@ -174,7 +174,7 @@ void MaterialGenerator::ProcessGroupParticles(Box* box, Node* node, Value& value value = writeOperation2(node, age, lifetime, '/'); break; } - // Particle Radius + // Particle Radius case 111: { value = AccessParticleAttribute(node, TEXT("Radius"), ParticleAttributeValueTypes::Float); diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp index bb940b610..44b8805d0 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -8,7 +8,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) { switch (node->TypeID) { - // Texture + // Texture case 1: { // Check if texture has been selected @@ -28,11 +28,11 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) } break; } - // TexCoord + // TexCoord case 2: value = getUVs; break; - // Cube Texture + // Cube Texture case 3: { // Check if texture has been selected @@ -52,7 +52,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) } break; } - // Normal Map + // Normal Map case 4: { // Check if texture has been selected @@ -72,7 +72,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) } break; } - // Parallax Occlusion Mapping + // Parallax Occlusion Mapping case 5: { auto heightTextureBox = node->GetBox(4); @@ -163,7 +163,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) value = result; break; } - // Scene Texture + // Scene Texture case 6: { // Get texture type @@ -267,7 +267,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) } break; } - // Scene Color + // Scene Color case 7: { // Sample scene color texture @@ -275,13 +275,13 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) sampleTexture(node, value, box, ¶m); break; } - // Scene Depth + // Scene Depth case 8: { sampleSceneDepth(node, value, box); break; } - // Sample Texture + // Sample Texture case 9: { enum CommonSamplerType @@ -401,7 +401,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) value = textureBox->Cache; break; } - // Flipbook + // Flipbook case 10: { // Get input values @@ -420,6 +420,30 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) value = writeLocal(VariantType::Vector2, String::Format(TEXT("({3} + float2({0}, {1})) * {2}"), frameX.Value, frameY.Value, framesXYInv.Value, uv.Value), node); break; } + // Sample Global SDF + case 14: + { + auto param = findOrAddGlobalSDF(); + Value worldPosition = tryGetValue(node->GetBox(1), Value(VariantType::Vector3, TEXT("input.WorldPosition.xyz"))).Cast(VariantType::Vector3); + value = writeLocal(VariantType::Float, String::Format(TEXT("SampleGlobalSDF({0}, {0}_Tex, {1})"), param.ShaderName, worldPosition.Value), node); + _includes.Add(TEXT("./Flax/GlobalSignDistanceField.hlsl")); + break; + } + // Sample Global SDF Gradient + case 15: + { + auto gradientBox = node->GetBox(0); + auto distanceBox = node->GetBox(2); + auto param = findOrAddGlobalSDF(); + Value worldPosition = tryGetValue(node->GetBox(1), Value(VariantType::Vector3, TEXT("input.WorldPosition.xyz"))).Cast(VariantType::Vector3); + auto distance = writeLocal(VariantType::Float, node); + auto gradient = writeLocal(VariantType::Vector3, String::Format(TEXT("SampleGlobalSDFGradient({0}, {0}_Tex, {1}, {2})"), param.ShaderName, worldPosition.Value, distance.Value), node); + _includes.Add(TEXT("./Flax/GlobalSignDistanceField.hlsl")); + gradientBox->Cache = gradient; + distanceBox->Cache = distance; + value = box == gradientBox ? gradient : distance; + break; + } default: break; } diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Tools.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Tools.cpp index 1c5160593..21a9e6ec0 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Tools.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Tools.cpp @@ -8,7 +8,7 @@ void MaterialGenerator::ProcessGroupTools(Box* box, Node* node, Value& value) { switch (node->TypeID) { - // Fresnel + // Fresnel case 1: case 4: { @@ -33,7 +33,7 @@ void MaterialGenerator::ProcessGroupTools(Box* box, Node* node, Value& value) value = local6; break; } - // Desaturation + // Desaturation case 2: { // Get inputs @@ -46,13 +46,13 @@ void MaterialGenerator::ProcessGroupTools(Box* box, Node* node, Value& value) value = writeFunction3(node, input, dot, scale, TEXT("lerp"), VariantType::Vector3); break; } - // Time + // Time case 3: { value = getTime; break; } - // Panner + // Panner case 6: { // Get inputs @@ -68,7 +68,7 @@ void MaterialGenerator::ProcessGroupTools(Box* box, Node* node, Value& value) value = writeOperation2(node, uv, local1, '+'); break; } - // Linearize Depth + // Linearize Depth case 7: { // Get input diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index f1d5b5840..42b57c341 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -392,6 +392,21 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo // Update material usage based on material generator outputs materialInfo.UsageFlags = baseLayer->UsageFlags; + // Find all Custom Global Code nodes + Array> customGlobalCodeNodes; + Array> graphs; + _functions.GetValues(graphs); + for (MaterialLayer* layer : _layers) + graphs.Add(&layer->Graph); + for (Graph* graph : graphs) + { + for (const MaterialGraph::Node& node : graph->Nodes) + { + if (node.Type == GRAPH_NODE_MAKE_TYPE(1, 38) && (bool)node.Values[1]) + customGlobalCodeNodes.Add(&node); + } + } + #define WRITE_FEATURES(input) FeaturesLock.Lock(); for (auto f : features) _writer.Write(Features[f].Inputs[(int32)FeatureTemplateInputsMapping::input]); FeaturesLock.Unlock(); // Defines { @@ -408,6 +423,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo } WRITE_FEATURES(Defines); inputs[In_Defines] = _writer.ToString(); + WriteCustomGlobalCode(customGlobalCodeNodes, In_Defines); _writer.Clear(); } @@ -416,6 +432,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo for (auto& include : _includes) _writer.Write(TEXT("#include \"{0}\"\n"), include.Item); WRITE_FEATURES(Includes); + WriteCustomGlobalCode(customGlobalCodeNodes, In_Includes); inputs[In_Includes] = _writer.ToString(); _writer.Clear(); } @@ -425,6 +442,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo WRITE_FEATURES(Constants); if (_parameters.HasItems()) ShaderGraphUtilities::GenerateShaderConstantBuffer(_writer, _parameters); + WriteCustomGlobalCode(customGlobalCodeNodes, In_Constants); inputs[In_Constants] = _writer.ToString(); _writer.Clear(); } @@ -485,6 +503,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo return true; } } + WriteCustomGlobalCode(customGlobalCodeNodes, In_ShaderResources); inputs[In_ShaderResources] = _writer.ToString(); _writer.Clear(); } @@ -492,21 +511,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo // Utilities { WRITE_FEATURES(Utilities); - Array> graphs; - _functions.GetValues(graphs); - for (MaterialLayer* layer : _layers) - graphs.Add(&layer->Graph); - for (Graph* graph : graphs) - { - for (const MaterialGraph::Node& node : graph->Nodes) - { - if (node.Type == GRAPH_NODE_MAKE_TYPE(1, 38) && (bool)node.Values[1]) - { - // Custom Global Code - _writer.Write((StringView)node.Values[0]); - } - } - } + WriteCustomGlobalCode(customGlobalCodeNodes, In_Utilities); inputs[In_Utilities] = _writer.ToString(); _writer.Clear(); } @@ -514,6 +519,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo // Shaders { WRITE_FEATURES(Shaders); + WriteCustomGlobalCode(customGlobalCodeNodes, In_Shaders); inputs[In_Shaders] = _writer.ToString(); _writer.Clear(); } @@ -799,4 +805,17 @@ void MaterialGenerator::ProcessGroupMath(Box* box, Node* node, Value& value) } } +void MaterialGenerator::WriteCustomGlobalCode(const Array>& nodes, int32 templateInputsMapping) +{ + for (const MaterialGraph::Node* node : nodes) + { + if ((int32)node->Values[2] == templateInputsMapping) + { + _writer.Write(TEXT("\n")); + _writer.Write((StringView)node->Values[0]); + _writer.Write(TEXT("\n")); + } + } +} + #endif diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h index 48cd21cb4..9a8aa5290 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h @@ -205,6 +205,7 @@ private: MaterialValue AccessParticleAttribute(Node* caller, const StringView& name, ParticleAttributeValueTypes valueType, const Char* index = nullptr, ParticleAttributeSpace space = ParticleAttributeSpace::AsIs); void prepareLayer(MaterialLayer* layer, bool allowVisibleParams); + void WriteCustomGlobalCode(const Array>& nodes, int32 templateInputsMapping); public: diff --git a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp new file mode 100644 index 000000000..b6fe6fa9d --- /dev/null +++ b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp @@ -0,0 +1,557 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#if COMPILE_WITH_MODEL_TOOL + +#include "MeshAccelerationStructure.h" +#include "Engine/Core/Math/Math.h" +#include "Engine/Content/Assets/Model.h" +#include "Engine/Graphics/Models/ModelData.h" +#include "Engine/Profiler/ProfilerCPU.h" + +void MeshAccelerationStructure::BuildBVH(int32 node, int32 maxLeafSize, Array& scratch) +{ + auto& root = _bvh[node]; + ASSERT_LOW_LAYER(root.Leaf.IsLeaf); + if (root.Leaf.TriangleCount <= maxLeafSize) + return; + + // Spawn two leaves + const int32 childIndex = _bvh.Count(); + _bvh.AddDefault(2); + auto& left = _bvh.Get()[childIndex]; + auto& right = _bvh.Get()[childIndex + 1]; + left.Leaf.IsLeaf = 1; + right.Leaf.IsLeaf = 1; + left.Leaf.MeshIndex = root.Leaf.MeshIndex; + right.Leaf.MeshIndex = root.Leaf.MeshIndex; + + // Mid-point splitting based on the largest axis + Vector3 boundsSize; + root.Bounds.GetSize(boundsSize); + int32 axisCount = 0; + int32 axis = 0; +RETRY: + if (axisCount == 0) + { + // Pick the highest axis + axis = 0; + if (boundsSize.Y > boundsSize.X && boundsSize.Y >= boundsSize.Z) + axis = 1; + else if (boundsSize.Z > boundsSize.X) + axis = 2; + } + else if (axisCount == 3) + { + // Failed to split + _bvh.Resize(childIndex); + return; + } + else + { + // Go to the next axis + axis = (axis + 1) % 3; + } + const float midPoint = root.Bounds.Minimum.Raw[axis] + boundsSize.Raw[axis] * 0.5f; + const Mesh& meshData = _meshes[root.Leaf.MeshIndex]; + const Vector3* vb = meshData.VertexBuffer.Get(); + int32 indexStart = root.Leaf.TriangleIndex * 3; + int32 indexEnd = indexStart + root.Leaf.TriangleCount * 3; + left.Leaf.TriangleCount = 0; + right.Leaf.TriangleCount = 0; + if (meshData.Use16BitIndexBuffer) + { + struct Tri + { + uint16 I0, I1, I2; + }; + scratch.Resize(root.Leaf.TriangleCount * sizeof(Tri)); + auto dst = (Tri*)scratch.Get(); + auto ib16 = meshData.IndexBuffer.Get(); + for (int32 i = indexStart; i < indexEnd;) + { + const Tri tri = { ib16[i++], ib16[i++], ib16[i++] }; + const float v0 = vb[tri.I0].Raw[axis]; + const float v1 = vb[tri.I1].Raw[axis]; + const float v2 = vb[tri.I2].Raw[axis]; + const float centroid = (v0 + v1 + v2) * 0.333f; + if (centroid <= midPoint) + dst[left.Leaf.TriangleCount++] = tri; // Left + else + dst[root.Leaf.TriangleCount - ++right.Leaf.TriangleCount] = tri; // Right + } + Platform::MemoryCopy(ib16 + indexStart, dst, root.Leaf.TriangleCount * 3 * sizeof(uint16)); + if (left.Leaf.TriangleCount == 0 || right.Leaf.TriangleCount == 0) + { + axisCount++; + goto RETRY; + } + + left.Bounds = BoundingBox(vb[dst[0].I0]); + indexStart = 0; + indexEnd = left.Leaf.TriangleCount * 3; + for (int32 i = indexStart; i < indexEnd; i++) + left.Bounds.Merge(vb[((uint16*)scratch.Get())[i]]); + + right.Bounds = BoundingBox(vb[dst[root.Leaf.TriangleCount - 1].I0]); + indexStart = left.Leaf.TriangleCount; + indexEnd = root.Leaf.TriangleCount * 3; + for (int32 i = indexStart; i < indexEnd; i++) + right.Bounds.Merge(vb[((uint16*)scratch.Get())[i]]); + } + else + { + struct Tri + { + uint32 I0, I1, I2; + }; + scratch.Resize(root.Leaf.TriangleCount * sizeof(Tri)); + auto dst = (Tri*)scratch.Get(); + auto ib32 = meshData.IndexBuffer.Get(); + for (int32 i = indexStart; i < indexEnd;) + { + const Tri tri = { ib32[i++], ib32[i++], ib32[i++] }; + const float v0 = vb[tri.I0].Raw[axis]; + const float v1 = vb[tri.I1].Raw[axis]; + const float v2 = vb[tri.I2].Raw[axis]; + const float centroid = (v0 + v1 + v2) * 0.333f; + if (centroid <= midPoint) + dst[left.Leaf.TriangleCount++] = tri; // Left + else + dst[root.Leaf.TriangleCount - ++right.Leaf.TriangleCount] = tri; // Right + } + Platform::MemoryCopy(ib32 + indexStart, dst, root.Leaf.TriangleCount * 3 * sizeof(uint32)); + if (left.Leaf.TriangleCount == 0 || right.Leaf.TriangleCount == 0) + { + axisCount++; + goto RETRY; + } + + left.Bounds = BoundingBox(vb[dst[0].I0]); + indexStart = 0; + indexEnd = left.Leaf.TriangleCount * 3; + for (int32 i = indexStart; i < indexEnd; i++) + left.Bounds.Merge(vb[((uint32*)scratch.Get())[i]]); + + right.Bounds = BoundingBox(vb[dst[root.Leaf.TriangleCount - 1].I0]); + indexStart = left.Leaf.TriangleCount; + indexEnd = root.Leaf.TriangleCount * 3; + for (int32 i = indexStart; i < indexEnd; i++) + right.Bounds.Merge(vb[((uint32*)scratch.Get())[i]]); + } + ASSERT_LOW_LAYER(left.Leaf.TriangleCount + right.Leaf.TriangleCount == root.Leaf.TriangleCount); + left.Leaf.TriangleIndex = root.Leaf.TriangleIndex; + right.Leaf.TriangleIndex = left.Leaf.TriangleIndex + left.Leaf.TriangleCount; + + // Convert into a node + root.Node.IsLeaf = 0; + root.Node.ChildIndex = childIndex; + root.Node.ChildrenCount = 2; + + // Split children + BuildBVH(childIndex, maxLeafSize, scratch); + BuildBVH(childIndex + 1, maxLeafSize, scratch); +} + +bool MeshAccelerationStructure::PointQueryBVH(int32 node, const Vector3& point, float& hitDistance, Vector3& hitPoint, Triangle& hitTriangle) const +{ + const auto& root = _bvh[node]; + bool hit = false; + if (root.Leaf.IsLeaf) + { + // Find closest triangle + Vector3 p; + const Mesh& meshData = _meshes[root.Leaf.MeshIndex]; + const Vector3* vb = meshData.VertexBuffer.Get(); + const int32 indexStart = root.Leaf.TriangleIndex * 3; + const int32 indexEnd = indexStart + root.Leaf.TriangleCount * 3; + if (meshData.Use16BitIndexBuffer) + { + const uint16* ib16 = meshData.IndexBuffer.Get(); + for (int32 i = indexStart; i < indexEnd;) + { + Vector3 v0 = vb[ib16[i++]]; + Vector3 v1 = vb[ib16[i++]]; + Vector3 v2 = vb[ib16[i++]]; + CollisionsHelper::ClosestPointPointTriangle(point, v0, v1, v2, p); + const float distance = Vector3::Distance(point, p); + if (distance < hitDistance) + { + hitDistance = distance; + hitPoint = p; + hitTriangle = Triangle(v0, v1, v2); + hit = true; + } + } + } + else + { + const uint32* ib32 = meshData.IndexBuffer.Get(); + for (int32 i = indexStart; i < indexEnd;) + { + Vector3 v0 = vb[ib32[i++]]; + Vector3 v1 = vb[ib32[i++]]; + Vector3 v2 = vb[ib32[i++]]; + CollisionsHelper::ClosestPointPointTriangle(point, v0, v1, v2, p); + const float distance = Vector3::Distance(point, p); + if (distance < hitDistance) + { + hitDistance = distance; + hitPoint = p; + hitTriangle = Triangle(v0, v1, v2); + hit = true; + } + } + } + } + else + { + // Check all nested nodes + for (uint32 i = 0; i < root.Node.ChildrenCount; i++) + { + const int32 index = root.Node.ChildIndex + i; + if (_bvh[index].Bounds.Distance(point) >= hitDistance) + continue; + if (PointQueryBVH(index, point, hitDistance, hitPoint, hitTriangle)) + hit = true; + } + } + return hit; +} + +bool MeshAccelerationStructure::RayCastBVH(int32 node, const Ray& ray, float& hitDistance, Vector3& hitNormal, Triangle& hitTriangle) const +{ + const auto& root = _bvh[node]; + if (!root.Bounds.Intersects(ray)) + return false; + Vector3 normal; + float distance; + bool hit = false; + if (root.Leaf.IsLeaf) + { + // Ray cast along triangles in the leaf + const Mesh& meshData = _meshes[root.Leaf.MeshIndex]; + const Vector3* vb = meshData.VertexBuffer.Get(); + const int32 indexStart = root.Leaf.TriangleIndex * 3; + const int32 indexEnd = indexStart + root.Leaf.TriangleCount * 3; + if (meshData.Use16BitIndexBuffer) + { + const uint16* ib16 = meshData.IndexBuffer.Get(); + for (int32 i = indexStart; i < indexEnd;) + { + Vector3 v0 = vb[ib16[i++]]; + Vector3 v1 = vb[ib16[i++]]; + Vector3 v2 = vb[ib16[i++]]; + if (CollisionsHelper::RayIntersectsTriangle(ray, v0, v1, v2, distance, normal) && distance < hitDistance) + { + hitDistance = distance; + hitNormal = normal; + hitTriangle = Triangle(v0, v1, v2); + hit = true; + } + } + } + else + { + const uint32* ib32 = meshData.IndexBuffer.Get(); + for (int32 i = indexStart; i < indexEnd;) + { + Vector3 v0 = vb[ib32[i++]]; + Vector3 v1 = vb[ib32[i++]]; + Vector3 v2 = vb[ib32[i++]]; + if (CollisionsHelper::RayIntersectsTriangle(ray, v0, v1, v2, distance, normal) && distance < hitDistance) + { + hitDistance = distance; + hitNormal = normal; + hitTriangle = Triangle(v0, v1, v2); + hit = true; + } + } + } + } + else + { + // Ray cast all child nodes + Triangle triangle; + for (uint32 i = 0; i < root.Node.ChildrenCount; i++) + { + const int32 index = root.Node.ChildIndex + i; + distance = hitDistance; + if (RayCastBVH(index, ray, distance, normal, triangle) && distance < hitDistance) + { + hitDistance = distance; + hitNormal = normal; + hitTriangle = triangle; + hit = true; + } + } + } + return hit; +} + +void MeshAccelerationStructure::Add(Model* model, int32 lodIndex) +{ + PROFILE_CPU(); + lodIndex = Math::Clamp(lodIndex, model->HighestResidentLODIndex(), model->LODs.Count() - 1); + ModelLOD& lod = model->LODs[lodIndex]; + const int32 meshesStart = _meshes.Count(); + _meshes.AddDefault(lod.Meshes.Count()); + bool failed = false; + for (int32 i = 0; i < lod.Meshes.Count(); i++) + { + auto& mesh = lod.Meshes[i]; + auto& meshData = _meshes[meshesStart + i]; + if (model->IsVirtual()) + { + meshData.Indices = mesh.GetTriangleCount() * 3; + meshData.Vertices = mesh.GetVertexCount(); + failed |= mesh.DownloadDataGPU(MeshBufferType::Index, meshData.IndexBuffer); + failed |= mesh.DownloadDataGPU(MeshBufferType::Vertex0, meshData.VertexBuffer); + } + else + { + failed |= mesh.DownloadDataCPU(MeshBufferType::Index, meshData.IndexBuffer, meshData.Indices); + failed |= mesh.DownloadDataCPU(MeshBufferType::Vertex0, meshData.VertexBuffer, meshData.Vertices); + } + if (failed) + { + _meshes.Resize(meshesStart); + return; + } + if (!meshData.IndexBuffer.IsAllocated() && meshData.IndexBuffer.Length() != 0) + { + // BVH nodes modifies index buffer (sorts data in-place) so clone it + meshData.IndexBuffer.Copy(meshData.IndexBuffer.Get(), meshData.IndexBuffer.Length()); + } + meshData.Use16BitIndexBuffer = mesh.Use16BitIndexBuffer(); + meshData.Bounds = mesh.GetBox(); + } +} + +void MeshAccelerationStructure::Add(ModelData* modelData, int32 lodIndex, bool copy) +{ + PROFILE_CPU(); + lodIndex = Math::Clamp(lodIndex, 0, modelData->LODs.Count() - 1); + ModelLodData& lod = modelData->LODs[lodIndex]; + const int32 meshesStart = _meshes.Count(); + _meshes.AddDefault(lod.Meshes.Count()); + for (int32 i = 0; i < lod.Meshes.Count(); i++) + { + MeshData* mesh = lod.Meshes[i]; + auto& meshData = _meshes[meshesStart + i]; + meshData.Indices = mesh->Indices.Count(); + meshData.Vertices = mesh->Positions.Count(); + if (copy) + { + meshData.IndexBuffer.Copy((const byte*)mesh->Indices.Get(), meshData.Indices * sizeof(uint32)); + meshData.VertexBuffer.Copy((const byte*)mesh->Positions.Get(), meshData.Vertices * sizeof(Vector3)); + } + else + { + meshData.IndexBuffer.Link((const byte*)mesh->Indices.Get(), meshData.Indices * sizeof(uint32)); + meshData.VertexBuffer.Link((const byte*)mesh->Positions.Get(), meshData.Vertices * sizeof(Vector3)); + } + meshData.Use16BitIndexBuffer = false; + mesh->CalculateBox(meshData.Bounds); + } +} + +void MeshAccelerationStructure::Add(Vector3* vb, int32 vertices, void* ib, int32 indices, bool use16BitIndex, bool copy) +{ + auto& meshData = _meshes.AddOne(); + if (copy) + { + meshData.VertexBuffer.Copy((const byte*)vb, vertices * sizeof(Vector3)); + } + else + { + meshData.VertexBuffer.Link((const byte*)vb, vertices * sizeof(Vector3)); + } + meshData.IndexBuffer.Copy((const byte*)ib, indices * (use16BitIndex ? sizeof(uint16) : sizeof(uint32))); + meshData.Vertices = vertices; + meshData.Indices = indices; + meshData.Use16BitIndexBuffer = use16BitIndex; +} + +void MeshAccelerationStructure::BuildBVH(int32 maxLeafSize) +{ + if (_meshes.Count() == 0) + return; + PROFILE_CPU(); + + // Estimate memory usage + int32 trianglesCount = 0; + for (const Mesh& meshData : _meshes) + trianglesCount += meshData.Indices / 3; + _bvh.Clear(); + _bvh.EnsureCapacity(trianglesCount / maxLeafSize); + + // Init with the root node and all meshes as leaves + auto& root = _bvh.AddOne(); + root.Node.IsLeaf = 0; + root.Node.ChildIndex = 1; + root.Node.ChildrenCount = _meshes.Count(); + root.Bounds = _meshes[0].Bounds; + for (int32 i = 0; i < _meshes.Count(); i++) + { + const Mesh& meshData = _meshes[i]; + auto& child = _bvh.AddOne(); + child.Leaf.IsLeaf = 1; + child.Leaf.MeshIndex = i; + child.Leaf.TriangleIndex = 0; + child.Leaf.TriangleCount = meshData.Indices / 3; + child.Bounds = meshData.Bounds; + BoundingBox::Merge(root.Bounds, meshData.Bounds, root.Bounds); + } + + // Sub-divide mesh nodes into smaller leaves + Array scratch; + for (int32 i = 0; i < _meshes.Count(); i++) + BuildBVH(i + 1, maxLeafSize, scratch); +} + +bool MeshAccelerationStructure::PointQuery(const Vector3& point, float& hitDistance, Vector3& hitPoint, Triangle& hitTriangle, float maxDistance) const +{ + hitDistance = maxDistance >= MAX_float ? maxDistance : maxDistance * maxDistance; + bool hit = false; + + // BVH + if (_bvh.Count() != 0) + { + Array> stack; + stack.Push(0); + while (stack.HasItems()) + { + const int32 node = stack.Pop(); + auto& root = _bvh[node]; + + // Skip too far nodes + if (root.Bounds.Distance(point) >= hitDistance) + continue; + + if (root.Leaf.IsLeaf) + { + // Check this leaf + hit |= PointQueryBVH(node, point, hitDistance, hitPoint, hitTriangle); + } + else + { + // Check this node children + for (uint32 i = 0; i < root.Node.ChildrenCount; i++) + stack.Push(root.Node.ChildIndex + i); + } + } + //hit = PointQueryBVH(0, point, hitDistance, hitPoint, hitTriangle); + return hit; + } + + // Brute-force + { + Vector3 p; + for (const Mesh& meshData : _meshes) + { + const Vector3* vb = meshData.VertexBuffer.Get(); + if (meshData.Use16BitIndexBuffer) + { + const uint16* ib16 = meshData.IndexBuffer.Get(); + for (int32 i = 0; i < meshData.Indices;) + { + Vector3 v0 = vb[ib16[i++]]; + Vector3 v1 = vb[ib16[i++]]; + Vector3 v2 = vb[ib16[i++]]; + CollisionsHelper::ClosestPointPointTriangle(point, v0, v1, v2, p); + const float distance = Vector3::DistanceSquared(point, p); + if (distance < hitDistance) + { + hitDistance = distance; + hitPoint = p; + hitTriangle = Triangle(v0, v1, v2); + hit = true; + } + } + } + else + { + const uint32* ib32 = meshData.IndexBuffer.Get(); + for (int32 i = 0; i < meshData.Indices;) + { + Vector3 v0 = vb[ib32[i++]]; + Vector3 v1 = vb[ib32[i++]]; + Vector3 v2 = vb[ib32[i++]]; + CollisionsHelper::ClosestPointPointTriangle(point, v0, v1, v2, p); + const float distance = Vector3::DistanceSquared(point, p); + if (distance < hitDistance) + { + hitDistance = distance; + hitPoint = p; + hitTriangle = Triangle(v0, v1, v2); + hit = true; + } + } + } + } + if (hit) + hitDistance = Math::Sqrt(hitDistance); + return hit; + } +} + +bool MeshAccelerationStructure::RayCast(const Ray& ray, float& hitDistance, Vector3& hitNormal, Triangle& hitTriangle, float maxDistance) const +{ + hitDistance = maxDistance; + + // BVH + if (_bvh.Count() != 0) + { + return RayCastBVH(0, ray, hitDistance, hitNormal, hitTriangle); + } + + // Brute-force + { + Vector3 normal; + float distance; + bool hit = false; + for (const Mesh& meshData : _meshes) + { + if (!meshData.Bounds.Intersects(ray)) + continue; + const Vector3* vb = meshData.VertexBuffer.Get(); + if (meshData.Use16BitIndexBuffer) + { + const uint16* ib16 = meshData.IndexBuffer.Get(); + for (int32 i = 0; i < meshData.Indices;) + { + Vector3 v0 = vb[ib16[i++]]; + Vector3 v1 = vb[ib16[i++]]; + Vector3 v2 = vb[ib16[i++]]; + if (CollisionsHelper::RayIntersectsTriangle(ray, v0, v1, v2, distance, normal) && distance < hitDistance) + { + hitDistance = distance; + hitNormal = normal; + hitTriangle = Triangle(v0, v1, v2); + hit = true; + } + } + } + else + { + const uint32* ib32 = meshData.IndexBuffer.Get(); + for (int32 i = 0; i < meshData.Indices;) + { + Vector3 v0 = vb[ib32[i++]]; + Vector3 v1 = vb[ib32[i++]]; + Vector3 v2 = vb[ib32[i++]]; + if (CollisionsHelper::RayIntersectsTriangle(ray, v0, v1, v2, distance, normal) && distance < hitDistance) + { + hitDistance = distance; + hitNormal = normal; + hitTriangle = Triangle(v0, v1, v2); + hit = true; + } + } + } + } + return hit; + } +} + +#endif diff --git a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h new file mode 100644 index 000000000..1063b457c --- /dev/null +++ b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h @@ -0,0 +1,79 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#if COMPILE_WITH_MODEL_TOOL + +#include "Engine/Core/Math/Triangle.h" +#include "Engine/Core/Math/BoundingBox.h" +#include "Engine/Core/Types/DataContainer.h" +#include "Engine/Core/Collections/Array.h" + +class Model; +class ModelData; + +/// +/// Acceleration Structure utility for robust ray tracing mesh geometry with optimized data structure. +/// +class FLAXENGINE_API MeshAccelerationStructure +{ +private: + struct Mesh + { + BytesContainer IndexBuffer, VertexBuffer; + int32 Indices, Vertices; + bool Use16BitIndexBuffer; + BoundingBox Bounds; + }; + + struct BVH + { + BoundingBox Bounds; + + union + { + struct + { + uint32 IsLeaf : 1; + uint16 TriangleCount : 15; + uint16 MeshIndex : 16; + uint32 TriangleIndex; + } Leaf; + + struct + { + uint32 IsLeaf : 1; + uint32 ChildrenCount : 31; + int32 ChildIndex; + } Node; + }; + }; + + Array> _meshes; + Array _bvh; + + void BuildBVH(int32 node, int32 maxLeafSize, Array& scratch); + bool PointQueryBVH(int32 node, const Vector3& point, float& hitDistance, Vector3& hitPoint, Triangle& hitTriangle) const; + bool RayCastBVH(int32 node, const Ray& ray, float& hitDistance, Vector3& hitNormal, Triangle& hitTriangle) const; + +public: + // Adds the model geometry for the build to the structure. + void Add(Model* model, int32 lodIndex); + + // Adds the model geometry for the build to the structure. + void Add(ModelData* modelData, int32 lodIndex, bool copy = false); + + // Adds the triangles geometry for the build to the structure. + void Add(Vector3* vb, int32 vertices, void* ib, int32 indices, bool use16BitIndex, bool copy = false); + + // Builds Bounding Volume Hierarchy (BVH) structure for accelerated geometry queries. + void BuildBVH(int32 maxLeafSize = 16); + + // Queries the closest triangle. + bool PointQuery(const Vector3& point, float& hitDistance, Vector3& hitPoint, Triangle& hitTriangle, float maxDistance = MAX_float) const; + + // Ray traces the triangles. + bool RayCast(const Ray& ray, float& hitDistance, Vector3& hitNormal, Triangle& hitTriangle, float maxDistance = MAX_float) const; +}; + +#endif diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Build.cs b/Source/Engine/Tools/ModelTool/ModelTool.Build.cs index 25f43d98a..7a4eb59c3 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Build.cs +++ b/Source/Engine/Tools/ModelTool/ModelTool.Build.cs @@ -15,6 +15,11 @@ public class ModelTool : EngineModule { base.Setup(options); + options.PublicDefinitions.Add("COMPILE_WITH_MODEL_TOOL"); + + if (!options.Target.IsEditor) + return; + bool useAssimp = true; bool useAutodeskFbxSdk = false; bool useOpenFBX = true; @@ -56,21 +61,19 @@ public class ModelTool : EngineModule options.PrivateDependencies.Add("UVAtlas"); break; case TargetPlatform.Linux: - case TargetPlatform.Mac: - break; + case TargetPlatform.Mac: break; default: throw new InvalidPlatformException(options.Platform.Target); } options.PrivateDependencies.Add("meshoptimizer"); options.PrivateDependencies.Add("MikkTSpace"); options.PrivateDependencies.Add("Physics"); - - options.PublicDefinitions.Add("COMPILE_WITH_MODEL_TOOL"); } /// public override void GetFilesToDeploy(List files) { files.Add(Path.Combine(FolderPath, "ModelTool.h")); + files.Add(Path.Combine(FolderPath, "MeshAccelerationStructure.h")); } } diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp index 2b3620616..964df9d6e 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp @@ -1,6 +1,6 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. -#if COMPILE_WITH_MODEL_TOOL +#if COMPILE_WITH_MODEL_TOOL && USE_EDITOR #include "ModelTool.h" #include "Engine/Core/Log.h" @@ -62,6 +62,8 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj SERIALIZE(ImportMaterials); SERIALIZE(ImportTextures); SERIALIZE(RestoreMaterialsOnReimport); + SERIALIZE(GenerateSDF); + SERIALIZE(SDFResolution); SERIALIZE(SplitObjects); SERIALIZE(ObjectIndex); } @@ -100,6 +102,8 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(ImportMaterials); DESERIALIZE(ImportTextures); DESERIALIZE(RestoreMaterialsOnReimport); + DESERIALIZE(GenerateSDF); + DESERIALIZE(SDFResolution); DESERIALIZE(SplitObjects); DESERIALIZE(ObjectIndex); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index f014bd571..fd8575612 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -3,7 +3,21 @@ #if COMPILE_WITH_MODEL_TOOL #include "ModelTool.h" +#include "MeshAccelerationStructure.h" #include "Engine/Core/Log.h" +#include "Engine/Core/RandomStream.h" +#include "Engine/Core/Math/Int3.h" +#include "Engine/Core/Math/Ray.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Threading/JobSystem.h" +#include "Engine/Graphics/RenderTools.h" +#include "Engine/Graphics/Async/GPUTask.h" +#include "Engine/Graphics/Textures/GPUTexture.h" +#include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Graphics/Models/ModelData.h" +#include "Engine/Content/Assets/Model.h" +#include "Engine/Serialization/MemoryWriteStream.h" +#if USE_EDITOR #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Types/Pair.h" @@ -19,6 +33,297 @@ #include "Engine/ContentImporters/CreateCollisionData.h" #include "Editor/Utilities/EditorUtilities.h" #include +#endif + +ModelSDFHeader::ModelSDFHeader(const ModelBase::SDFData& sdf, const GPUTextureDescription& desc) + : LocalToUVWMul(sdf.LocalToUVWMul) + , WorldUnitsPerVoxel(sdf.WorldUnitsPerVoxel) + , LocalToUVWAdd(sdf.LocalToUVWAdd) + , MaxDistance(sdf.MaxDistance) + , LocalBoundsMin(sdf.LocalBoundsMin) + , MipLevels(desc.MipLevels) + , LocalBoundsMax(sdf.LocalBoundsMax) + , Width(desc.Width) + , Height(desc.Height) + , Depth(desc.Depth) + , Format(desc.Format) + , ResolutionScale(sdf.ResolutionScale) + , LOD(sdf.LOD) +{ +} + +ModelSDFMip::ModelSDFMip(int32 mipIndex, uint32 rowPitch, uint32 slicePitch) + : MipIndex(mipIndex) + , RowPitch(rowPitch) + , SlicePitch(slicePitch) +{ +} + +ModelSDFMip::ModelSDFMip(int32 mipIndex, const TextureMipData& mip) + : MipIndex(mipIndex) + , RowPitch(mip.RowPitch) + , SlicePitch(mip.Data.Length()) +{ +} + +bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, MemoryWriteStream* outputStream, const StringView& assetName, float backfacesThreshold) +{ + PROFILE_CPU(); + auto startTime = Platform::GetTimeSeconds(); + + // Setup SDF texture properties + BoundingBox bounds; + if (inputModel) + bounds = inputModel->LODs[lodIndex].GetBox(); + else if (modelData) + bounds = modelData->LODs[lodIndex].GetBox(); + else + return true; + Vector3 size = bounds.GetSize(); + ModelBase::SDFData sdf; + sdf.WorldUnitsPerVoxel = 10 / Math::Max(resolutionScale, 0.0001f); + Int3 resolution(Vector3::Ceil(Vector3::Clamp(size / sdf.WorldUnitsPerVoxel, 4, 256))); + Vector3 uvwToLocalMul = size; + Vector3 uvwToLocalAdd = bounds.Minimum; + sdf.LocalToUVWMul = Vector3::One / uvwToLocalMul; + sdf.LocalToUVWAdd = -uvwToLocalAdd / uvwToLocalMul; + sdf.MaxDistance = size.MaxValue(); + sdf.LocalBoundsMin = bounds.Minimum; + sdf.LocalBoundsMax = bounds.Maximum; + sdf.ResolutionScale = resolutionScale; + sdf.LOD = lodIndex; + // TODO: maybe apply 1 voxel margin around the geometry? + const int32 maxMips = 3; + const int32 mipCount = Math::Min(MipLevelsCount(resolution.X, resolution.Y, resolution.Z, true), maxMips); + PixelFormat format = PixelFormat::R16_UNorm; + int32 formatStride = 2; + float formatMaxValue = MAX_uint16; + typedef float (*FormatRead)(void* ptr); + typedef void (*FormatWrite)(void* ptr, float v); + FormatRead formatRead = [](void* ptr) + { + return (float)*(uint16*)ptr; + }; + FormatWrite formatWrite = [](void* ptr, float v) + { + *(uint16*)ptr = (uint16)v; + }; + if (resolution.MaxValue() < 8) + { + // For smaller meshes use more optimized format (gives small perf and memory gain but introduces artifacts on larger meshes) + format = PixelFormat::R8_UNorm; + formatStride = 1; + formatMaxValue = MAX_uint8; + formatRead = [](void* ptr) + { + return (float)*(uint8*)ptr; + }; + formatWrite = [](void* ptr, float v) + { + *(uint8*)ptr = (uint8)v; + }; + } + GPUTextureDescription textureDesc = GPUTextureDescription::New3D(resolution.X, resolution.Y, resolution.Z, format, GPUTextureFlags::ShaderResource, mipCount); + if (outputSDF) + { + *outputSDF = sdf; + if (!outputSDF->Texture) + outputSDF->Texture = GPUTexture::New(); + if (outputSDF->Texture->Init(textureDesc)) + { + SAFE_DELETE_GPU_RESOURCE(outputSDF->Texture); + return true; + } + } + + // TODO: support GPU to generate model SDF on-the-fly (if called during rendering) + + // Setup acceleration structure for fast ray tracing the mesh triangles + MeshAccelerationStructure scene; + if (inputModel) + scene.Add(inputModel, lodIndex); + else if (modelData) + scene.Add(modelData, lodIndex); + scene.BuildBVH(); + + // Allocate memory for the distant field + const int32 voxelsSize = resolution.X * resolution.Y * resolution.Z * formatStride; + void* voxels = Allocator::Allocate(voxelsSize); + Vector3 xyzToLocalMul = uvwToLocalMul / Vector3(resolution); + Vector3 xyzToLocalAdd = uvwToLocalAdd; + const Vector2 encodeMAD(0.5f / sdf.MaxDistance * formatMaxValue, 0.5f * formatMaxValue); + const Vector2 decodeMAD(2.0f * sdf.MaxDistance / formatMaxValue, -sdf.MaxDistance); + int32 voxelSizeSum = voxelsSize; + + // TODO: use optimized sparse storage for SDF data as hierarchical bricks as in papers below: + // https://graphics.pixar.com/library/IrradianceAtlas/paper.pdf + // http://maverick.inria.fr/Membres/Cyril.Crassin/thesis/CCrassinThesis_EN_Web.pdf + // http://ramakarl.com/pdfs/2016_Hoetzlein_GVDB.pdf + // https://www.cse.chalmers.se/~uffe/HighResolutionSparseVoxelDAGs.pdf + + // Brute-force for each voxel to calculate distance to the closest triangle with point query and distance sign by raycasting around the voxel + const int32 sampleCount = 12; + Array sampleDirections; + sampleDirections.Resize(sampleCount); + { + RandomStream rand; + sampleDirections.Get()[0] = Vector3::Up; + sampleDirections.Get()[1] = Vector3::Down; + sampleDirections.Get()[2] = Vector3::Left; + sampleDirections.Get()[3] = Vector3::Right; + sampleDirections.Get()[4] = Vector3::Forward; + sampleDirections.Get()[5] = Vector3::Backward; + for (int32 i = 6; i < sampleCount; i++) + sampleDirections.Get()[i] = rand.GetUnitVector(); + } + Function sdfJob = [&sdf, &resolution, &backfacesThreshold, &sampleDirections, &scene, &voxels, &xyzToLocalMul, &xyzToLocalAdd, &encodeMAD, &formatStride, &formatWrite](int32 z) + { + PROFILE_CPU_NAMED("Model SDF Job"); + float hitDistance; + Vector3 hitNormal, hitPoint; + Triangle hitTriangle; + const int32 zAddress = resolution.Y * resolution.X * z; + for (int32 y = 0; y < resolution.Y; y++) + { + const int32 yAddress = resolution.X * y + zAddress; + for (int32 x = 0; x < resolution.X; x++) + { + float minDistance = sdf.MaxDistance; + Vector3 voxelPos = Vector3((float)x, (float)y, (float)z) * xyzToLocalMul + xyzToLocalAdd; + + // Point query to find the distance to the closest surface + scene.PointQuery(voxelPos, minDistance, hitPoint, hitTriangle); + + // Raycast samples around voxel to count triangle backfaces hit + int32 hitBackCount = 0, hitCount = 0; + for (int32 sample = 0; sample < sampleDirections.Count(); sample++) + { + Ray sampleRay(voxelPos, sampleDirections[sample]); + if (scene.RayCast(sampleRay, hitDistance, hitNormal, hitTriangle)) + { + hitCount++; + const bool backHit = Vector3::Dot(sampleRay.Direction, hitTriangle.GetNormal()) > 0; + if (backHit) + hitBackCount++; + } + } + + float distance = minDistance; + // TODO: surface thickness threshold? shift reduce distance for all voxels by something like 0.01 to enlarge thin geometry + // if ((float)hitBackCount > (float)hitCount * 0.3f && hitCount != 0) + if ((float)hitBackCount > (float)sampleDirections.Count() * backfacesThreshold && hitCount != 0) + { + // Voxel is inside the geometry so turn it into negative distance to the surface + distance *= -1; + } + const int32 xAddress = x + yAddress; + formatWrite((byte*)voxels + xAddress * formatStride, distance * encodeMAD.X + encodeMAD.Y); + } + } + }; + JobSystem::Execute(sdfJob, resolution.Z); + + // Cache SDF data on a CPU + if (outputStream) + { + outputStream->WriteInt32(1); // Version + ModelSDFHeader data(sdf, textureDesc); + outputStream->Write(&data); + ModelSDFMip mipData(0, resolution.X * formatStride, voxelsSize); + outputStream->Write(&mipData); + outputStream->WriteBytes(voxels, voxelsSize); + } + + // Upload data to the GPU + if (outputSDF) + { + BytesContainer data; + data.Link((byte*)voxels, voxelsSize); + auto task = outputSDF->Texture->UploadMipMapAsync(data, 0, resolution.X * formatStride, voxelsSize, true); + if (task) + task->Start(); + } + + // Generate mip maps + void* voxelsMip = nullptr; + for (int32 mipLevel = 1; mipLevel < mipCount; mipLevel++) + { + Int3 resolutionMip = Int3::Max(resolution / 2, Int3::One); + const int32 voxelsMipSize = resolutionMip.X * resolutionMip.Y * resolutionMip.Z * formatStride; + if (voxelsMip == nullptr) + voxelsMip = Allocator::Allocate(voxelsMipSize); + + // Downscale mip + Function mipJob = [&voxelsMip, &voxels, &resolution, &resolutionMip, &encodeMAD, &decodeMAD, &formatStride, &formatRead, &formatWrite](int32 z) + { + PROFILE_CPU_NAMED("Model SDF Mip Job"); + const int32 zAddress = resolutionMip.Y * resolutionMip.X * z; + for (int32 y = 0; y < resolutionMip.Y; y++) + { + const int32 yAddress = resolutionMip.X * y + zAddress; + for (int32 x = 0; x < resolutionMip.X; x++) + { + // Linear box filter around the voxel + // TODO: use min distance for nearby texels (texel distance + distance to texel) + float distance = 0; + for (int32 dz = 0; dz < 2; dz++) + { + const int32 dzAddress = (z * 2 + dz) * (resolution.Y * resolution.X); + for (int32 dy = 0; dy < 2; dy++) + { + const int32 dyAddress = (y * 2 + dy) * (resolution.X) + dzAddress; + for (int32 dx = 0; dx < 2; dx++) + { + const int32 dxAddress = (x * 2 + dx) + dyAddress; + const float d = formatRead((byte*)voxels + dxAddress * formatStride) * decodeMAD.X + decodeMAD.Y; + distance += d; + } + } + } + distance *= 1.0f / 8.0f; + + const int32 xAddress = x + yAddress; + formatWrite((byte*)voxelsMip + xAddress * formatStride, distance * encodeMAD.X + encodeMAD.Y); + } + } + }; + JobSystem::Execute(mipJob, resolutionMip.Z); + + // Cache SDF data on a CPU + if (outputStream) + { + ModelSDFMip mipData(mipLevel, resolutionMip.X * formatStride, voxelsMipSize); + outputStream->Write(&mipData); + outputStream->WriteBytes(voxelsMip, voxelsMipSize); + } + + // Upload to the GPU + if (outputSDF) + { + BytesContainer data; + data.Link((byte*)voxelsMip, voxelsMipSize); + auto task = outputSDF->Texture->UploadMipMapAsync(data, mipLevel, resolutionMip.X * formatStride, voxelsMipSize, true); + if (task) + task->Start(); + } + + // Go down + voxelSizeSum += voxelsSize; + Swap(voxelsMip, voxels); + resolution = resolutionMip; + } + + Allocator::Free(voxelsMip); + Allocator::Free(voxels); + +#if !BUILD_RELEASE + auto endTime = Platform::GetTimeSeconds(); + LOG(Info, "Generated SDF {}x{}x{} ({} kB) in {}ms for {}", resolution.X, resolution.Y, resolution.Z, voxelSizeSum / 1024, (int32)((endTime - startTime) * 1000.0), assetName); +#endif + return false; +} + +#if USE_EDITOR void RemoveNamespace(String& name) { @@ -1308,3 +1613,5 @@ bool ModelTool::FindTexture(const String& sourcePath, const String& file, String } #endif + +#endif diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 6e22b4cd6..5af8a84eb 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -5,6 +5,8 @@ #if COMPILE_WITH_MODEL_TOOL #include "Engine/Core/Config.h" +#include "Engine/Content/Assets/ModelBase.h" +#if USE_EDITOR #include "Engine/Serialization/ISerializable.h" #include "Engine/Graphics/Models/ModelData.h" #include "Engine/Graphics/Models/SkeletonData.h" @@ -141,13 +143,52 @@ public: } }; +#endif + +struct ModelSDFHeader +{ + Vector3 LocalToUVWMul; + float WorldUnitsPerVoxel; + Vector3 LocalToUVWAdd; + float MaxDistance; + Vector3 LocalBoundsMin; + int32 MipLevels; + Vector3 LocalBoundsMax; + int32 Width; + int32 Height; + int32 Depth; + PixelFormat Format; + float ResolutionScale; + int32 LOD; + + ModelSDFHeader() = default; + ModelSDFHeader(const ModelBase::SDFData& sdf, const struct GPUTextureDescription& desc); +}; + +struct ModelSDFMip +{ + int32 MipIndex; + uint32 RowPitch; + uint32 SlicePitch; + + ModelSDFMip() = default; + ModelSDFMip(int32 mipIndex, uint32 rowPitch, uint32 slicePitch); + ModelSDFMip(int32 mipIndex, const TextureMipData& mip); +}; + /// -/// Import models and animations helper. +/// Models data importing and processing utility. /// class FLAXENGINE_API ModelTool { public: + // Optional: inputModel or modelData + // Optional: outputSDF or null, outputStream or null + static bool GenerateModelSDF(class Model* inputModel, class ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, class MemoryWriteStream* outputStream, const StringView& assetName, float backfacesThreshold = 0.6f); + +#if USE_EDITOR +public: /// /// Declares the imported data type. /// @@ -206,6 +247,10 @@ public: bool ImportTextures = true; bool RestoreMaterialsOnReimport = true; + // SDF + bool GenerateSDF = false; + float SDFResolution = 1.0f; + // Splitting bool SplitObjects = false; int32 ObjectIndex = -1; @@ -283,6 +328,7 @@ private: #if USE_OPEN_FBX static bool ImportDataOpenFBX(const char* path, ImportedModelData& data, Options& options, String& errorMsg); #endif +#endif }; #endif diff --git a/Source/Engine/Tools/ModelTool/SpatialSort.cpp b/Source/Engine/Tools/ModelTool/SpatialSort.cpp index 773455a7a..de13084dc 100644 --- a/Source/Engine/Tools/ModelTool/SpatialSort.cpp +++ b/Source/Engine/Tools/ModelTool/SpatialSort.cpp @@ -44,7 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file Implementation of the helper class to quickly find vertices close to a given position */ #include "SpatialSort.h" -#if COMPILE_WITH_MODEL_TOOL +#if COMPILE_WITH_MODEL_TOOL && USE_EDITOR #include using namespace Assimp; diff --git a/Source/Engine/Tools/ModelTool/SpatialSort.h b/Source/Engine/Tools/ModelTool/SpatialSort.h index 6a6371de7..34d716b70 100644 --- a/Source/Engine/Tools/ModelTool/SpatialSort.h +++ b/Source/Engine/Tools/ModelTool/SpatialSort.h @@ -43,7 +43,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #pragma once /** Small helper classes to optimise finding vertizes close to a given location */ -#ifndef AI_SPATIALSORT_H_INC +#if !defined(AI_SPATIALSORT_H_INC) && COMPILE_WITH_MODEL_TOOL && USE_EDITOR #define AI_SPATIALSORT_H_INC #include diff --git a/Source/Engine/Tools/ModelTool/VertexTriangleAdjacency.cpp b/Source/Engine/Tools/ModelTool/VertexTriangleAdjacency.cpp index 040d862d6..4dd820853 100644 --- a/Source/Engine/Tools/ModelTool/VertexTriangleAdjacency.cpp +++ b/Source/Engine/Tools/ModelTool/VertexTriangleAdjacency.cpp @@ -1,6 +1,6 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. -#if COMPILE_WITH_MODEL_TOOL +#if COMPILE_WITH_MODEL_TOOL && USE_EDITOR #include "VertexTriangleAdjacency.h" #include "Engine/Core/Math/Math.h" diff --git a/Source/Engine/Tools/ModelTool/VertexTriangleAdjacency.h b/Source/Engine/Tools/ModelTool/VertexTriangleAdjacency.h index 56e9cfd9c..c37d767ba 100644 --- a/Source/Engine/Tools/ModelTool/VertexTriangleAdjacency.h +++ b/Source/Engine/Tools/ModelTool/VertexTriangleAdjacency.h @@ -2,7 +2,7 @@ #pragma once -#if COMPILE_WITH_MODEL_TOOL +#if COMPILE_WITH_MODEL_TOOL && USE_EDITOR #include "Engine/Core/Config.h" #include "Engine/Core/Types/BaseTypes.h" diff --git a/Source/Engine/UI/GUI/Control.cs b/Source/Engine/UI/GUI/Control.cs index 0031e07bd..8cd9c932b 100644 --- a/Source/Engine/UI/GUI/Control.cs +++ b/Source/Engine/UI/GUI/Control.cs @@ -313,7 +313,7 @@ namespace FlaxEngine.GUI /// /// Gets the control DPI scale factor (1 is default). Includes custom DPI scale. /// - public float DpiScale => RootWindow?.Window.DpiScale ?? Platform.DpiScale; + public float DpiScale => RootWindow?.Window?.DpiScale ?? Platform.DpiScale; /// /// Gets screen position of the control (upper left corner). diff --git a/Source/Engine/UI/SpriteRender.cpp b/Source/Engine/UI/SpriteRender.cpp index 32d26623a..0ced24966 100644 --- a/Source/Engine/UI/SpriteRender.cpp +++ b/Source/Engine/UI/SpriteRender.cpp @@ -106,6 +106,8 @@ bool SpriteRender::HasContentLoaded() const void SpriteRender::Draw(RenderContext& renderContext) { + if (renderContext.View.Pass == DrawPass::GlobalSDF || renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) + return; if (!Material || !Material->IsLoaded() || !_quadModel || !_quadModel->IsLoaded()) return; auto model = _quadModel.As(); @@ -131,11 +133,6 @@ void SpriteRender::Draw(RenderContext& renderContext) model->LODs[0].Draw(renderContext, _materialInstance, world, GetStaticFlags(), false, DrawModes, GetPerInstanceRandom()); } -void SpriteRender::DrawGeneric(RenderContext& renderContext) -{ - Draw(renderContext); -} - void SpriteRender::Serialize(SerializeStream& stream, const void* otherObj) { // Base @@ -173,7 +170,7 @@ void SpriteRender::Deserialize(DeserializeStream& stream, ISerializeModifier* mo void SpriteRender::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void SpriteRender::OnEndPlay() @@ -193,7 +190,7 @@ void SpriteRender::OnEndPlay() void SpriteRender::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddGeometry(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); // Base Actor::OnEnable(); @@ -201,7 +198,7 @@ void SpriteRender::OnEnable() void SpriteRender::OnDisable() { - GetSceneRendering()->RemoveGeometry(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); @@ -218,5 +215,5 @@ void SpriteRender::OnTransformChanged() BoundingSphere::Transform(localSphere, world, _sphere); BoundingBox::FromSphere(_sphere, _box); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } diff --git a/Source/Engine/UI/SpriteRender.h b/Source/Engine/UI/SpriteRender.h index 00b232266..e28399860 100644 --- a/Source/Engine/UI/SpriteRender.h +++ b/Source/Engine/UI/SpriteRender.h @@ -95,7 +95,6 @@ public: // [Actor] bool HasContentLoaded() const override; void Draw(RenderContext& renderContext) override; - void DrawGeneric(RenderContext& renderContext) override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; void OnLayerChanged() override; diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 10e17d16b..cf206f4bc 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -330,7 +330,7 @@ void TextRender::UpdateLayout() BoundingBox::Transform(_localBox, _world, _box); BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } bool TextRender::HasContentLoaded() const @@ -340,6 +340,10 @@ bool TextRender::HasContentLoaded() const void TextRender::Draw(RenderContext& renderContext) { + if (renderContext.View.Pass == DrawPass::GlobalSDF) + return; // TODO: Text rendering to Global SDF + if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) + return; // TODO: Text rendering to Global Surface Atlas if (_isDirty) { UpdateLayout(); @@ -400,11 +404,6 @@ void TextRender::Draw(RenderContext& renderContext) GEOMETRY_DRAW_STATE_EVENT_END(_drawState, _world); } -void TextRender::DrawGeneric(RenderContext& renderContext) -{ - Draw(renderContext); -} - #if USE_EDITOR #include "Engine/Debug/DebugDraw.h" @@ -426,7 +425,7 @@ void TextRender::OnDebugDrawSelected() void TextRender::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } bool TextRender::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) @@ -483,6 +482,13 @@ void TextRender::Deserialize(DeserializeStream& stream, ISerializeModifier* modi DESERIALIZE_MEMBER(Scale, _layoutOptions.Scale); DESERIALIZE_MEMBER(GapScale, _layoutOptions.BaseLinesGapScale); + // [Deprecated on 07.02.2022, expires on 07.02.2024] + if (modifier->EngineBuild <= 6330) + DrawModes |= DrawPass::GlobalSDF; + // [Deprecated on 27.04.2022, expires on 27.04.2024] + if (modifier->EngineBuild <= 6331) + DrawModes |= DrawPass::GlobalSurfaceAtlas; + _isDirty = true; } @@ -495,7 +501,7 @@ void TextRender::OnEnable() { UpdateLayout(); } - _sceneRenderingKey = GetSceneRendering()->AddGeometry(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); } void TextRender::OnDisable() @@ -505,7 +511,7 @@ void TextRender::OnDisable() _isLocalized = false; Localization::LocalizationChanged.Unbind(this); } - GetSceneRendering()->RemoveGeometry(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); @@ -520,5 +526,5 @@ void TextRender::OnTransformChanged() BoundingBox::Transform(_localBox, _world, _box); BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } diff --git a/Source/Engine/UI/TextRender.h b/Source/Engine/UI/TextRender.h index 1b19737ad..c6a49b84e 100644 --- a/Source/Engine/UI/TextRender.h +++ b/Source/Engine/UI/TextRender.h @@ -166,7 +166,6 @@ public: // [Actor] bool HasContentLoaded() const override; void Draw(RenderContext& renderContext) override; - void DrawGeneric(RenderContext& renderContext) override; #if USE_EDITOR void OnDebugDrawSelected() override; #endif diff --git a/Source/Engine/Utilities/RectPack.h b/Source/Engine/Utilities/RectPack.h index 1662b4fec..b7528834c 100644 --- a/Source/Engine/Utilities/RectPack.h +++ b/Source/Engine/Utilities/RectPack.h @@ -25,9 +25,6 @@ struct RectPack SizeType Width; SizeType Height; - // The remaining space amount inside this slot (updated on every insertion, initial it equal to width*height). - SizeType SpaceLeft; - // True, if slot has been allocated, otherwise it's free. bool IsUsed; @@ -45,7 +42,6 @@ struct RectPack , Y(y) , Width(width) , Height(height) - , SpaceLeft(width * height) , IsUsed(false) { } @@ -75,13 +71,15 @@ struct RectPack NodeType* result; const SizeType paddedWidth = itemWidth + itemPadding; const SizeType paddedHeight = itemHeight + itemPadding; - const SizeType paddedSize = paddedWidth * paddedHeight; - // Check if there is enough space to fix that item within this slot - if (SpaceLeft < paddedSize) + // Check if we're free and just the right size + if (!IsUsed && Width == paddedWidth && Height == paddedHeight) { - // Not enough space - return nullptr; + // Insert into this slot + IsUsed = true; + result = (NodeType*)this; + result->OnInsert(Forward(args)...); + return result; } // If there are left and right slots there are empty regions around this slot (it also means this slot is occupied) @@ -91,23 +89,14 @@ struct RectPack { result = Left->Insert(itemWidth, itemHeight, itemPadding, Forward(args)...); if (result) - { - SpaceLeft -= paddedSize; return result; - } } if (Right) { result = Right->Insert(itemWidth, itemHeight, itemPadding, Forward(args)...); if (result) - { - SpaceLeft -= paddedSize; return result; - } } - - // Not enough space - return nullptr; } // This slot can't fit or has been already occupied @@ -117,17 +106,6 @@ struct RectPack return nullptr; } - // Check if we're just right size - if (Width == paddedWidth && Height == paddedHeight) - { - // Insert into this slot - IsUsed = true; - SpaceLeft -= paddedSize; - result = (NodeType*)this; - result->OnInsert(Forward(args)...); - return result; - } - // The width and height of the new child node const SizeType remainingWidth = Math::Max(0, Width - paddedWidth); const SizeType remainingHeight = Math::Max(0, Height - paddedHeight); @@ -152,9 +130,20 @@ struct RectPack // Insert into this slot IsUsed = true; - SpaceLeft -= paddedSize; result = (NodeType*)this; result->OnInsert(Forward(args)...); return result; } + + /// + /// Frees the node. + /// + /// The node that contains inserted an item or null if failed to find a free space. + template + void Free(Args&&...args) + { + ASSERT(IsUsed); + IsUsed = false; + ((NodeType*)this)->OnFree(Forward(args)...); + } }; diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp index bed49a901..e2f1472d3 100644 --- a/Source/Engine/Visject/ShaderGraph.cpp +++ b/Source/Engine/Visject/ShaderGraph.cpp @@ -413,10 +413,17 @@ void ShaderGenerator::ProcessGroupMath(Box* box, Node* node, Value& value) const auto rangeA = tryGetValue(node->GetBox(1), node->Values[1].AsVector2()); const auto rangeB = tryGetValue(node->GetBox(2), node->Values[2].AsVector2()); const auto clamp = tryGetValue(node->GetBox(3), node->Values[3]).AsBool(); - const auto mapFunc = String::Format(TEXT("{2}.x + ({0} - {1}.x) * ({2}.y - {2}.x) / ({1}.y - {1}.x)"), inVal.Value, rangeA.Value, rangeB.Value); value = writeLocal(ValueType::Float, String::Format(TEXT("{2} ? clamp({0}, {1}.x, {1}.y) : {0}"), mapFunc, rangeB.Value, clamp.Value), node); break; + } + // Rotate Vector + case 49: + { + const Value quaternion = tryGetValue(node->GetBox(0), Value::InitForZero(VariantType::Quaternion)).Cast(VariantType::Quaternion); + const Value vector = tryGetValue(node->GetBox(1), Vector3::Forward).Cast(VariantType::Vector3); + value = writeLocal(ValueType::Vector3, String::Format(TEXT("QuatRotateVector({0}, {1})"), quaternion.Value, vector.Value), node); + break; } default: break; @@ -1302,6 +1309,27 @@ SerializedMaterialParam& ShaderGenerator::findOrAddTextureGroupSampler(int32 ind return param; } +SerializedMaterialParam& ShaderGenerator::findOrAddGlobalSDF() +{ + // Find + for (int32 i = 0; i < _parameters.Count(); i++) + { + SerializedMaterialParam& param = _parameters[i]; + if (!param.IsPublic && param.Type == MaterialParameterType::GlobalSDF) + return param; + } + + // Create + SerializedMaterialParam& param = _parameters.AddOne(); + param.Type = MaterialParameterType::GlobalSDF; + param.IsPublic = false; + param.Override = true; + param.Name = TEXT("Global SDF"); + param.ShaderName = getParamName(_parameters.Count()); + param.ID = Guid(_parameters.Count(), 0, 0, 3); // Assign temporary id + return param; +} + String ShaderGenerator::getLocalName(int32 index) { return TEXT("local") + StringUtils::ToString(index); diff --git a/Source/Engine/Visject/ShaderGraph.h b/Source/Engine/Visject/ShaderGraph.h index 1ae7cab7f..38205f743 100644 --- a/Source/Engine/Visject/ShaderGraph.h +++ b/Source/Engine/Visject/ShaderGraph.h @@ -286,6 +286,7 @@ protected: SerializedMaterialParam findOrAddCubeTexture(const Guid& id); SerializedMaterialParam findOrAddSceneTexture(MaterialSceneTextures type); SerializedMaterialParam& findOrAddTextureGroupSampler(int32 index); + SerializedMaterialParam& findOrAddGlobalSDF(); static String getLocalName(int32 index); static String getParamName(int32 index); diff --git a/Source/Engine/Visject/ShaderGraphUtilities.cpp b/Source/Engine/Visject/ShaderGraphUtilities.cpp index 0944685e9..2db2459c9 100644 --- a/Source/Engine/Visject/ShaderGraphUtilities.cpp +++ b/Source/Engine/Visject/ShaderGraphUtilities.cpp @@ -9,19 +9,19 @@ #include "Engine/Content/Content.h" #include "Engine/Engine/GameplayGlobals.h" #include "Engine/Graphics/Config.h" +#include "Engine/Renderer/GlobalSignDistanceFieldPass.h" void ShaderGraphUtilities::GenerateShaderConstantBuffer(TextWriterUnicode& writer, Array& parameters) { int32 constantsOffset = 0; int32 paddingIndex = 0; - for (int32 i = 0; i < parameters.Count(); i++) { auto& param = parameters[i]; - const Char* format = nullptr; int32 size; int32 alignment; + bool zeroRegister = true; switch (param.Type) { case MaterialParameterType::Bool: @@ -107,11 +107,15 @@ void ShaderGraphUtilities::GenerateShaderConstantBuffer(TextWriterUnicode& write alignment = 16; format = TEXT("float4 {0};"); break; - default: ; } break; } - default: ; + case MaterialParameterType::GlobalSDF: + zeroRegister = false; + size = sizeof(GlobalSignDistanceFieldPass::ConstantsData); + alignment = 16; + format = TEXT("GlobalSDFData {0};"); + break; } if (format) { @@ -126,7 +130,8 @@ void ShaderGraphUtilities::GenerateShaderConstantBuffer(TextWriterUnicode& write } } - param.RegisterIndex = 0; + if (zeroRegister) + param.RegisterIndex = 0; param.Offset = constantsOffset; writer.WriteLine(format, param.ShaderName); constantsOffset += size; @@ -139,7 +144,9 @@ const Char* ShaderGraphUtilities::GenerateShaderResources(TextWriterUnicode& wri for (int32 i = 0; i < parameters.Count(); i++) { auto& param = parameters[i]; - const Char* format; + const Char* format = nullptr; + bool zeroOffset = true; + int32 registers = 1; switch (param.Type) { case MaterialParameterType::NormalMap: @@ -158,16 +165,19 @@ const Char* ShaderGraphUtilities::GenerateShaderResources(TextWriterUnicode& wri case MaterialParameterType::GPUTextureVolume: format = TEXT("Texture3D {0} : register(t{1});"); break; - default: - format = nullptr; + case MaterialParameterType::GlobalSDF: + format = TEXT("Texture3D {0}_Tex[4] : register(t{1});"); + registers = 4; + zeroOffset = false; break; } if (format) { - param.Offset = 0; + if (zeroOffset) + param.Offset = 0; param.RegisterIndex = (byte)startRegister; writer.WriteLine(format, param.ShaderName, startRegister); - startRegister++; + startRegister += registers; if (param.RegisterIndex >= GPU_MAX_SR_BINDED) { return TEXT("Too many textures used. The maximum supported amount is " MACRO_TO_STR(GPU_MAX_SR_BINDED) " (including lightmaps and utility textures for lighting)."); diff --git a/Source/Engine/Visject/ShaderGraphValue.cpp b/Source/Engine/Visject/ShaderGraphValue.cpp index cd3c1354a..0b4178ca3 100644 --- a/Source/Engine/Visject/ShaderGraphValue.cpp +++ b/Source/Engine/Visject/ShaderGraphValue.cpp @@ -2,6 +2,7 @@ #include "ShaderGraphValue.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Math/Quaternion.h" #include "Engine/Core/Math/Vector2.h" #include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Math/Vector4.h" @@ -54,6 +55,10 @@ ShaderGraphValue::ShaderGraphValue(const Variant& v) Type = VariantType::Types::Vector4; Value = String::Format(TEXT("float4({0}, {1}, {2}, {3})"), (*(Vector4*)v.AsData).X, (*(Vector4*)v.AsData).Y, (*(Vector4*)v.AsData).Z, (*(Vector4*)v.AsData).W); break; + case VariantType::Quaternion: + Type = VariantType::Types::Quaternion; + Value = String::Format(TEXT("float4({0}, {1}, {2}, {3})"), (*(Quaternion*)v.AsData).X, (*(Quaternion*)v.AsData).Y, (*(Quaternion*)v.AsData).Z, (*(Quaternion*)v.AsData).W); + break; case VariantType::String: Type = VariantType::Types::String; Value = (StringView)v; @@ -115,14 +120,17 @@ ShaderGraphValue ShaderGraphValue::InitForZero(VariantType::Types type) case VariantType::Types::Color: v = TEXT("float4(0, 0, 0, 0)"); break; + case VariantType::Types::Quaternion: + v = TEXT("float4(0, 0, 0, 1)"); + break; case VariantType::Types::Void: v = TEXT("((Material)0)"); break; default: - CRASH; + CRASH; v = nullptr; } - return ShaderGraphValue(type, String(v)); + return ShaderGraphValue(type, v); } ShaderGraphValue ShaderGraphValue::InitForHalf(VariantType::Types type) @@ -145,11 +153,12 @@ ShaderGraphValue ShaderGraphValue::InitForHalf(VariantType::Types type) v = TEXT("float3(0.5, 0.5, 0.5)"); break; case VariantType::Types::Vector4: + case VariantType::Types::Quaternion: case VariantType::Types::Color: v = TEXT("float4(0.5, 0.5, 0.5, 0.5)"); break; default: - CRASH; + CRASH; v = nullptr; } return ShaderGraphValue(type, String(v)); @@ -175,11 +184,12 @@ ShaderGraphValue ShaderGraphValue::InitForOne(VariantType::Types type) v = TEXT("float3(1, 1, 1)"); break; case VariantType::Types::Vector4: + case VariantType::Types::Quaternion: case VariantType::Types::Color: v = TEXT("float4(1, 1, 1, 1)"); break; default: - CRASH; + CRASH; v = nullptr; } return ShaderGraphValue(type, String(v)); @@ -208,6 +218,7 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType:: case VariantType::Types::Vector2: case VariantType::Types::Vector3: case VariantType::Types::Vector4: + case VariantType::Types::Quaternion: case VariantType::Types::Color: format = TEXT("((bool){0}.x)"); break; @@ -224,6 +235,7 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType:: case VariantType::Types::Vector2: case VariantType::Types::Vector3: case VariantType::Types::Vector4: + case VariantType::Types::Quaternion: case VariantType::Types::Color: format = TEXT("((int){0}.x)"); break; @@ -240,6 +252,7 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType:: case VariantType::Types::Vector2: case VariantType::Types::Vector3: case VariantType::Types::Vector4: + case VariantType::Types::Quaternion: case VariantType::Types::Color: format = TEXT("((uint){0}.x)"); break; @@ -256,6 +269,7 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType:: case VariantType::Types::Vector2: case VariantType::Types::Vector3: case VariantType::Types::Vector4: + case VariantType::Types::Quaternion: case VariantType::Types::Color: format = TEXT("((float){0}.x)"); break; @@ -272,6 +286,7 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType:: break; case VariantType::Types::Vector3: case VariantType::Types::Vector4: + case VariantType::Types::Quaternion: case VariantType::Types::Color: format = TEXT("{0}.xy"); break; @@ -293,6 +308,9 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType:: case VariantType::Types::Color: format = TEXT("{0}.xyz"); break; + case VariantType::Types::Quaternion: + format = TEXT("QuatRotateVector({0}, float3(0, 0, 1))"); // Returns direction vector + break; } break; case VariantType::Types::Vector4: @@ -312,6 +330,16 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType:: format = TEXT("float4({0}.xyz, 0)"); break; case VariantType::Types::Color: + case VariantType::Types::Vector4: + case VariantType::Types::Quaternion: + format = TEXT("{0}"); + break; + } + break; + case VariantType::Types::Quaternion: + switch (v.Type) + { + case VariantType::Types::Color: case VariantType::Types::Vector4: format = TEXT("{0}"); break; diff --git a/Source/Engine/Visject/ShaderGraphValue.h b/Source/Engine/Visject/ShaderGraphValue.h index 913fdcf41..6c9a846fe 100644 --- a/Source/Engine/Visject/ShaderGraphValue.h +++ b/Source/Engine/Visject/ShaderGraphValue.h @@ -75,6 +75,17 @@ public: { } + /// + /// Initializes a new instance of the struct. + /// + /// The type. + /// The value. + ShaderGraphValue(VariantType::Types type, const String&& value) + : Type(type) + , Value(MoveTemp(value)) + { + } + /// /// Initializes a new instance of the struct. /// diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp index 6725e55a1..9d2d8a25c 100644 --- a/Source/Engine/Visject/VisjectGraph.cpp +++ b/Source/Engine/Visject/VisjectGraph.cpp @@ -422,11 +422,17 @@ void VisjectExecutor::ProcessGroupMath(Box* box, Node* node, Value& value) const Vector2 rangeA = tryGetValue(node->GetBox(1), node->Values[1]).AsVector2(); const Vector2 rangeB = tryGetValue(node->GetBox(2), node->Values[2]).AsVector2(); const bool clamp = tryGetValue(node->GetBox(3), node->Values[3]).AsBool; - auto mapFunc = Math::Remap(inVal, rangeA.X, rangeA.Y, rangeB.X, rangeB.Y); - value = clamp ? Math::Clamp(mapFunc, rangeB.X, rangeB.Y) : mapFunc; break; + } + // Rotate Vector + case 49: + { + const Quaternion quaternion = (Quaternion)tryGetValue(node->GetBox(0), Quaternion::Identity); + const Vector3 vector = (Vector3)tryGetValue(node->GetBox(1), Vector3::Forward); + value = quaternion * vector; + break; } default: break; diff --git a/Source/FlaxEngine.Gen.cs b/Source/FlaxEngine.Gen.cs index 04f4338d6..403e7e965 100644 --- a/Source/FlaxEngine.Gen.cs +++ b/Source/FlaxEngine.Gen.cs @@ -13,5 +13,5 @@ using System.Runtime.InteropServices; [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: Guid("b8442186-4a70-7c85-704a-857cc7990797")] -[assembly: AssemblyVersion("1.3.6229")] -[assembly: AssemblyFileVersion("1.3.6229")] +[assembly: AssemblyVersion("1.4.6332")] +[assembly: AssemblyFileVersion("1.4.6332")] diff --git a/Source/FlaxEngine.Gen.h b/Source/FlaxEngine.Gen.h index b50ce5cc5..fae7510a9 100644 --- a/Source/FlaxEngine.Gen.h +++ b/Source/FlaxEngine.Gen.h @@ -3,11 +3,11 @@ #pragma once #define FLAXENGINE_NAME "FlaxEngine" -#define FLAXENGINE_VERSION Version(1, 3, 6229) -#define FLAXENGINE_VERSION_TEXT "1.3.6229" +#define FLAXENGINE_VERSION Version(1, 4, 6332) +#define FLAXENGINE_VERSION_TEXT "1.4.6332" #define FLAXENGINE_VERSION_MAJOR 1 -#define FLAXENGINE_VERSION_MINOR 3 -#define FLAXENGINE_VERSION_BUILD 6229 +#define FLAXENGINE_VERSION_MINOR 4 +#define FLAXENGINE_VERSION_BUILD 6332 #define FLAXENGINE_COMPANY "Flax" #define FLAXENGINE_COPYRIGHT "Copyright (c) 2012-2022 Wojciech Figat. All rights reserved." diff --git a/Source/Shaders/Collisions.hlsl b/Source/Shaders/Collisions.hlsl index e68871e27..4a62aebc4 100644 --- a/Source/Shaders/Collisions.hlsl +++ b/Source/Shaders/Collisions.hlsl @@ -18,4 +18,28 @@ bool RayHitRect(float3 r, float3 rectCenter, float3 rectX, float3 rectY, float3 return inExtentX && inExtentY; } +// Hits axis-aligned box (boxMin, boxMax) with a line (lineStart, lineEnd). +// Returns the intersections on the line (x - closest, y - furthest). +// Line hits the box if: intersections.x < intersections.y. +// Hit point is: hitPoint = lineStart + (lineEnd - lineStart) * intersections.x/y. +float2 LineHitBox(float3 lineStart, float3 lineEnd, float3 boxMin, float3 boxMax) +{ + float3 invDirection = 1.0f / (lineEnd - lineStart); + float3 enterIntersection = (boxMin - lineStart) * invDirection; + float3 exitIntersection = (boxMax - lineStart) * invDirection; + float3 minIntersections = min(enterIntersection, exitIntersection); + float3 maxIntersections = max(enterIntersection, exitIntersection); + float2 intersections; + intersections.x = max(minIntersections.x, max(minIntersections.y, minIntersections.z)); + intersections.y = min(maxIntersections.x, min(maxIntersections.y, maxIntersections.z)); + return saturate(intersections); +} + +// Determines whether there is an intersection between a box and a sphere. +bool BoxIntersectsSphere(float3 boxMin, float3 boxMax, float3 sphereCenter, float sphereRadius) +{ + const float3 clampedCenter = clamp(sphereCenter, boxMin, boxMax); + return distance(sphereCenter, clampedCenter) <= sphereRadius; +} + #endif diff --git a/Source/Shaders/Common.hlsl b/Source/Shaders/Common.hlsl index ed2951c7e..fd5fc19f4 100644 --- a/Source/Shaders/Common.hlsl +++ b/Source/Shaders/Common.hlsl @@ -204,6 +204,19 @@ float Luminance(float3 color) return dot(color, float3(0.299f, 0.587f, 0.114f)); } +// Quaternion multiplication (http://mathworld.wolfram.com/Quaternion.html) +float4 QuatMultiply(float4 q1, float4 q2) +{ + return float4(q2.xyz * q1.w + q1.xyz * q2.w + cross(q1.xyz, q2.xyz), q1.w * q2.w - dot(q1.xyz, q2.xyz)); +} + +// Vector rotation with a quaternion (http://mathworld.wolfram.com/Quaternion.html) +float3 QuatRotateVector(float4 q, float3 v) +{ + float4 nq = q * float4(-1, -1, -1, 1); + return QuatMultiply(q, QuatMultiply(float4(v, 0), nq)).xyz; +} + // Samples the unwrapped 3D texture (eg. volume texture of size 16x16x16 would be unwrapped to 256x16) float4 SampleUnwrappedTexture3D(Texture2D tex, SamplerState s, float3 uvw, float size) { diff --git a/Source/Shaders/GBuffer.hlsl b/Source/Shaders/GBuffer.hlsl index 0edcbef27..9ec390135 100644 --- a/Source/Shaders/GBuffer.hlsl +++ b/Source/Shaders/GBuffer.hlsl @@ -144,24 +144,6 @@ GBufferSample SampleGBufferFast(GBufferData gBuffer, float2 uv) return result; } -// Sample GBuffer normal vector, shading model and view space position -GBufferSample SampleGBufferNormalVPos(GBufferData gBuffer, float2 uv) -{ - GBufferSample result; - - // Sample GBuffer - float4 gBuffer1 = SAMPLE_RT(GBuffer1, uv); - - // Decode normal and shading model - result.Normal = DecodeNormal(gBuffer1.rgb); - result.ShadingModel = (int)(gBuffer1.a * 3.999); - - // Calculate view space position - result.ViewPos = GetViewPos(gBuffer, uv); - - return result; -} - #if defined(USE_GBUFFER_CUSTOM_DATA) // Sample GBuffer custom data only diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl new file mode 100644 index 000000000..b1ea4d5d5 --- /dev/null +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl @@ -0,0 +1,238 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "./Flax/Common.hlsl" +#include "./Flax/Collisions.hlsl" + +// This must match C++ +#define GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION 40 // Amount of chunks (in each direction) to split atlas draw distance for objects culling +#define GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE 4 +#define GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE 5 // Amount of float4s per-tile +#define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.1f // Cut-off value for tiles transitions blending during sampling +#define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes) + +struct GlobalSurfaceTile +{ + float4 AtlasRectUV; + float4x4 WorldToLocal; + float3 ViewBoundsSize; +}; + +struct GlobalSurfaceObject +{ + float3 BoundsPosition; + float BoundsRadius; + float4x4 WorldToLocal; + float3 Extent; + uint TileOffsets[6]; + uint DataSize; // count of float4s for object+tiles +}; + +float4 LoadGlobalSurfaceAtlasObjectBounds(Buffer objects, uint objectAddress) +{ + // This must match C++ + return objects.Load(objectAddress + 0); +} + +uint LoadGlobalSurfaceAtlasObjectDataSize(Buffer objects, uint objectAddress) +{ + // This must match C++ + return asuint(objects.Load(objectAddress + 1).w); +} + +GlobalSurfaceObject LoadGlobalSurfaceAtlasObject(Buffer objects, uint objectAddress) +{ + // This must match C++ + float4 vector0 = objects.Load(objectAddress + 0); + float4 vector1 = objects.Load(objectAddress + 1); + float4 vector2 = objects.Load(objectAddress + 2); + float4 vector3 = objects.Load(objectAddress + 3); + float4 vector4 = objects.Load(objectAddress + 4); + float4 vector5 = objects.Load(objectAddress + 5); // w unused + GlobalSurfaceObject object = (GlobalSurfaceObject)0; + object.BoundsPosition = vector0.xyz; + object.BoundsRadius = vector0.w; + object.WorldToLocal[0] = float4(vector2.xyz, 0.0f); + object.WorldToLocal[1] = float4(vector3.xyz, 0.0f); + object.WorldToLocal[2] = float4(vector4.xyz, 0.0f); + object.WorldToLocal[3] = float4(vector2.w, vector3.w, vector4.w, 1.0f); + object.Extent = vector5.xyz; + uint vector1x = asuint(vector1.x); + uint vector1y = asuint(vector1.y); + uint vector1z = asuint(vector1.z); + object.DataSize = asuint(vector1.w); + object.TileOffsets[0] = vector1x & 0xffff; + object.TileOffsets[1] = vector1x >> 16; + object.TileOffsets[2] = vector1y & 0xffff; + object.TileOffsets[3] = vector1y >> 16; + object.TileOffsets[4] = vector1z & 0xffff; + object.TileOffsets[5] = vector1z >> 16; + return object; +} + +GlobalSurfaceTile LoadGlobalSurfaceAtlasTile(Buffer objects, uint tileAddress) +{ + // This must match C++ + float4 vector0 = objects.Load(tileAddress + 0); + float4 vector1 = objects.Load(tileAddress + 1); + float4 vector2 = objects.Load(tileAddress + 2); + float4 vector3 = objects.Load(tileAddress + 3); + float4 vector4 = objects.Load(tileAddress + 4); // w unused + GlobalSurfaceTile tile = (GlobalSurfaceTile)0; + tile.AtlasRectUV = vector0.xyzw; + tile.WorldToLocal[0] = float4(vector1.xyz, 0.0f); + tile.WorldToLocal[1] = float4(vector2.xyz, 0.0f); + tile.WorldToLocal[2] = float4(vector3.xyz, 0.0f); + tile.WorldToLocal[3] = float4(vector1.w, vector2.w, vector3.w, 1.0f); + tile.ViewBoundsSize = vector4.xyz; + return tile; +} + +// Global Surface Atlas data for a constant buffer +struct GlobalSurfaceAtlasData +{ + float3 ViewPos; + float Padding0; + float Padding1; + float Resolution; + float ChunkSize; + uint ObjectsCount; +}; + +float3 SampleGlobalSurfaceAtlasTex(Texture2D atlas, float2 atlasUV, float4 bilinearWeights) +{ + float4 sampleX = atlas.GatherRed(SamplerLinearClamp, atlasUV); + float4 sampleY = atlas.GatherGreen(SamplerLinearClamp, atlasUV); + float4 sampleZ = atlas.GatherBlue(SamplerLinearClamp, atlasUV); + return float3(dot(sampleX, bilinearWeights), dot(sampleY, bilinearWeights), dot(sampleZ, bilinearWeights)); +} + +float4 SampleGlobalSurfaceAtlasTile(const GlobalSurfaceAtlasData data, GlobalSurfaceTile tile, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal, float surfaceThreshold) +{ + // Tile normal weight based on the sampling angle + float3 tileNormal = normalize(mul(worldNormal, (float3x3)tile.WorldToLocal)); + float normalWeight = saturate(dot(float3(0, 0, -1), tileNormal)); + normalWeight = (normalWeight - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) / (1.0f - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD); + if (normalWeight <= 0.0f) + return 0; + + // Get tile UV and depth at the world position + float3 tilePosition = mul(float4(worldPosition, 1), tile.WorldToLocal).xyz; + float tileDepth = tilePosition.z / tile.ViewBoundsSize.z; + float2 tileUV = saturate((tilePosition.xy / tile.ViewBoundsSize.xy) + 0.5f); + tileUV.y = 1.0 - tileUV.y; + float2 atlasUV = tileUV * tile.AtlasRectUV.zw + tile.AtlasRectUV.xy; + + // Calculate bilinear weights + float2 bilinearWeightsUV = frac(atlasUV * data.Resolution + 0.5f); + float4 bilinearWeights; + bilinearWeights.x = (1.0 - bilinearWeightsUV.x) * (bilinearWeightsUV.y); + bilinearWeights.y = (bilinearWeightsUV.x) * (bilinearWeightsUV.y); + bilinearWeights.z = (bilinearWeightsUV.x) * (1 - bilinearWeightsUV.y); + bilinearWeights.w = (1 - bilinearWeightsUV.x) * (1 - bilinearWeightsUV.y); + + // Tile depth weight based on sample position occlusion + float4 tileZ = depth.Gather(SamplerLinearClamp, atlasUV, 0.0f); + float depthThreshold = 2.0f * surfaceThreshold / tile.ViewBoundsSize.z; + float4 depthVisibility = 1.0f; + UNROLL + for (uint i = 0; i < 4; i++) + { + depthVisibility[i] = 1.0f - saturate((abs(tileDepth - tileZ[i]) - depthThreshold) / (0.5f * depthThreshold)); + if (tileZ[i] >= 1.0f) + depthVisibility[i] = 0.0f; + } + float sampleWeight = normalWeight * dot(depthVisibility, bilinearWeights); + if (sampleWeight <= 0.0f) + return 0; + bilinearWeights = depthVisibility * bilinearWeights; + //bilinearWeights = normalize(bilinearWeights); + + // Sample atlas texture + float3 sampleColor = SampleGlobalSurfaceAtlasTex(atlas, atlasUV, bilinearWeights); + + //return float4(sampleWeight.xxx, sampleWeight); + return float4(sampleColor.rgb * sampleWeight, sampleWeight); + //return float4(normalWeight.xxx, sampleWeight); +} + +// Samples the Global Surface Atlas and returns the lighting (with opacity) at the given world location (and direction). +// surfaceThreshold - Additional threshold (in world-units) between object or tile size compared with input data (error due to SDF or LOD incorrect appearance) +float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, ByteAddressBuffer chunks, Buffer culledObjects, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal, float surfaceThreshold = 20.0f) +{ + float4 result = float4(0, 0, 0, 0); + + // Snap to the closest chunk to get culled objects + uint3 chunkCoord = (uint3)clamp(floor((worldPosition - data.ViewPos) / data.ChunkSize + (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * 0.5f)), 0, GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION - 1); + uint chunkAddress = (chunkCoord.z * (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION) + chunkCoord.y * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION + chunkCoord.x) * 4; + uint objectsStart = chunks.Load(chunkAddress); + if (objectsStart == 0) + { + // Empty chunk + return result; + } + + // Read objects counter + float4 chunkHeader = culledObjects[objectsStart]; + objectsStart++; + uint objectsCount = asuint(chunkHeader.x); + if (objectsCount > data.ObjectsCount) // Prevents crashing - don't know why the data is invalid here (rare issue when moving fast though scene with terrain) + return result; + + // Loop over culled objects inside the chunk + LOOP + for (uint objectIndex = 0; objectIndex < objectsCount; objectIndex++) + { + // Cull point vs sphere + uint objectAddress = objectsStart; + float4 objectBounds = LoadGlobalSurfaceAtlasObjectBounds(culledObjects, objectAddress); + uint objectSize = LoadGlobalSurfaceAtlasObjectDataSize(culledObjects, objectAddress); + objectsStart += objectSize; + if (distance(objectBounds.xyz, worldPosition) > objectBounds.w) + continue; + GlobalSurfaceObject object = LoadGlobalSurfaceAtlasObject(culledObjects, objectAddress); + float3 localPosition = mul(float4(worldPosition, 1), object.WorldToLocal).xyz; + float3 localExtent = object.Extent + surfaceThreshold; + if (any(localPosition > localExtent) || any(localPosition < -localExtent)) + continue; + + // Remove the scale vector from the transformation matrix + float3x3 worldToLocal = (float3x3)object.WorldToLocal; + float scaleX = length(worldToLocal[0]); + float scaleY = length(worldToLocal[1]); + float scaleZ = length(worldToLocal[2]); + float3 invScale = float3( + scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, + scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, + scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); + worldToLocal[0] *= invScale.x; + worldToLocal[1] *= invScale.y; + worldToLocal[2] *= invScale.z; + + // Sample tiles based on the directionality + float3 localNormal = normalize(mul(worldNormal, worldToLocal)); + float3 localNormalSq = localNormal * localNormal; + uint tileOffset = object.TileOffsets[localNormal.x > 0.0f ? 0 : 1]; + if (localNormalSq.x > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) + { + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } + tileOffset = object.TileOffsets[localNormal.y > 0.0f ? 2 : 3]; + if (localNormalSq.y > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) + { + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } + tileOffset = object.TileOffsets[localNormal.z > 0.0f ? 4 : 5]; + if (localNormalSq.z > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) + { + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } + } + + // Normalize result + result.rgb /= max(result.a, 0.0001f); + + return result; +} diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader new file mode 100644 index 000000000..b9e805e80 --- /dev/null +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -0,0 +1,275 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +// Diffuse-only lighting +#define NO_SPECULAR + +#include "./Flax/Common.hlsl" +#include "./Flax/Math.hlsl" +#include "./Flax/LightingCommon.hlsl" +#include "./Flax/GlobalSignDistanceField.hlsl" +#include "./Flax/GI/GlobalSurfaceAtlas.hlsl" + +META_CB_BEGIN(0, Data) +float3 ViewWorldPos; +float ViewNearPlane; +float Padding00; +uint CulledObjectsCapacity; +float LightShadowsStrength; +float ViewFarPlane; +float4 ViewFrustumWorldRays[4]; +GlobalSDFData GlobalSDF; +GlobalSurfaceAtlasData GlobalSurfaceAtlas; +LightData Light; +META_CB_END + +struct AtlasVertexInput +{ + float2 Position : POSITION0; + float2 TileUV : TEXCOORD0; + uint TileAddress : TEXCOORD1; +}; + +struct AtlasVertexOutput +{ + float4 Position : SV_Position; + float2 TileUV : TEXCOORD0; + nointerpolation uint TileAddress : TEXCOORD1; +}; + +// Vertex shader for Global Surface Atlas rendering (custom vertex buffer to render per-tile) +META_VS(true, FEATURE_LEVEL_SM5) +META_VS_IN_ELEMENT(POSITION, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 1, R32_UINT, 0, ALIGN, PER_VERTEX, 0, true) +AtlasVertexOutput VS_Atlas(AtlasVertexInput input) +{ + AtlasVertexOutput output; + output.Position = float4(input.Position, 1, 1); + output.TileUV = input.TileUV; + output.TileAddress = input.TileAddress; + return output; +} + +// Pixel shader for Global Surface Atlas software clearing +META_PS(true, FEATURE_LEVEL_SM5) +void PS_Clear(out float4 Light : SV_Target0, out float4 RT0 : SV_Target1, out float4 RT1 : SV_Target2, out float4 RT2 : SV_Target3) +{ + Light = float4(0, 0, 0, 0); + RT0 = float4(0, 0, 0, 0); + RT1 = float4(0, 0, 0, 0); + RT2 = float4(1, 0, 0, 0); +} + +#ifdef _PS_DirectLighting + +#include "./Flax/GBuffer.hlsl" +#include "./Flax/Matrix.hlsl" +#include "./Flax/Lighting.hlsl" + +// GBuffer+Depth at 0-3 slots +Buffer GlobalSurfaceAtlasObjects : register(t4); +Texture3D GlobalSDFTex[4] : register(t5); +Texture3D GlobalSDFMip[4] : register(t9); + +// Pixel shader for Global Surface Atlas shading with direct light contribution +META_PS(true, FEATURE_LEVEL_SM5) +META_PERMUTATION_1(RADIAL_LIGHT=0) +META_PERMUTATION_1(RADIAL_LIGHT=1) +float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target +{ + // Load current tile info + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(GlobalSurfaceAtlasObjects, input.TileAddress); + float2 atlasUV = input.TileUV * tile.AtlasRectUV.zw + tile.AtlasRectUV.xy; + + // Load GBuffer sample from atlas + GBufferData gBufferData = (GBufferData)0; + GBufferSample gBuffer = SampleGBuffer(gBufferData, atlasUV); + BRANCH + if (gBuffer.ShadingModel == SHADING_MODEL_UNLIT) + { + // Skip unlit pixels + discard; + return 0; + } + + // Reconstruct world-space position manually (from uv+depth within a tile) + float tileDepth = SampleZ(atlasUV); + //float tileNear = -GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET; + //float tileFar = tile.ViewBoundsSize.z + 2 * GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET; + //gBufferData.ViewInfo.zw = float2(tileFar / (tileFar - tileNear), (-tileFar * tileNear) / (tileFar - tileNear) / tileFar); + //gBufferData.ViewInfo.zw = float2(1, 0); + //float tileLinearDepth = LinearizeZ(gBufferData, tileDepth); + float3 tileSpacePos = float3(input.TileUV.x - 0.5f, 0.5f - input.TileUV.y, tileDepth); + float3 gBufferTilePos = tileSpacePos * tile.ViewBoundsSize; + float4x4 tileLocalToWorld = Inverse(tile.WorldToLocal); + gBuffer.WorldPos = mul(float4(gBufferTilePos, 1), tileLocalToWorld).xyz; + + // Calculate shadowing + float3 L = Light.Direction; +#if RADIAL_LIGHT + float3 toLight = Light.Position - gBuffer.WorldPos; + float toLightDst = length(toLight); + if (toLightDst >= Light.Radius) + { + // Skip texels outside the light influence range + discard; + return 0; + } + L = toLight / toLightDst; +#else + float toLightDst = GLOBAL_SDF_WORLD_SIZE; +#endif + float4 shadowMask = 1; + if (Light.CastShadows > 0) + { + float NoL = dot(gBuffer.Normal, L); + float shadowBias = 10.0f; + float bias = 2 * shadowBias * saturate(1 - NoL) + shadowBias; + BRANCH + if (NoL > 0) + { + // TODO: try using shadow map for on-screen pixels + // TODO: try using cone trace with Global SDF for smoother shadow (eg. for sun shadows or for area lights) + + // Shot a ray from texel into the light to see if there is any occluder + GlobalSDFTrace trace; + trace.Init(gBuffer.WorldPos + gBuffer.Normal * shadowBias, L, bias, toLightDst - bias); + GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace); + shadowMask = hit.IsHit() ? LightShadowsStrength : 1; + } + else + { + shadowMask = 0; + } + } + + // Calculate lighting +#if RADIAL_LIGHT + bool isSpotLight = Light.SpotAngles.x > -2.0f; +#else + bool isSpotLight = false; +#endif + float4 light = GetLighting(ViewWorldPos, Light, gBuffer, shadowMask, RADIAL_LIGHT, isSpotLight); + + return light; +} + +#endif + +#if defined(_CS_CullObjects) + +#include "./Flax/Collisions.hlsl" + +RWByteAddressBuffer RWGlobalSurfaceAtlasChunks : register(u0); +RWBuffer RWGlobalSurfaceAtlasCulledObjects : register(u1); +Buffer GlobalSurfaceAtlasObjects : register(t0); + +// Compute shader for culling objects into chunks +META_CS(true, FEATURE_LEVEL_SM5) +[numthreads(GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE, GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE, GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE)] +void CS_CullObjects(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) +{ + uint3 chunkCoord = DispatchThreadId; + uint chunkAddress = (chunkCoord.z * (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION) + chunkCoord.y * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION + chunkCoord.x) * 4; + if (chunkAddress == 0) + return; // Skip chunk at 0,0,0 (used for counter) + float3 chunkMin = GlobalSurfaceAtlas.ViewPos + (chunkCoord - (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * 0.5f)) * GlobalSurfaceAtlas.ChunkSize; + float3 chunkMax = chunkMin + GlobalSurfaceAtlas.ChunkSize; + + // Count objects data size in this chunk (amount of float4s) + uint objectsSize = 0, objectAddress = 0, objectsCount = 0; + // TODO: maybe cache 20-30 culled object indices in thread memory to skip culling them again when copying data (maybe reude chunk size to get smaller objects count per chunk)? + LOOP + for (uint objectIndex = 0; objectIndex < GlobalSurfaceAtlas.ObjectsCount; objectIndex++) + { + float4 objectBounds = LoadGlobalSurfaceAtlasObjectBounds(GlobalSurfaceAtlasObjects, objectAddress); + uint objectSize = LoadGlobalSurfaceAtlasObjectDataSize(GlobalSurfaceAtlasObjects, objectAddress); + if (BoxIntersectsSphere(chunkMin, chunkMax, objectBounds.xyz, objectBounds.w)) + { + objectsSize += objectSize; + objectsCount++; + } + objectAddress += objectSize; + } + if (objectsSize == 0) + { + // Empty chunk + RWGlobalSurfaceAtlasChunks.Store(chunkAddress, 0); + return; + } + objectsSize++; // Include objects count before actual objects data + + // Allocate object data size in the buffer + uint objectsStart; + RWGlobalSurfaceAtlasChunks.InterlockedAdd(0, objectsSize, objectsStart); + if (objectsStart + objectsSize > CulledObjectsCapacity) + { + // Not enough space in the buffer + RWGlobalSurfaceAtlasChunks.Store(chunkAddress, 0); + return; + } + + // Write object data start + RWGlobalSurfaceAtlasChunks.Store(chunkAddress, objectsStart); + + // Write objects count before actual objects data + RWGlobalSurfaceAtlasCulledObjects[objectsStart] = float4(asfloat(objectsCount), 0, 0, 0); + objectsStart++; + + // Copy objects data in this chunk + objectAddress = 0; + LOOP + for (uint objectIndex = 0; objectIndex < GlobalSurfaceAtlas.ObjectsCount; objectIndex++) + { + float4 objectBounds = LoadGlobalSurfaceAtlasObjectBounds(GlobalSurfaceAtlasObjects, objectAddress); + uint objectSize = LoadGlobalSurfaceAtlasObjectDataSize(GlobalSurfaceAtlasObjects, objectAddress); + if (BoxIntersectsSphere(chunkMin, chunkMax, objectBounds.xyz, objectBounds.w)) + { + for (uint i = 0; i < objectSize; i++) + { + RWGlobalSurfaceAtlasCulledObjects[objectsStart + i] = GlobalSurfaceAtlasObjects[objectAddress + i]; + } + objectsStart += objectSize; + } + objectAddress += objectSize; + } +} + +#endif + +#ifdef _PS_Debug + +Texture3D GlobalSDFTex[4] : register(t0); +Texture3D GlobalSDFMip[4] : register(t4); +ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t8); +Buffer GlobalSurfaceAtlasCulledObjects : register(t9); +Texture2D GlobalSurfaceAtlasDepth : register(t10); +Texture2D GlobalSurfaceAtlasTex : register(t11); + +// Pixel shader for Global Surface Atlas debug drawing +META_PS(true, FEATURE_LEVEL_SM5) +float4 PS_Debug(Quad_VS2PS input) : SV_Target +{ +#if 0 + // Preview Global Surface Atlas texture + return float4(GlobalSurfaceAtlasTex.SampleLevel(SamplerLinearClamp, input.TexCoord, 0).rgb, 1); +#endif + + // Shot a ray from camera into the Global SDF + GlobalSDFTrace trace; + float3 viewRay = lerp(lerp(ViewFrustumWorldRays[3], ViewFrustumWorldRays[0], input.TexCoord.x), lerp(ViewFrustumWorldRays[2], ViewFrustumWorldRays[1], input.TexCoord.x), 1 - input.TexCoord.y).xyz; + viewRay = normalize(viewRay - ViewWorldPos); + trace.Init(ViewWorldPos, viewRay, ViewNearPlane, ViewFarPlane); + trace.NeedsHitNormal = true; + GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace); + if (!hit.IsHit()) + return float4(float3(0.4f, 0.4f, 1.0f) * saturate(hit.StepsCount / 80.0f), 1); + //return float4(hit.HitNormal * 0.5f + 0.5f, 1); + + // Sample Global Surface Atlas at the hit location + float surfaceThreshold = hit.HitCascade * 10.0f + 20.0f; // Scale the threshold based on the hit cascade (less precision) + float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay, surfaceThreshold); + return float4(surfaceColor.rgb, 1); +} + +#endif diff --git a/Source/Shaders/GlobalSignDistanceField.hlsl b/Source/Shaders/GlobalSignDistanceField.hlsl new file mode 100644 index 000000000..f851fc192 --- /dev/null +++ b/Source/Shaders/GlobalSignDistanceField.hlsl @@ -0,0 +1,204 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "./Flax/Common.hlsl" +#include "./Flax/Collisions.hlsl" + +#define GLOBAL_SDF_RASTERIZE_CHUNK_SIZE 32 +#define GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN 4 +#define GLOBAL_SDF_MIP_FLOODS 5 +#define GLOBAL_SDF_WORLD_SIZE 60000.0f + +// Global SDF data for a constant buffer +struct GlobalSDFData +{ + float4 CascadePosDistance[4]; + float4 CascadeVoxelSize; + float3 Padding; + float Resolution; +}; + +// Global SDF ray trace settings. +struct GlobalSDFTrace +{ + float3 WorldPosition; + float MinDistance; + float3 WorldDirection; + float MaxDistance; + float StepScale; + bool NeedsHitNormal; + + void Init(float3 worldPosition, float3 worldDirection, float minDistance, float maxDistance, float stepScale = 1.0f) + { + WorldPosition = worldPosition; + WorldDirection = worldDirection; + MinDistance = minDistance; + MaxDistance = maxDistance; + StepScale = stepScale; + NeedsHitNormal = false; + } +}; + +// Global SDF ray trace hit information. +struct GlobalSDFHit +{ + float3 HitNormal; + float HitTime; + uint HitCascade; + uint StepsCount; + + bool IsHit() + { + return HitTime >= 0.0f; + } + + float3 GetHitPosition(const GlobalSDFTrace trace) + { + return trace.WorldPosition + trace.WorldDirection * HitTime; + } +}; + +// Samples the Global SDF and returns the distance to the closest surface (in world units) at the given world location. +float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex[4], float3 worldPosition) +{ + float distance = data.CascadePosDistance[3].w * 2.0f; + if (distance <= 0.0f) + return GLOBAL_SDF_WORLD_SIZE; + UNROLL + for (uint cascade = 0; cascade < 4; cascade++) + { + float4 cascadePosDistance = data.CascadePosDistance[cascade]; + float cascadeMaxDistance = cascadePosDistance.w * 2; + float3 posInCascade = worldPosition - cascadePosDistance.xyz; + float3 cascadeUV = posInCascade / cascadeMaxDistance + 0.5f; + float cascadeDistance = tex[cascade].SampleLevel(SamplerLinearClamp, cascadeUV, 0); + if (cascadeDistance < 1.0f && !any(cascadeUV < 0) && !any(cascadeUV > 1)) + { + distance = cascadeDistance * cascadeMaxDistance; + break; + } + } + return distance; +} + +// Samples the Global SDF and returns the gradient vector (derivative) at the given world location. Normalize it to get normal vector. +float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex[4], float3 worldPosition, out float distance) +{ + float3 gradient = float3(0, 0.00001f, 0); + distance = GLOBAL_SDF_WORLD_SIZE; + if (data.CascadePosDistance[3].w <= 0.0f) + return gradient; + UNROLL + for (uint cascade = 0; cascade < 4; cascade++) + { + float4 cascadePosDistance = data.CascadePosDistance[cascade]; + float cascadeMaxDistance = cascadePosDistance.w * 2; + float3 posInCascade = worldPosition - cascadePosDistance.xyz; + float3 cascadeUV = posInCascade / cascadeMaxDistance + 0.5f; + float cascadeDistance = tex[cascade].SampleLevel(SamplerLinearClamp, cascadeUV, 0); + if (cascadeDistance < 0.9f && !any(cascadeUV < 0) && !any(cascadeUV > 1)) + { + float texelOffset = 1.0f / data.Resolution; + float xp = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x + texelOffset, cascadeUV.y, cascadeUV.z), 0).x; + float xn = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x - texelOffset, cascadeUV.y, cascadeUV.z), 0).x; + float yp = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y + texelOffset, cascadeUV.z), 0).x; + float yn = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y - texelOffset, cascadeUV.z), 0).x; + float zp = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y, cascadeUV.z + texelOffset), 0).x; + float zn = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y, cascadeUV.z - texelOffset), 0).x; + gradient = float3(xp - xn, yp - yn, zp - zn) * cascadeMaxDistance; + distance = cascadeDistance * cascadeMaxDistance; + break; + } + } + return gradient; +} + +// Ray traces the Global SDF. +GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex[4], Texture3D mips[4], const GlobalSDFTrace trace) +{ + GlobalSDFHit hit = (GlobalSDFHit)0; + hit.HitTime = -1.0f; + float chunkSizeDistance = (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / data.Resolution; // Size of the chunk in SDF distance (0-1) + float chunkMarginDistance = (float)GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN / data.Resolution; // Size of the chunk margin in SDF distance (0-1) + float nextIntersectionStart = 0.0f; + float traceMaxDistance = min(trace.MaxDistance, data.CascadePosDistance[3].w * 2); + float3 traceEndPosition = trace.WorldPosition + trace.WorldDirection * traceMaxDistance; + UNROLL + for (uint cascade = 0; cascade < 4 && hit.HitTime < 0.0f; cascade++) + { + float4 cascadePosDistance = data.CascadePosDistance[cascade]; + float cascadeMaxDistance = cascadePosDistance.w * 2; + float voxelSize = data.CascadeVoxelSize[cascade]; + float voxelExtent = voxelSize * 0.5f; + float cascadeMinStep = voxelSize; + + // Hit the cascade bounds to find the intersection points + float2 intersections = LineHitBox(trace.WorldPosition, traceEndPosition, cascadePosDistance.xyz - cascadePosDistance.www, cascadePosDistance.xyz + cascadePosDistance.www); + intersections.xy *= traceMaxDistance; + intersections.x = max(intersections.x, nextIntersectionStart); + float stepTime = intersections.x; + if (intersections.x >= intersections.y) + { + // Skip the current cascade if the ray starts outside it + stepTime = intersections.y; + } + else + { + // Skip the current cascade tracing on the next cascade + nextIntersectionStart = intersections.y; + } + + // Walk over the cascade SDF + uint step = 0; + LOOP + for (; step < 250 && stepTime < intersections.y; step++) + { + float3 stepPosition = trace.WorldPosition + trace.WorldDirection * stepTime; + + // Sample SDF + float3 posInCascade = stepPosition - cascadePosDistance.xyz; + float3 cascadeUV = posInCascade / cascadeMaxDistance + 0.5f; + float stepDistance = mips[cascade].SampleLevel(SamplerLinearClamp, cascadeUV, 0); + if (stepDistance < chunkSizeDistance) + { + float stepDistanceTex = tex[cascade].SampleLevel(SamplerLinearClamp, cascadeUV, 0); + if (stepDistanceTex < chunkMarginDistance * 2) + { + stepDistance = stepDistanceTex; + } + } + else + { + // Assume no SDF nearby so perform a jump + stepDistance = chunkSizeDistance; + } + stepDistance *= cascadeMaxDistance; + + // Detect surface hit + float minSurfaceThickness = voxelExtent * saturate(stepTime / (voxelExtent * 2.0f)); + if (stepDistance < minSurfaceThickness) + { + // Surface hit + hit.HitTime = max(stepTime + stepDistance - minSurfaceThickness, 0.0f); + hit.HitCascade = cascade; + if (trace.NeedsHitNormal) + { + // Calculate hit normal from SDF gradient + float texelOffset = 1.0f / data.Resolution; + float xp = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x + texelOffset, cascadeUV.y, cascadeUV.z), 0).x; + float xn = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x - texelOffset, cascadeUV.y, cascadeUV.z), 0).x; + float yp = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y + texelOffset, cascadeUV.z), 0).x; + float yn = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y - texelOffset, cascadeUV.z), 0).x; + float zp = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y, cascadeUV.z + texelOffset), 0).x; + float zn = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y, cascadeUV.z - texelOffset), 0).x; + hit.HitNormal = normalize(float3(xp - xn, yp - yn, zp - zn)); + } + break; + } + + // Move forward + stepTime += max(stepDistance * trace.StepScale, cascadeMinStep); + } + hit.StepsCount += step; + } + return hit; +} diff --git a/Source/Shaders/GlobalSignDistanceField.shader b/Source/Shaders/GlobalSignDistanceField.shader new file mode 100644 index 000000000..4c2ad666e --- /dev/null +++ b/Source/Shaders/GlobalSignDistanceField.shader @@ -0,0 +1,275 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "./Flax/Common.hlsl" +#include "./Flax/Math.hlsl" +#include "./Flax/GlobalSignDistanceField.hlsl" + +#define GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT 28 +#define GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT 2 +#define GLOBAL_SDF_RASTERIZE_GROUP_SIZE 8 +#define GLOBAL_SDF_MIP_GROUP_SIZE 4 + +struct ObjectRasterizeData +{ + float4x4 WorldToVolume; // TODO: use 3x4 matrix + float4x4 VolumeToWorld; // TODO: use 3x4 matrix + float3 VolumeToUVWMul; + float MipOffset; + float3 VolumeToUVWAdd; + float DecodeMul; + float3 VolumeLocalBoundsExtent; + float DecodeAdd; +}; + +META_CB_BEGIN(0, Data) +float3 ViewWorldPos; +float ViewNearPlane; +float3 Padding00; +float ViewFarPlane; +float4 ViewFrustumWorldRays[4]; +GlobalSDFData GlobalSDF; +META_CB_END + +META_CB_BEGIN(1, ModelsRasterizeData) +int3 ChunkCoord; +float MaxDistance; +float3 CascadeCoordToPosMul; +int ObjectsCount; +float3 CascadeCoordToPosAdd; +int CascadeResolution; +float Padding0; +float CascadeVoxelSize; +int CascadeMipResolution; +int CascadeMipFactor; +uint4 Objects[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT / 4]; +META_CB_END + +float CombineDistanceToSDF(float sdf, float distanceToSDF) +{ + // Simple sum (aprox) + //return sdf + distanceToSDF; + + // Negative distinace inside the SDF + if (sdf <= 0 && distanceToSDF <= 0) return sdf; + + // Worst-case scenario with triangle edge (C^2 = A^2 + B^2) + return sqrt(Square(max(sdf, 0)) + Square(distanceToSDF)); +} + +#if defined(_CS_RasterizeModel) || defined(_CS_RasterizeHeightfield) + +RWTexture3D GlobalSDFTex : register(u0); +StructuredBuffer ObjectsBuffer : register(t0); + +#endif + +#if defined(_CS_RasterizeModel) + +Texture3D ObjectsTextures[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT] : register(t1); + +float DistanceToModelSDF(float minDistance, ObjectRasterizeData modelData, Texture3D modelSDFTex, float3 worldPos) +{ + // Compute SDF volume UVs and distance in world-space to the volume bounds + float3 volumePos = mul(float4(worldPos, 1), modelData.WorldToVolume).xyz; + float3 volumeUV = volumePos * modelData.VolumeToUVWMul + modelData.VolumeToUVWAdd; + float3 volumePosClamped = clamp(volumePos, -modelData.VolumeLocalBoundsExtent, modelData.VolumeLocalBoundsExtent); + float3 worldPosClamped = mul(float4(volumePosClamped, 1), modelData.VolumeToWorld).xyz; + float distanceToVolume = distance(worldPos, worldPosClamped); + + // Skip sampling SDF if there is already a better result + BRANCH if (minDistance <= distanceToVolume) return distanceToVolume; + + // Sample SDF + float volumeDistance = modelSDFTex.SampleLevel(SamplerLinearClamp, volumeUV, modelData.MipOffset).x * modelData.DecodeMul + modelData.DecodeAdd; + + // Combine distance to the volume with distance to the surface inside the model + float result = CombineDistanceToSDF(volumeDistance, distanceToVolume); + if (distanceToVolume > 0) + { + // Prevent negative distance outside the model + result = max(distanceToVolume, result); + } + return result; +} + +// Compute shader for rasterizing model SDF into Global SDF +META_CS(true, FEATURE_LEVEL_SM5) +META_PERMUTATION_1(READ_SDF=0) +META_PERMUTATION_1(READ_SDF=1) +[numthreads(GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE)] +void CS_RasterizeModel(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) +{ + uint3 voxelCoord = ChunkCoord + DispatchThreadId; + float3 voxelWorldPos = voxelCoord * CascadeCoordToPosMul + CascadeCoordToPosAdd; + float minDistance = MaxDistance; +#if READ_SDF + minDistance *= GlobalSDFTex[voxelCoord]; +#endif + for (uint i = 0; i < ObjectsCount; i++) + { + ObjectRasterizeData objectData = ObjectsBuffer[Objects[i / 4][i % 4]]; + float objectDistance = DistanceToModelSDF(minDistance, objectData, ObjectsTextures[i], voxelWorldPos); + minDistance = min(minDistance, objectDistance); + } + GlobalSDFTex[voxelCoord] = saturate(minDistance / MaxDistance); +} + +#endif + +#if defined(_CS_RasterizeHeightfield) + +Texture2D ObjectsTextures[GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT] : register(t1); + +// Compute shader for rasterizing heightfield into Global SDF +META_CS(true, FEATURE_LEVEL_SM5) +[numthreads(GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE)] +void CS_RasterizeHeightfield(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) +{ + uint3 voxelCoord = ChunkCoord + DispatchThreadId; + float3 voxelWorldPos = voxelCoord * CascadeCoordToPosMul + CascadeCoordToPosAdd; + float minDistance = MaxDistance * GlobalSDFTex[voxelCoord]; + float thickness = CascadeVoxelSize * -8; + for (uint i = 0; i < ObjectsCount; i++) + { + ObjectRasterizeData objectData = ObjectsBuffer[Objects[i / 4][i % 4]]; + + // Convert voxel world-space position into heightfield local-space position and get heightfield UV + float3 volumePos = mul(float4(voxelWorldPos, 1), objectData.WorldToVolume).xyz; + float3 volumeUV = volumePos * objectData.VolumeToUVWMul + objectData.VolumeToUVWAdd; + float2 heightfieldUV = float2(volumeUV.x, volumeUV.z); + + // Sample the heightfield + float4 heightmapValue = ObjectsTextures[i].SampleLevel(SamplerLinearClamp, heightfieldUV, objectData.MipOffset); + bool isHole = (heightmapValue.b + heightmapValue.a) >= 1.9f; + if (isHole || any(heightfieldUV < 0.0f) || any(heightfieldUV > 1.0f)) + continue; + float height = (float)((int)(heightmapValue.x * 255.0) + ((int)(heightmapValue.y * 255) << 8)) / 65535.0; + float2 positionXZ = volumePos.xz; + float3 position = float3(positionXZ.x, height, positionXZ.y); + float3 heightfieldPosition = mul(float4(position, 1), objectData.VolumeToWorld).xyz; + float3 heightfieldNormal = normalize(float3(objectData.VolumeToWorld[0].y, objectData.VolumeToWorld[1].y, objectData.VolumeToWorld[2].y)); + + // Calculate distance from voxel center to the heightfield + float objectDistance = dot(heightfieldNormal, voxelWorldPos - heightfieldPosition); + if (objectDistance < thickness) + objectDistance = thickness - objectDistance; + minDistance = min(minDistance, objectDistance); + } + GlobalSDFTex[voxelCoord] = saturate(minDistance / MaxDistance); +} + +#endif + +#if defined(_CS_ClearChunk) + +RWTexture3D GlobalSDFTex : register(u0); + +// Compute shader for clearing Global SDF chunk +META_CS(true, FEATURE_LEVEL_SM5) +[numthreads(GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE)] +void CS_ClearChunk(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) +{ + uint3 voxelCoord = ChunkCoord + DispatchThreadId; + GlobalSDFTex[voxelCoord] = 1.0f; +} + +#endif + +#if defined(_CS_GenerateMip) + +RWTexture3D GlobalSDFMip : register(u0); +Texture3D GlobalSDFTex : register(t0); + +float SampleSDF(uint3 voxelCoordMip, int3 offset) +{ +#if SAMPLE_MIP + // Sampling Global SDF Mip + float resolution = CascadeMipResolution; +#else + // Sampling Global SDF Tex + voxelCoordMip *= CascadeMipFactor; + float resolution = CascadeResolution; +#endif + + // Sample SDF + voxelCoordMip = (uint3)clamp((int3)voxelCoordMip + offset, 0, resolution - 1); + float result = GlobalSDFTex[voxelCoordMip].r; + + // Extend by distance to the sampled texel location + float distanceInWorldUnits = length(offset) * (MaxDistance / resolution); + float distanceToVoxel = distanceInWorldUnits / MaxDistance; + result = CombineDistanceToSDF(result, distanceToVoxel); + + return result; +} + +// Compute shader for generating mip for Global SDF (uses flood fill algorithm) +META_CS(true, FEATURE_LEVEL_SM5) +META_PERMUTATION_1(SAMPLE_MIP=0) +META_PERMUTATION_1(SAMPLE_MIP=1) +[numthreads(GLOBAL_SDF_MIP_GROUP_SIZE, GLOBAL_SDF_MIP_GROUP_SIZE, GLOBAL_SDF_MIP_GROUP_SIZE)] +void CS_GenerateMip(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) +{ + uint3 voxelCoordMip = DispatchThreadId; + float minDistance = SampleSDF(voxelCoordMip, int3(0, 0, 0)); + + // Find the distance to the closest surface by sampling the nearby area (flood fill) + minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(1, 0, 0))); + minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(0, 1, 0))); + minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(0, 0, 1))); + minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(-1, 0, 0))); + minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(0, -1, 0))); + minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(0, 0, -1))); + + GlobalSDFMip[voxelCoordMip] = minDistance; +} + +#endif + +#ifdef _PS_Debug + +Texture3D GlobalSDFTex[4] : register(t0); +Texture3D GlobalSDFMip[4] : register(t4); + +// Pixel shader for Global SDF debug drawing +META_PS(true, FEATURE_LEVEL_SM5) +float4 PS_Debug(Quad_VS2PS input) : SV_Target +{ +#if 0 + // Preview Global SDF slice + float zSlice = 0.6f; + float mip = 0; + uint cascade = 0; + float distance01 = GlobalSDFTex[cascade].SampleLevel(SamplerLinearClamp, float3(input.TexCoord, zSlice), mip).x; + //float distance01 = GlobalSDFMip[cascade].SampleLevel(SamplerLinearClamp, float3(input.TexCoord, zSlice), mip).x; + float distance = distance01 * GlobalSDF.CascadePosDistance[cascade].w; + if (abs(distance) < 1) + return float4(1, 0, 0, 1); + if (distance01 < 0) + return float4(0, 0, 1 - distance01, 1); + return float4(0, 1 - distance01, 0, 1); +#endif + + // Shot a ray from camera into the Global SDF + GlobalSDFTrace trace; + float3 viewRay = lerp(lerp(ViewFrustumWorldRays[3], ViewFrustumWorldRays[0], input.TexCoord.x), lerp(ViewFrustumWorldRays[2], ViewFrustumWorldRays[1], input.TexCoord.x), 1 - input.TexCoord.y).xyz; + viewRay = normalize(viewRay - ViewWorldPos); + trace.Init(ViewWorldPos, viewRay, ViewNearPlane, ViewFarPlane); + GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace); + + // Debug draw + float3 color = saturate(hit.StepsCount / 80.0f).xxx; + if (!hit.IsHit()) + color.rg *= 0.4f; +#if 0 + else + { + // Debug draw SDF normals + float dst; + color.rgb = normalize(SampleGlobalSDFGradient(GlobalSDF, GlobalSDFTex, hit.GetHitPosition(trace), dst)) * 0.5f + 0.5f; + } +#endif + return float4(color, 1); +} + +#endif diff --git a/Source/Shaders/Lighting.hlsl b/Source/Shaders/Lighting.hlsl index 9b81504a6..4a1cabac6 100644 --- a/Source/Shaders/Lighting.hlsl +++ b/Source/Shaders/Lighting.hlsl @@ -3,10 +3,6 @@ #ifndef __LIGHTING__ #define __LIGHTING__ -#if !defined(USE_GBUFFER_CUSTOM_DATA) -#error "Cannot calculate lighting without custom data in GBuffer. Define USE_GBUFFER_CUSTOM_DATA." -#endif - #include "./Flax/LightingCommon.hlsl" ShadowData GetShadow(LightData lightData, GBufferSample gBuffer, float4 shadowMask) @@ -20,21 +16,23 @@ ShadowData GetShadow(LightData lightData, GBufferSample gBuffer, float4 shadowMa LightingData StandardShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) { float3 diffuseColor = GetDiffuseColor(gBuffer); - float3 specularColor = GetSpecularColor(gBuffer); - float3 H = normalize(V + L); float NoL = saturate(dot(N, L)); float NoV = max(dot(N, V), 1e-5); float NoH = saturate(dot(N, H)); float VoH = saturate(dot(V, H)); - float D = D_GGX(gBuffer.Roughness, NoH) * energy; - float Vis = Vis_SmithJointApprox(gBuffer.Roughness, NoV, NoL); - float3 F = F_Schlick(specularColor, VoH); - LightingData lighting; lighting.Diffuse = Diffuse_Lambert(diffuseColor); +#if defined(NO_SPECULAR) + lighting.Specular = 0; +#else + float3 specularColor = GetSpecularColor(gBuffer); + float3 F = F_Schlick(specularColor, VoH); + float D = D_GGX(gBuffer.Roughness, NoH) * energy; + float Vis = Vis_SmithJointApprox(gBuffer.Roughness, NoV, NoL); lighting.Specular = (D * Vis) * F; +#endif lighting.Transmission = 0; return lighting; } @@ -42,7 +40,7 @@ LightingData StandardShading(GBufferSample gBuffer, float energy, float3 L, floa LightingData SubsurfaceShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) { LightingData lighting = StandardShading(gBuffer, energy, L, V, N); - +#if defined(USE_GBUFFER_CUSTOM_DATA) // Fake effect of the light going through the material float3 subsurfaceColor = gBuffer.CustomData.rgb; float opacity = gBuffer.CustomData.a; @@ -51,21 +49,21 @@ LightingData SubsurfaceShading(GBufferSample gBuffer, float energy, float3 L, fl float normalContribution = saturate(dot(N, H) * opacity + 1.0f - opacity); float backScatter = gBuffer.AO * normalContribution / (PI * 2.0f); lighting.Transmission = lerp(backScatter, 1, inscatter) * subsurfaceColor; - +#endif return lighting; } LightingData FoliageShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) { LightingData lighting = StandardShading(gBuffer, energy, L, V, N); - +#if defined(USE_GBUFFER_CUSTOM_DATA) // Fake effect of the light going through the thin foliage float3 subsurfaceColor = gBuffer.CustomData.rgb; float wrapNoL = saturate((-dot(N, L) + 0.5f) / 2.25); float VoL = dot(V, L); float scatter = D_GGX(0.36, saturate(-VoL)); lighting.Transmission = subsurfaceColor * (wrapNoL * scatter); - +#endif return lighting; } @@ -144,9 +142,6 @@ float4 GetLighting(float3 viewPos, LightData lightData, GBufferSample gBuffer, f // Calculate direct lighting LightingData lighting = SurfaceShading(gBuffer, energy, L, V, N); -#if NO_SPECULAR - lighting.Specular = float3(0, 0, 0); -#endif // Calculate final light color float3 surfaceLight = (lighting.Diffuse + lighting.Specular) * (NoL * attenuation * shadow.SurfaceShadow); diff --git a/Source/Shaders/MaterialCommon.hlsl b/Source/Shaders/MaterialCommon.hlsl index 40a83ca5c..a18e29f81 100644 --- a/Source/Shaders/MaterialCommon.hlsl +++ b/Source/Shaders/MaterialCommon.hlsl @@ -98,26 +98,24 @@ struct ModelInput #if USE_VERTEX_COLOR half4 Color : COLOR; #endif - #if USE_INSTANCING float4 InstanceOrigin : ATTRIBUTE0; // .w contains PerInstanceRandom float4 InstanceTransform1 : ATTRIBUTE1; // .w contains LODDitherFactor float3 InstanceTransform2 : ATTRIBUTE2; float3 InstanceTransform3 : ATTRIBUTE3; - half4 InstanceLightmapArea : ATTRIBUTE4; + half4 InstanceLightmapArea : ATTRIBUTE4; #endif }; struct ModelInput_PosOnly { float3 Position : POSITION; - #if USE_INSTANCING float4 InstanceOrigin : ATTRIBUTE0; // .w contains PerInstanceRandom float4 InstanceTransform1 : ATTRIBUTE1; // .w contains LODDitherFactor float3 InstanceTransform2 : ATTRIBUTE2; float3 InstanceTransform3 : ATTRIBUTE3; - half4 InstanceLightmapArea : ATTRIBUTE4; + half4 InstanceLightmapArea : ATTRIBUTE4; #endif }; @@ -129,13 +127,12 @@ struct ModelInput_Skinned float4 Tangent : TANGENT; uint4 BlendIndices : BLENDINDICES; float4 BlendWeights : BLENDWEIGHT; - #if USE_INSTANCING float4 InstanceOrigin : ATTRIBUTE0; // .w contains PerInstanceRandom float4 InstanceTransform1 : ATTRIBUTE1; // .w contains LODDitherFactor float3 InstanceTransform2 : ATTRIBUTE2; float3 InstanceTransform3 : ATTRIBUTE3; - half4 InstanceLightmapArea : ATTRIBUTE4; + half4 InstanceLightmapArea : ATTRIBUTE4; #endif }; diff --git a/Source/Shaders/Math.hlsl b/Source/Shaders/Math.hlsl index d211779d3..037efccba 100644 --- a/Source/Shaders/Math.hlsl +++ b/Source/Shaders/Math.hlsl @@ -54,6 +54,36 @@ float4 Square(float4 x) return x * x; } +float Min2(float2 x) +{ + return min(x.x, x.y); +} + +float Min3(float3 x) +{ + return min(x.x, min(x.y, x.z)); +} + +float Min4(float4 x) +{ + return min(x.x, min(x.y, min(x.z, x.w))); +} + +float Max2(float2 x) +{ + return max(x.x, x.y); +} + +float Max3(float3 x) +{ + return max(x.x, max(x.y, x.z)); +} + +float Max4(float4 x) +{ + return max(x.x, max(x.y, max(x.z, x.w))); +} + float Pow2(float x) { return x * x; diff --git a/Source/Shaders/Octahedral.hlsl b/Source/Shaders/Octahedral.hlsl new file mode 100644 index 000000000..7cc1092a4 --- /dev/null +++ b/Source/Shaders/Octahedral.hlsl @@ -0,0 +1,47 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#ifndef __OCTAHEDRAL__ +#define __OCTAHEDRAL__ + +// Implementation based on: +// "A Survey of Efficient Representations for Independent Unit Vectors", Journal of Computer Graphics Tools (JCGT), vol. 3, no. 2, 1-30, 2014 +// Zina H. Cigolle, Sam Donow, Daniel Evangelakos, Michael Mara, Morgan McGuire, and Quirin Meyer +// http://jcgt.org/published/0003/02/01/ + +float GetSignNotZero(float v) +{ + return v >= 0.0f ? 1.0f : -1.0f; +} + +float2 GetSignNotZero(float2 v) +{ + return float2(GetSignNotZero(v.x), GetSignNotZero(v.y)); +} + +// Calculates octahedral coordinates (in range [-1; 1]) for direction vector +float2 GetOctahedralCoords(float3 direction) +{ + float2 uv = direction.xy * (1.0f / (abs(direction.x) + abs(direction.y) + abs(direction.z))); + if (direction.z < 0.0f) + uv = (1.0f - abs(uv.yx)) * GetSignNotZero(uv.xy); + return uv; +} + +// Calculates octahedral coordinates (in range [-1; 1]) for 2D texture (assuming 1 pixel border around) +float2 GetOctahedralCoords(uint2 texCoords, uint resolution) +{ + float2 uv = float2(texCoords.x % resolution, texCoords.y % resolution) + 0.5f; + uv.xy /= float(resolution); + return uv * 2.0f - 1.0f; +} + +// Gets the direction vector from octahedral coordinates +float3 GetOctahedralDirection(float2 coords) +{ + float3 direction = float3(coords.x, coords.y, 1.0f - abs(coords.x) - abs(coords.y)); + if (direction.z < 0.0f) + direction.xy = (1.0f - abs(direction.yx)) * GetSignNotZero(direction.xy); + return normalize(direction); +} + +#endif diff --git a/Source/Shaders/Quaternion.hlsl b/Source/Shaders/Quaternion.hlsl new file mode 100644 index 000000000..09b5c49b3 --- /dev/null +++ b/Source/Shaders/Quaternion.hlsl @@ -0,0 +1,18 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#ifndef __QUATERNION__ +#define __QUATERNION__ + +float4 QuaternionMultiply(float4 q1, float4 q2) +{ + return float4(q2.xyz * q1.w + q1.xyz * q2.w + cross(q1.xyz, q2.xyz), q1.w * q2.w - dot(q1.xyz, q2.xyz)); +} + +float3 QuaternionRotate(float4 q, float3 v) +{ + float3 b = q.xyz; + float b2 = dot(b, b); + return (v * (q.w * q.w - b2) + b * (dot(v, b) * 2.f) + cross(b, v) * (q.w * 2.f)); +} + +#endif diff --git a/Source/Tools/FlaxEngine.Tests/FlaxEngine.Tests.csproj b/Source/Tools/FlaxEngine.Tests/FlaxEngine.Tests.csproj index ede414a44..b47350759 100644 --- a/Source/Tools/FlaxEngine.Tests/FlaxEngine.Tests.csproj +++ b/Source/Tools/FlaxEngine.Tests/FlaxEngine.Tests.csproj @@ -49,6 +49,7 @@ + diff --git a/Source/Tools/FlaxEngine.Tests/TestPropertyNameUI.cs b/Source/Tools/FlaxEngine.Tests/TestPropertyNameUI.cs new file mode 100644 index 000000000..0b7644190 --- /dev/null +++ b/Source/Tools/FlaxEngine.Tests/TestPropertyNameUI.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +using FlaxEditor.Utilities; +using NUnit.Framework; + +namespace FlaxEditor.Tests +{ + [TestFixture] + public class TestPropertyNameUI + { + [Test] + public void TestFormatting() + { + Assert.AreEqual("property", Utils.GetPropertyNameUI("property")); + Assert.AreEqual("property", Utils.GetPropertyNameUI("_property")); + Assert.AreEqual("property", Utils.GetPropertyNameUI("m_property")); + Assert.AreEqual("property", Utils.GetPropertyNameUI("g_property")); + Assert.AreEqual("Property", Utils.GetPropertyNameUI("Property")); + Assert.AreEqual("Property 1", Utils.GetPropertyNameUI("Property1")); + Assert.AreEqual("Property Name", Utils.GetPropertyNameUI("PropertyName")); + Assert.AreEqual("Property Name", Utils.GetPropertyNameUI("Property_Name")); + Assert.AreEqual("Property 123", Utils.GetPropertyNameUI("Property_123")); + Assert.AreEqual("Property TMP", Utils.GetPropertyNameUI("Property_TMP")); + Assert.AreEqual("Property TMP", Utils.GetPropertyNameUI("PropertyTMP")); + Assert.AreEqual("Property T", Utils.GetPropertyNameUI("PropertyT")); + Assert.AreEqual("Property TMP Word", Utils.GetPropertyNameUI("PropertyTMPWord")); + Assert.AreEqual("Generate SDF On Model Import", Utils.GetPropertyNameUI("GenerateSDFOnModelImport")); + } + } +} diff --git a/Source/flax.natvis b/Source/flax.natvis index 4cc1397d8..a38626357 100644 --- a/Source/flax.natvis +++ b/Source/flax.natvis @@ -61,24 +61,24 @@ - + - {{ Size={_elementsCount} Capacity={_tableSize} }} - - _elementsCount - _tableSize - - - _elementsCount - - - - _table[i] - - i++ - - - + {{ Size={_elementsCount} Capacity={_size} }} + + _elementsCount + _size + + + _elementsCount + + + + _allocation._data[i].Item + + i++ + + +