diff --git a/Assets/Engine/Shaders/DistanceClouds.ext b/Assets/Engine/Shaders/DistanceClouds.ext deleted file mode 100644 index 322acef5f3..0000000000 --- a/Assets/Engine/Shaders/DistanceClouds.ext +++ /dev/null @@ -1,51 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: Particles shader extension used by the editor -// for automatic shader generation (based on "Particles" shader template) -// -//////////////////////////////////////////////////////////////////////////// - -Version (1.00) - -Property -{ - Name = %DIFFUSE - Mask = 0x1 - Hidden -} - -Property -{ - Name = %SIMPLE - Mask = 0x2 - Property (Simple distance clouds) - Description (Use distance clouds with no volumetric shading computations) -} - -Property -{ - Name = %ADVANCED - Mask = 0x4 - Property (Advanced distance clouds) - Description (Use distance clouds with more accurate shading computations) -} - -Property -{ - Name = %DEPTH_FADE - Mask = 0x8 - Property (Depth Fade) - Description (Fades the output based on closeness of objects behind it) -} diff --git a/Assets/Engine/Shaders/Eye.ext b/Assets/Engine/Shaders/Eye.ext deleted file mode 100644 index 28df961f79..0000000000 --- a/Assets/Engine/Shaders/Eye.ext +++ /dev/null @@ -1,74 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: Eye shader extension used by the editor -// for automatic shader generation (based on "Eye" shader template) -// -//////////////////////////////////////////////////////////////////////////// - -Version (1.00) - -UsesCommonGlobalFlags - -Property -{ - Name = %NORMAL_MAP - Mask = 0x1 - Property (Normal map) - Description (Use normal-map texture) - DependencySet = $TEX_Normals - DependencyReset = $TEX_Normals - Hidden -} - -Property -{ - Name = %ENVIRONMENT_MAP - Mask = 0x2 - Property (Environment map) - Description (Use environment map as separate texture) - DependencyReset = $TEX_EnvCM -} - -Property -{ - Name = %EYE_AO_OVERLAY - Mask = 0x4 - Property (Ambient occlusion overlay) - Description (Use for ambient occlusion overlay rendering) -} - -Property -{ - Name = %EYE_SPECULAR_OVERLAY - Mask = 0x8 - Property (Specular overlay) - Description (Use for specular overlay rendering) -} - -Property -{ - Name = %VERTCOLORS - Mask = 0x400000 - DependencySet = $UserEnabled - Hidden -} - -Property -{ - Name = %TEMP_EYES - Mask = 0x80000000 - DependencySet = $UserEnabled - Hidden -} \ No newline at end of file diff --git a/Assets/Engine/Shaders/Fur.ext b/Assets/Engine/Shaders/Fur.ext deleted file mode 100644 index 067c85d274..0000000000 --- a/Assets/Engine/Shaders/Fur.ext +++ /dev/null @@ -1,126 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// - -// Description: Fur shader extension used by the editor -// for automatic shader generation (based on "Fur" shader template) -// -//////////////////////////////////////////////////////////////////////////// - -Version (1.00) - -UsesCommonGlobalFlags - -Property -{ - Name = %NORMAL_MAP - Mask = 0x1 - Property (Normal map) - Description (Use normal-map texture) - DependencySet = $TEX_Normals - DependencyReset = $TEX_Normals - Hidden -} - -Property -{ - Name = %SPECULAR_MAP - Mask = 0x2 - Property (Specular map) - Description (Use specular map as separate texture) - DependencySet = $TEX_Specular - DependencyReset = $TEX_Specular - Hidden -} - -Property -{ - Name = %CUSTOM_MODIFICATOR - Mask = 0x4 - Property (Call CustomModificator function in vertex shader) - DependencySet = $UserEnabled - Hidden -} - -Property -{ - Name = %FUR_VERT_COLORS - Mask = 0x8 - Property (Fur Color Data) - Description (Vertex color channel contains fur combing and scaling info) -} - -Property -{ - Name = %FUR_WIND_BENDING - Mask = 0x10 - Property (Wind bending) - Description (Enable wind bending for fur) -} - -Property -{ - Name = %FUR_BLENDLAYER - Mask = 0x20 - Property (Fur Blendlayer) - Description (Diffuse layer blended into fur as it grows from base to tip) -} - -Property -{ - Name = %FUR_BLENDCOLOR - Mask = 0x40 - Property (Fur Blend color) - Description (Specified color blended into fur diffuse as it grows from base to tip) -} - -Property -{ - Name = %FUR_LENGTH_SCALED - Mask = 0x80 - Property (Scale fur length) - Description (Fur length scales with object scale) -} - -Property -{ - Name = %MODEL_SPACE_Z_UP - Mask = 0x100 - Property (Model Space Z Up) - Description (Check if model was generated with Z up, if unchecked, assumes Y up) -} - -Property -{ - Name = %DEPTH_FIXUP - Mask = 0x200 - Property (Depth Fixup) - Description (Write depth for depth of field and postprocessing) - DependencySet = $UserEnabled - Hidden -} - -Property -{ - Name = %ZPASS_CUSTOM_DIFFUSE - Mask = 0x400 - Property (Calls GetZPassDiffuse function in z pass pixel shader for custom diffuse handling) - DependencySet = $UserEnabled - Hidden -} - -Property -{ - Name = %FUR_MULTI_LAYER_ALPHA_BLEND - Mask = 0x800 - Property(Enable OIT) - Description(Use OIT for accurate alpha blending - performance penalty expected) -} diff --git a/Assets/Engine/Shaders/GeometryBeam.ext b/Assets/Engine/Shaders/GeometryBeam.ext deleted file mode 100644 index 25e395f42d..0000000000 --- a/Assets/Engine/Shaders/GeometryBeam.ext +++ /dev/null @@ -1,43 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// -//////////////////////////////////////////////////////////////////////////// - - -Property -{ - Name = %NOISE - Mask = 0x1 - Property (Dust & Turbulence) - Description (Add a dust overlay [spec map for dust, bump map for turbulence] ) -} - - -Property -{ - Name = %RECEIVE_SHADOWS - Mask = 0x2 - Property (Receive Shadows) - Description (Enable shadow receiving) -} - - -Property -{ - Name = %UV_VIGNETTING - Mask = 0x4 - Property (UV Vignetting) - Description (Enabling this will cause contents to fade out at UV boundaries) -} \ No newline at end of file diff --git a/Assets/Engine/Shaders/Glass.ext b/Assets/Engine/Shaders/Glass.ext deleted file mode 100644 index 1070d4e5c9..0000000000 --- a/Assets/Engine/Shaders/Glass.ext +++ /dev/null @@ -1,111 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: Glass shader extension used by the editor -// for automatic shader generation (based on "Glass" shader template) -// -//////////////////////////////////////////////////////////////////////////// - - -Version (2.00) - -UsesCommonGlobalFlags - -Property -{ - Name = %DIRT_MAP - Mask = 0x100000 - Property (Use Diffuse map) - Description (Use Diffuse map for dirt, etc. Requires Alpha channel) -} - -Property -{ - Name = %SPECULAR_MAP - Mask = 0x200000 - Property (Specular map) - Description (Use specular map as separate texture) - DependencySet = $TEX_Specular - DependencyReset = $TEX_Specular - Hidden -} - -Property -{ - Name = %ENVIRONMENT_MAP - Mask = 0x10 - Property (Environment map) - Description (Use environment map as separate texture) - DependencyReset = $TEX_EnvCM -} - - -Property -{ - Name = %TINT_MAP - Mask = 0x200 - Property (Tint map - Tint/Gloss/Spec) - Description (Use RGB Spec Map to control Tinting in Red channel / Cloudiness in Green channel / Specular in Blue channel) - -} - -Property -{ - Name = %TINT_COLOR_MAP - Mask = 0x400 - Property (Use Tint Color Map) - Description (Use Tint Color Map for multi-colored glass, goes in the custom Tint Color Map slot) - DependencyReset = $TEX_Custom - DependencySet = $TEX_Custom -} - -Property -{ - Name = %BLUR_REFRACTION - Mask = 0x2000 - Property (Blur refraction - PC Only) - Description (Blur objects seen through the glass) -} - -Property -{ - Name = %DEPTH_FOG - Mask = 0x4000 - Property (Depth Fog) - Description (Enables depth fog behind glass surface) -} - -Property -{ - Name = %UNLIT - Mask = 0x8000 - Property (Disable Lights) - Description (Disables the reflection of lights) -} - -Property -{ - Name = %DEPTH_FIXUP - Mask = 0x4000000 - Property (Depth Fixup) - Description (Write depth for depth of field and postprocessing) -} - -Property -{ - Name = %SAA_FILTERING - Mask = 0x80000000 - Property (Specular Antialiasing) - Description (Perform specular Antialiasing) -} \ No newline at end of file diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/AuxGeom.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/AuxGeom.cfx deleted file mode 100644 index ac64ed293a..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/AuxGeom.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d924c59739f63724999b1bf6cd326d10024b031bdfbf02fe8e09cb0b783dfe67 -size 2891 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Clouds.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Clouds.cfx deleted file mode 100644 index cf8ee86492..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Clouds.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:045f6506425364221f9dbf0b359e38877f276300770ef7a5ab694f1055eb0d02 -size 1569 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Common.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/Common.cfi deleted file mode 100644 index 644826685e..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Common.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f25999b453c3e91c8237d28ccc1ef3baefaa2a7ec8f9aad65adb95042e25e435 -size 56582 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Common.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Common.cfx deleted file mode 100644 index d347b5c967..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Common.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:396a70d86a4678b16fa110b3eceacbd5b515aa85090d16bdb6a8562d7c94e4a8 -size 17205 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/CommonDebugPass.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/CommonDebugPass.cfi deleted file mode 100644 index 95abf99bbf..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/CommonDebugPass.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b05841cd01ec853455ad792e80302cf5ffbbd104c5ecf46699f5ecd4b0624c09 -size 983 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/CommonMotionBlurPass.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/CommonMotionBlurPass.cfi deleted file mode 100644 index 54f160e01b..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/CommonMotionBlurPass.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:725006c2f2844e49b691506cbc3d015466cdfbad19209a63964acc264b78c782 -size 4096 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/CommonMotionBlurPassTess.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/CommonMotionBlurPassTess.cfi deleted file mode 100644 index 4d74269d97..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/CommonMotionBlurPassTess.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5886e0be4d4047a9b41431c141738cb0a13a3b3a4033cc262f173b5e9e195707 -size 4734 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/CommonSVO.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/CommonSVO.cfi deleted file mode 100644 index 023437ec3c..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/CommonSVO.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:57682432ba4de87ddc5ba542b6630ad512f75dbaca2a0c1402fb2a59ed35804c -size 111615 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/CommonShadowGenPass.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/CommonShadowGenPass.cfi deleted file mode 100644 index 7907e3913d..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/CommonShadowGenPass.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:01bbe928766a8c75bc3d990747f91e5becfbed6a59d58b71d2a2922f6d14bc1f -size 6970 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/CommonShadowGenPassTess.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/CommonShadowGenPassTess.cfi deleted file mode 100644 index ccb63377d1..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/CommonShadowGenPassTess.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c4c004afe3782ae58fa4ac90dd7f16e026d0c38e4bf66ed475beaa1ce3fcf98 -size 3363 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/CommonTessellation.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/CommonTessellation.cfi deleted file mode 100644 index 33aa6974cf..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/CommonTessellation.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:86f594a6f8fb6e1328b95e6b03a04109ea3e9e8b8642d30c674281ea916dbf41 -size 19522 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/CommonViewsPass.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/CommonViewsPass.cfi deleted file mode 100644 index 71876eddd2..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/CommonViewsPass.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:04850ac23185147fd8e97303efad70cbb876c6589c71cb413b0429d267705cd3 -size 8020 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/CommonViewsPassTess.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/CommonViewsPassTess.cfi deleted file mode 100644 index 96cbad4a40..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/CommonViewsPassTess.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6c660b79e7532b5a9513341d3f37215a742d3ee99532ba0003e2c13381d55bc6 -size 4238 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/CommonZPass.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/CommonZPass.cfi deleted file mode 100644 index 8a9f7ef4f1..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/CommonZPass.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8fe036b79e16737499920500c87997567d8ee32534748df4ea617c4118237cb5 -size 32052 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/CommonZPassTess.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/CommonZPassTess.cfi deleted file mode 100644 index adc03e7945..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/CommonZPassTess.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a30f1c7b94eb1bcb0261e518062da6eb69987698d65e6dd786bfe29be32d749e -size 4995 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/CommonZPrePass.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/CommonZPrePass.cfi deleted file mode 100644 index 43481f75fe..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/CommonZPrePass.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3c4ac5b9fa8a288f8fdfc4110599b25742b19ddd3a251d638e5e1da9284310d6 -size 2814 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/DXTCompress.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/DXTCompress.cfx deleted file mode 100644 index 26545c6531..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/DXTCompress.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a2b214f2873a3a99bcaccc120d6a397c708a4b3309c9b953860f8f9cf93461c8 -size 8591 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Debug.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Debug.cfx deleted file mode 100644 index 71ab497cc5..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Debug.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f26436234a598c2aa906653e711eb0e8ae14f99b65fc25eea0d238d9f56d914a -size 4345 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/DebugLight.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/DebugLight.cfx deleted file mode 100644 index cc9a79a324..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/DebugLight.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bb0a8212530051bd6e4ac5646f6ab2f0a59f43a349ce8e03e544f6cf6d87ba4b -size 1929 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/DeferredCaustics.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/DeferredCaustics.cfx deleted file mode 100644 index f477baa00c..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/DeferredCaustics.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:24e1f7c4242c708a638a1069908f258fa26aa77152330a2c8da72260dd192209 -size 14703 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/DeferredRain.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/DeferredRain.cfx deleted file mode 100644 index 2e771c73bf..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/DeferredRain.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:77910f05ea15757e24201187a68a9a0db924f7951dadd4b7c7330f0845bd5944 -size 14043 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/DeferredShading.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/DeferredShading.cfx deleted file mode 100644 index 9252e1b06e..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/DeferredShading.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ab94af4462722c1a111f4199714fabd0addf095aba82840f92e6ef8b056d78c -size 94446 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/DeferredShadows.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/DeferredShadows.cfi deleted file mode 100644 index 63c8b33c21..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/DeferredShadows.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0641726ad6140704434896912f0e57e29e6f5e28ac2653bc97bbe7e2d444fbc2 -size 2828 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/DeferredSnow.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/DeferredSnow.cfx deleted file mode 100644 index 0789202955..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/DeferredSnow.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6ea4c40161f4f628fbf9cd1a3b7df445615dfa8ca15761a4fec15d0c2276b369 -size 22312 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/DepthOfField.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/DepthOfField.cfx deleted file mode 100644 index 9a431fb20a..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/DepthOfField.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b67ece889d68d8ed6f891ca5cfb1f951a777616ea928b0e6f91bd541b35c62ff -size 13136 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/DistanceClouds.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/DistanceClouds.cfx deleted file mode 100644 index 7448f0304a..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/DistanceClouds.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a06a4a972542f51bea726578b26ef0fc711c4b6f4a77a31a383690717c5ccba2 -size 10622 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Eye.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Eye.cfx deleted file mode 100644 index a3cb1227ea..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Eye.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fed5c2b72b2b4769954f2fad9573c6e35581c556e3a96685d449eea5576cf433 -size 19244 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/FXConstantDefs.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/FXConstantDefs.cfi deleted file mode 100644 index 7b0d7202e7..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/FXConstantDefs.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1b6aa7281d2174f24b510a7ac820b4ab308a215d6b3eae4a039bc01e34b131ec -size 9922 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/FXSamplerDefs.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/FXSamplerDefs.cfi deleted file mode 100644 index 7f5b097148..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/FXSamplerDefs.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:750ac5a4ef25f1379121c7346dad0230d389c7c889ffdccbe6281a511b021cea -size 4044 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/FXStreamDefs.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/FXStreamDefs.cfi deleted file mode 100644 index 4d05f905c1..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/FXStreamDefs.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:073303aa45adc5ca237f33f29fb4de52f8b3c935912298cdec611128c510399c -size 9279 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/FallBack.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/FallBack.cfx deleted file mode 100644 index 321440401b..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/FallBack.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:052a458b7bdef6d7e1fa1a26d4cb66f6e24f91b9e42451a8e48045d3779aef1c -size 2383 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/FixedPipelineEmu.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/FixedPipelineEmu.cfx deleted file mode 100644 index 8b92aa7399..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/FixedPipelineEmu.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab757a0df8246a0e9d20839a89219a6c616d6df054d72b21b79e459b8503ade8 -size 10188 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/FogVolume.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/FogVolume.cfx deleted file mode 100644 index 929d89ab5b..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/FogVolume.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a37c2ab18b0ad1da90005c0ff9fea89d7c78ade9fd51b28b665cf42b814ecd8d -size 23690 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Fur.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Fur.cfx deleted file mode 100644 index 7d7405cc5e..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Fur.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2f429a8537df95455e32da7fd480e79ef46751d6bc880c34b746c21515aa012a -size 31609 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/FurFinPass.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/FurFinPass.cfi deleted file mode 100644 index 84149cb0f6..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/FurFinPass.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e3f25ea475310f699442a160ffcc9f58c40e54b6570210dafbe41746a010b821 -size 15002 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/FurObliteratePass.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/FurObliteratePass.cfi deleted file mode 100644 index 3ffe779a54..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/FurObliteratePass.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f6246f93aa73fdab460c1c82a33f2588a0ae83a162acdcdb12cec86ddac332b6 -size 5142 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/FurZPass.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/FurZPass.cfi deleted file mode 100644 index 5735035e49..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/FurZPass.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fb0d83cc48ebee33b3d3db2b1a5c27636d35dfb7ce720e5391a28ff70ca0d729 -size 9023 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticle.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticle.cfi deleted file mode 100644 index 6cd187c993..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticle.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b690c770f90e33dc78604b5beca3453aba4fbb4db8014b730680c33b5cef5120 -size 2299 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleBegin.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleBegin.cfx deleted file mode 100644 index 3bc0dd3e01..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleBegin.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:64c184691014423f2f6c518d34ff9657acc1dc8867e0e43d20b5526491d35d5b -size 462 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleBitonicSort.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleBitonicSort.cfx deleted file mode 100644 index 14c0b06c58..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleBitonicSort.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a71929dda4a7b4516754286da5494546a0ea2075b9fde5cefef68f0a99d37381 -size 1520 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleBitonicSortGlobal2048.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleBitonicSortGlobal2048.cfx deleted file mode 100644 index b8ff2e6afc..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleBitonicSortGlobal2048.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a50144544a4c292d172d4bb9586a6016de4cc5b0a39958abfb00bc720e8c23e9 -size 1989 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleBitonicSortLocal.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleBitonicSortLocal.cfx deleted file mode 100644 index 6ee0b7b645..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleBitonicSortLocal.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:25d51818120964a1576f35727c0f13bc10a01727ab793f5fdf79cb9c1f7bf563 -size 2055 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleCurves.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleCurves.cfi deleted file mode 100644 index a565d62cc4..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleCurves.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4308e81d4ee4c65480a1cf110f3f602d347f2899b4cf299f334c2659904329dd -size 553 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleEmit.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleEmit.cfx deleted file mode 100644 index 8d417c29ac..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleEmit.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bfe3a765c1057d5dfae514ba17d0ce99d6598693035df2037314da5f38e4c305 -size 19142 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleGatherSortDistance.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleGatherSortDistance.cfx deleted file mode 100644 index 0a223398ff..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleGatherSortDistance.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f94a71dd8cbe95080f2de1bfa8cbd9443bea18106cc7bcae2c5c7225d113f2b4 -size 644 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleHelpers.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleHelpers.cfi deleted file mode 100644 index a3b10d582b..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleHelpers.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cf39d59eefe2c6641437e9d535528edb29e1e0dad6d1b84b20662c7075928fe8 -size 6969 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleOddEvenSort.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleOddEvenSort.cfx deleted file mode 100644 index be1e36e5db..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleOddEvenSort.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5cbcdd1c83247233dce9fb5c32e9d4530b23b825b284591a921a0b7e81d230ea -size 1817 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleRenderNoGS.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleRenderNoGS.cfx deleted file mode 100644 index 2d8fc2a1d0..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleRenderNoGS.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cae99346703f5ebc9bb59edb61c17b86184ae7659af78395ec4cba11cb6da471 -size 37741 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleUpdate.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleUpdate.cfx deleted file mode 100644 index 7524f13c6f..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/GPUParticleUpdate.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8a3a7bb8ad18d99c7490d15ce24b3c4867d9668b1539014a31b3ba5e80ebb00e -size 25753 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/GeometryBeam.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/GeometryBeam.cfx deleted file mode 100644 index 364f613063..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/GeometryBeam.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:45e7f387bb7cc9d7a489916b27ea6bff816510664099008ebbeaa0d10a26faa7 -size 12612 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Glass.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Glass.cfx deleted file mode 100644 index 38226fedec..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Glass.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:49aa341aa2563ced85aa4f62f74865c558238c91ba9d8d62ad21392088755820 -size 26913 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/HDRDolbyMetadataPass0.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/HDRDolbyMetadataPass0.cfx deleted file mode 100644 index b2df095b8a..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/HDRDolbyMetadataPass0.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c7f9297a483fd7d7b91a3f06f1a3eb83e77192b967691f4e2119d41b7a7afdd2 -size 2707 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/HDRDolbyMetadataPass1.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/HDRDolbyMetadataPass1.cfx deleted file mode 100644 index 25c959c8f6..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/HDRDolbyMetadataPass1.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:52d68438b25fbd2d88a7247295a021680a9b95a6f3ed07c3ae066cb3d44b25ed -size 2530 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/HDRPostProcess.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/HDRPostProcess.cfx deleted file mode 100644 index 76e692fa63..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/HDRPostProcess.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5cf8d27075f4aa76e5814d1c4a9718d7106df50229f33e1d8d7d7a4532d1e5f3 -size 40838 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/HDRPostProcessDolby.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/HDRPostProcessDolby.cfi deleted file mode 100644 index b8c0c3043c..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/HDRPostProcessDolby.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2e62a2aa829af58b2ba19a9920bd87d64a27854d24de5a51842740c7ec1e9ae8 -size 33567 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Hair.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Hair.cfx deleted file mode 100644 index 4788d26a0e..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Hair.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb56b0070f3811f1b8780e70d4b9410a08f3aac9f8fa3e947194a4d902c9d8ef -size 33265 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Helper.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Helper.cfx deleted file mode 100644 index 5c19cc54e0..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Helper.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9764bb3ab6d2895a355466f3c53f735c3500006420bac211bc9a0b7c29a605c6 -size 2168 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Hud3D.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Hud3D.cfx deleted file mode 100644 index c460d0468c..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Hud3D.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c63de566d2a5a69781a4c1e16958d64d187e057f3447afb4ec9aa47455ef7a5c -size 9617 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/HumanSkin.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/HumanSkin.cfx deleted file mode 100644 index ff2a2b894a..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/HumanSkin.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b81300b59d42b38b7669016cc8920db9adbf07b612f12d520d9480a6de59aa73 -size 18648 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/HumanSkinTess.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/HumanSkinTess.cfi deleted file mode 100644 index 8f7ce5f8c9..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/HumanSkinTess.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a79996ad61013920dcccd254f23e05fb4089879d477d8f3ce00ff98ee04d6ec1 -size 7783 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/HumanSkinValidations.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/HumanSkinValidations.cfi deleted file mode 100644 index 9743568b4d..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/HumanSkinValidations.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:548f2cc401046358ef4f4edeb37532f06437b99b2fe5bff70100e66a6ecb281a -size 986 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Illum.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Illum.cfx deleted file mode 100644 index 6de2f49547..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Illum.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3c110e0891fbe46a10f2fbcf4ffb8f82c0711ffaa943fac5ca92c96f7e63d051 -size 28796 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/IllumTess.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/IllumTess.cfi deleted file mode 100644 index ef66d513a9..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/IllumTess.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9a52e2256a43707dfab74c680e96249d27e7eb38a2984c9d4dcff83ffe7dd15b -size 5194 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/IllumValidations.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/IllumValidations.cfi deleted file mode 100644 index 7af7a4748c..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/IllumValidations.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4155e44e6687796095b5e2401c93a09e527c61a48830e8a35d4ab16d849d8159 -size 2031 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/LensOptics.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/LensOptics.cfx deleted file mode 100644 index ab20cc3bc7..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/LensOptics.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7acdc1aaa28aea83d9af53547f65c5686b2fc43be988068d8f4f88c87c4359a7 -size 16847 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Light.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Light.cfx deleted file mode 100644 index ebbe8183bf..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Light.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4436686d0f3e735973df2dd56523269683c66b3ae01da67fa7f41e5f9e1b1234 -size 12760 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/LightBeam.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/LightBeam.cfx deleted file mode 100644 index e19414deef..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/LightBeam.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:13c69186729534dcd2676d1740a530fb5d5a3307d7b713f5c08704220186ba6d -size 14169 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/LightVolumes.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/LightVolumes.cfi deleted file mode 100644 index 4ab290bb32..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/LightVolumes.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f79e6c1e182e86afd295758f0afefc901407e69eb3f93422d8a3089451e49c7d -size 5526 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/MeshBaker.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/MeshBaker.cfi deleted file mode 100644 index 5f019eca9a..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/MeshBaker.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0ca1d710fd2b0937a04ded739ab4dc35ff48e3ff0c07d6a5d4df95f32a1947a4 -size 5032 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/MeshBakerDilate.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/MeshBakerDilate.cfx deleted file mode 100644 index 59ff9363a5..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/MeshBakerDilate.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7a7db4ca9b7d87e4c3410df26f3045a18fb96a2535b88ab8afe00dcf1d621943 -size 4173 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/ModificatorTC.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/ModificatorTC.cfi deleted file mode 100644 index 6d0fd6bb42..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/ModificatorTC.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0eb9c63a60a3d2afb78d6e4077ac81322dbd1a0537f5551c638d57c353381e15 -size 5256 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/ModificatorVT.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/ModificatorVT.cfi deleted file mode 100644 index fff9af0b52..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/ModificatorVT.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dd0aecf5b1620c49a9b08c35865ea8de5487098f1eed8374fa4fc85e4a87e886 -size 52858 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Monitor.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Monitor.cfx deleted file mode 100644 index 840da6f035..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Monitor.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:758aa594628cd94f10d379f08a9a0a63478cc69eb9a093404d57c46e6830150a -size 19450 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/MotionBlur.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/MotionBlur.cfx deleted file mode 100644 index b6ba57f9f3..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/MotionBlur.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8b4f6c44e52297c1dfe02040a7e63739aeed73f2c2c4930053f5a83c3c73f816 -size 8716 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/MultiLayerAlphaBlend.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/MultiLayerAlphaBlend.cfi deleted file mode 100644 index 751d38bde8..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/MultiLayerAlphaBlend.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:01f08e32bfb1f8690759da687114e66bf6652b6d17b208ed1b316315f0a66a1b -size 6435 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/NoDraw.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/NoDraw.cfx deleted file mode 100644 index a4a038ac28..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/NoDraw.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1d183abe91463d2ab90f56c055a44aa8c3cad1d731f51c9f56bd6f9df3d7eba1 -size 180 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/OcclusionTest.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/OcclusionTest.cfx deleted file mode 100644 index 7f1b7bf522..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/OcclusionTest.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1ceef3fd50e977ba168e2f80803bc0a09542aa3c55721e0e6e27bbab45454607 -size 1545 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/ParticleImposter.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/ParticleImposter.cfx deleted file mode 100644 index 0506b14d5e..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/ParticleImposter.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b67b9997024270cfdede2d057220078029db61ab5dff6d522a39c99d08420245 -size 8657 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/ParticleVT.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/ParticleVT.cfi deleted file mode 100644 index bbe4d0c261..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/ParticleVT.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:670e9dd98f131fa0375ddcc4df8fe850eeabb43f9771d302b5c8e2fd7022d78d -size 8534 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Particles.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/Particles.cfi deleted file mode 100644 index 6f38a8eae7..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Particles.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:98f62a7869360e63083eea9a784652ad0fca13a9cd741930940be75c10ca83e7 -size 40918 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Particles.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Particles.cfx deleted file mode 100644 index 728135d83c..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Particles.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:55bcc8a634262b6001193fabf318cdf2960904dd47e915fa17de3a60c620639d -size 4613 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/ParticlesCustomPass.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/ParticlesCustomPass.cfi deleted file mode 100644 index 7f90f9f1a5..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/ParticlesCustomPass.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:af77abd5a2d462e4b533e6e5b489bc8e39909527c64a9cd599bbf5312b3aa17a -size 6052 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/ParticlesNoMat.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/ParticlesNoMat.cfx deleted file mode 100644 index 3728b1f379..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/ParticlesNoMat.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:db6f21607b4ce4e0d0a48ffa0a7db81f29ac0546c76846dd39f1fc584f7d23e5 -size 1517 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/ParticlesNoMatMirror.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/ParticlesNoMatMirror.cfx deleted file mode 100644 index 955c99c3e9..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/ParticlesNoMatMirror.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4f8178b44fc27aacde3600b86bc06038aefbac6ca4fbf4fbe29f32ca6e8e0584 -size 1603 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/ParticlesShadowPass.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/ParticlesShadowPass.cfi deleted file mode 100644 index 461f4d40ac..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/ParticlesShadowPass.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0c7c5e176c6f0a5f689bc433dd23d5bb998f8f10d4185c30cb9788022ebb38a0 -size 6464 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/PostAA.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/PostAA.cfx deleted file mode 100644 index d8ae4939e4..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/PostAA.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7a10320d3b31738cc0154de2c973fcaf912885209a7e729e3f6bd372f7633354 -size 67647 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/PostEffects.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/PostEffects.cfx deleted file mode 100644 index 95269fe06c..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/PostEffects.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ec164683d3fe45a512c0e9a2314c5b450602ee8c8a8281d99d43c1d76a761df3 -size 60596 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/PostEffectsGame.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/PostEffectsGame.cfx deleted file mode 100644 index efaa765189..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/PostEffectsGame.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a6d66a0aed7f39e3848ee4763c408c390ec7021205cf2f0f07459b99d84f1d27 -size 80783 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/PostEffectsLib.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/PostEffectsLib.cfi deleted file mode 100644 index 90ce479d4a..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/PostEffectsLib.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b73c3ae905c1833cade569500b647dc504e83b34f15a4584f72a372d254d884f -size 8502 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/ReferenceImage.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/ReferenceImage.cfx deleted file mode 100644 index 08b3c94d06..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/ReferenceImage.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:12d61af66003a6ab90e24574925fd94bc03622386da142fcaa655df21b0fd5bd -size 2033 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/ReferenceImageHDR.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/ReferenceImageHDR.cfx deleted file mode 100644 index aaa0a26a21..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/ReferenceImageHDR.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1c4913b73fec9ebdcd5b8e7c86d495b7dc9a135e7f8a77ee90b9e62a78d3cc47 -size 1819 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Scopes.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Scopes.cfx deleted file mode 100644 index 7be1e74629..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Scopes.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4565c5415b0a68ced049335d803fcfdbf899db4ea914b8fbc5ba34620e4c4045 -size 10709 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/ShadowBlur.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/ShadowBlur.cfx deleted file mode 100644 index ad707f2932..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/ShadowBlur.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3a6eafe0f42be55811eda4ac6b164bd01e22b6cc2b77723a8693549b26939871 -size 10768 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/ShadowCommon.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/ShadowCommon.cfi deleted file mode 100644 index fbaa4d7f65..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/ShadowCommon.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ddac405a61300a0857cba4ea7dabcb6d5aef5253ada53d09ec4ab591ed3cfbce -size 25680 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/ShadowMaskGen.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/ShadowMaskGen.cfx deleted file mode 100644 index 36b7604c43..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/ShadowMaskGen.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2aec8020aa2454b8a0cd406198bd2c067f878cb697dad2e2059e88c7cb2e5e87 -size 32118 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Sketch.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Sketch.cfx deleted file mode 100644 index 60d4aa0f4a..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Sketch.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1ba3cc8b5127ccb8a446e0ac85daf0b13c700092bcdcbdd92d5fbf45dfd430dc -size 11176 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/SketchTerrain.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/SketchTerrain.cfx deleted file mode 100644 index f8ff66d760..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/SketchTerrain.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b247b792e8e26abb50c2c01e9bc6c341216f6a84fc3fac67088294cf868428ab -size 2992 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Sky.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Sky.cfx deleted file mode 100644 index 06ec9fc6f1..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Sky.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7fec7735636fa7a4c2387e216918ec92414bc29ec6ecbd89605677214f829403 -size 4830 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/SkyHDR.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/SkyHDR.cfx deleted file mode 100644 index 48b47a9ae1..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/SkyHDR.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3195bdf90fa2a0b1539f0cac7471dc31ec7aa783d138f9c0455a32354ead3874 -size 8548 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/SoftOcclusionQuery.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/SoftOcclusionQuery.cfx deleted file mode 100644 index 425102e21d..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/SoftOcclusionQuery.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c82e35c6af179bb2405bbfe1d452ae2a8c5479934c0c9d8e8119d04db35d8cf5 -size 3546 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Stars.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Stars.cfx deleted file mode 100644 index 4fdbddb2c6..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Stars.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c5f191743c706e8531bada03abab8700c01cc9afbff0c9ea22c41d509b11ee76 -size 4075 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/StarterGame_GeometryBeamScaling.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/StarterGame_GeometryBeamScaling.cfx deleted file mode 100644 index a5a51340fc..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/StarterGame_GeometryBeamScaling.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:888486a516d3052e6b8b01cc0d36f01a68985ed482db683a25527c220290cc6c -size 13217 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Stereo.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Stereo.cfx deleted file mode 100644 index 8ef249a60f..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Stereo.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:761402eba9c7256a79fe78869b0287b95a8be8f810b48e41222049425b2eb535 -size 5422 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Sunshafts.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Sunshafts.cfx deleted file mode 100644 index 2274352ec3..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Sunshafts.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9a162b22e9ee5dc42559c8bcd4a6160c8b227bfeb6a5b367b62b8256109b99b5 -size 8884 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/TemplBeamProc.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/TemplBeamProc.cfx deleted file mode 100644 index 1a2de2a0dd..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/TemplBeamProc.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aed5eeb5e7467884319e268f985134f0d50482408cdbda6359873d211387737c -size 7182 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Terrain.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Terrain.cfx deleted file mode 100644 index 22abec6b24..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Terrain.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e4d2246d81afaad43e267addffdc01ce201dc4032bf5e05131cfb9f56ebc5ed0 -size 19043 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/TerrainValidations.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/TerrainValidations.cfi deleted file mode 100644 index 1c8349f673..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/TerrainValidations.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3956d70a7eff854ab02d44f6c712cdb694045e87e9a0201ad8427436c693897a -size 707 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/TiledShading.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/TiledShading.cfi deleted file mode 100644 index ebc1521531..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/TiledShading.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:72448b07701ad745e35d98de2229f0675cbb74bd6d40095ec4625929da906c5f -size 50201 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Total_Illumination.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Total_Illumination.cfx deleted file mode 100644 index 9b2c8e38dc..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Total_Illumination.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c73e03d1e09efd2e8cdb9292debb5260a0690772c81a9dbb085502f822b7f91b -size 38227 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/UI.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/UI.cfx deleted file mode 100644 index e98a5a2fae..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/UI.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c2b7a3ff846dde2ba481e46180e67e81528fa2a984ba6bb1a5c83304c301551d -size 7717 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Vegetation.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Vegetation.cfx deleted file mode 100644 index 0bca5a3e53..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Vegetation.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6c330b2ecb3d850e5eeb3673500cdebb95bee46b92eb93c3274291dbf96b2c98 -size 25807 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/VegetationTess.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/VegetationTess.cfi deleted file mode 100644 index 5abc5af104..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/VegetationTess.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:28aadc74c027782d5971e8145d3286f733fcbb571872447f638397df4f957765 -size 5746 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/VegetationValidations.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/VegetationValidations.cfi deleted file mode 100644 index 00d2e11b8f..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/VegetationValidations.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c05fb764a9fbc9cf7ea7b225680880c32847f4d355cab27f958564d5adcfc21b -size 1167 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Video.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Video.cfx deleted file mode 100644 index 966c5d92f6..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Video.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c1c4e2eed305e2319127d12349754819350301b42d33cb109def7237c3591f1 -size 2123 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/VolumeLighting.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/VolumeLighting.cfi deleted file mode 100644 index 77cb40a98f..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/VolumeLighting.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:515e3218c9b23e8ff4d67a200ca11b3c833ad3999500f7f8be4ecdc5c01d5565 -size 82057 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/VolumeObject.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/VolumeObject.cfx deleted file mode 100644 index 2ca573741e..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/VolumeObject.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a1fa34bd8cd7d8df9089739c7adf62b95c64f3d0993dcb2ee3892e05e118e83c -size 6700 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/VolumetricFog.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/VolumetricFog.cfi deleted file mode 100644 index 2556067f04..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/VolumetricFog.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3dd1721686abf6fe824df07a5e8ee1505b0fbcbaec6dc1a8ded98274d6ea4058 -size 25814 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Water.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Water.cfx deleted file mode 100644 index 175960ffc2..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Water.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d3ebab64f075124eb4cf426fee1c9ac446225ed647db3f10a28a28b1efaa5e63 -size 41727 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/WaterCausticsPass.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/WaterCausticsPass.cfi deleted file mode 100644 index 5aa4d40dff..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/WaterCausticsPass.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9f73053b56ef452bf8ae72a4cc6fed59876562d9f4541172265ce6c8d5740711 -size 3340 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/WaterFogVolume.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/WaterFogVolume.cfx deleted file mode 100644 index eda54be33e..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/WaterFogVolume.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9f9d26a36eb0fa05b58991aaaa6a073621309aa8a4a06a5bb4a6048b507a3fe1 -size 18306 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/WaterOceanBottom.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/WaterOceanBottom.cfx deleted file mode 100644 index 90cf67ba52..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/WaterOceanBottom.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5266c69503cb72e831e5c488332237fbfd343f96708ea3c75037780bd62fed60 -size 5280 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/WaterReflectionsPass.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/WaterReflectionsPass.cfi deleted file mode 100644 index a2ffd3d2a8..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/WaterReflectionsPass.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bb1ba5273a487655f813688f0a8500c662348639414ac53a0b0ee3d911010eab -size 10008 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/WaterVolume.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/WaterVolume.cfx deleted file mode 100644 index 7985e3dab0..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/WaterVolume.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:41c19dceb8daa6e9a9102b83c78208d6f0681066f5d6c2cf2e657a82e1ce695c -size 39737 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/Waterfall.cfx b/Assets/Engine/Shaders/HWScripts/CryFX/Waterfall.cfx deleted file mode 100644 index d09922f322..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/Waterfall.cfx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ebece6e3b0115174da434ed1b166b7209c012a6013e219e2421ffee5ebd6dda1 -size 11237 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/fragLib.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/fragLib.cfi deleted file mode 100644 index ffba873be1..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/fragLib.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3a340c8cbb76900b02bc171e680cfdc628900a5c38ecd3ce6a7b9eecbafa72ef -size 23148 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/shadeLib.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/shadeLib.cfi deleted file mode 100644 index b2633fd0da..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/shadeLib.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b33f5a65661f977c15047dea6b01809a1ebab5dbd6c7299a0e2888c2de8a911b -size 59794 diff --git a/Assets/Engine/Shaders/HWScripts/CryFX/vertexLib.cfi b/Assets/Engine/Shaders/HWScripts/CryFX/vertexLib.cfi deleted file mode 100644 index 66def251da..0000000000 --- a/Assets/Engine/Shaders/HWScripts/CryFX/vertexLib.cfi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8b577a8c20b7cc94e1c97909487b3a0986b47882502e120fc518880883e04435 -size 15666 diff --git a/Assets/Engine/Shaders/Hair.ext b/Assets/Engine/Shaders/Hair.ext deleted file mode 100644 index 975e29323e..0000000000 --- a/Assets/Engine/Shaders/Hair.ext +++ /dev/null @@ -1,111 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: Hair shader extension used by the editor -// for automatic shader generation (based on "Hair" shader template) -// -//////////////////////////////////////////////////////////////////////////// - -Version (1.00) - -UsesCommonGlobalFlags - -Property -{ - Name = %NORMAL_MAP - Mask = 0x1 - Property (Normal map) - Description (Use normal-map texture) - DependencySet = $TEX_Normals - DependencyReset = $TEX_Normals - Hidden -} - -Property -{ - Name = %VERTCOLORS - Mask = 0x10 - Property (Vertex Colors) - Description (Use vertex colors) -} - -Property -{ - Name = %HAIR_PASS - Mask = 0x20 - Property (Hair Pass) - DependencySet = $UserEnabled - Hidden -} - -Property -{ - Name = %ANISO_SPECULAR - Mask = 0x40 - Property (Anisotropic specular) - DependencySet = $UserEnabled - Hidden -} - -Property -{ - Name = %DIRECTION_MAP - Mask = 0x200 - Property (Direction map) - Description (Use direction map as separate texture) - DependencySet = $TEX_Detail - DependencyReset = $TEX_Detail - Hidden -} - -Property -{ - Name = %VIEW_ALIGNED_STRANDS - Mask = 0x800 - Property (View aligned strands) - Description (View aligned cards that get extruded from thin quads with texture u-coords 0 and 1) -} - -Property -{ - Name = %THIN_HAIR - Mask = 0x1000 - Property (Thin hair) - Description (Thin alpha-blended hair) -} - -Property -{ - Name = %HAIR_AMBIENT - Mask = 0x2000 - Property (Ambient cubemap) - Description (Use (nearest) cubemap specified in environment map slot for ambient lighting) - DependencyReset = $TEX_EnvCM -} - -Property -{ - Name = %ENFORCE_TILED_SHADING - Mask = 0x4000 - Property (Enforce tiled shading) - Description (Force hair to be fully affected by tiled shading. This can be expensive for dense hair meshes.) -} - -Property -{ - Name = %WIND_BENDING - Mask = 0x40000000 - Property (Wind bending) - Description (Gets affected by wind entities. Use extra shader parameters to tweak look.) -} \ No newline at end of file diff --git a/Assets/Engine/Shaders/HumanSkin.ext b/Assets/Engine/Shaders/HumanSkin.ext deleted file mode 100644 index 92429316ea..0000000000 --- a/Assets/Engine/Shaders/HumanSkin.ext +++ /dev/null @@ -1,116 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: Skin shader extension used by the editor -// for automatic shader generation (based on "Skin" shader template) -// -//////////////////////////////////////////////////////////////////////////// - - - -Version (1.00) - -UsesCommonGlobalFlags - -Property -{ - Name = %NORMAL_MAP - Mask = 0x1 - Property (Normal map) - Description (Use normal-map texture) - DependencySet = $TEX_Normals - DependencyReset = $TEX_Normals - Hidden -} - -Property -{ - Name = %SPECULAR_MAP - Mask = 0x2 - Property (Specular map) - Description (Use specular map as separate texture) - DependencySet = $TEX_Specular - DependencyReset = $TEX_Specular - Hidden -} - -Property -{ - Name = %WRINKLE_BLENDING - Mask = 0x200 - Property (Wrinkle blending) - Description (Use subsurface map alpha for wrinkle blending) - DependencyReset = $TEX_Custom - DependencyReset = $TEX_CustomSecondary -} - -Property -{ - Name = %TEMP_SKIN - Mask = 0x1000 - DependencySet = $UserEnabled - Hidden -} - -Property -{ - Name = %DECAL_MAP - Mask = 0x2000 - Property (Decal map) - Description (Use a decal map which is blended on top of the diffuse map) -} - -Property -{ - Name = %DETAIL_MAPPING - Mask = 0x20000 - Property (Detail normal-map) - Description (Tiled detail normal-map for pores and tiny details (_ddn)) -} - -Property -{ - Name = %SUBSURFACE_SCATTERING_MASK - Mask = 0x40000 - Property (Subsurface Scattering Mask) - Description (Use diffuse map alpha as subsurface scattering amount multiplier) -} - -#ifdef FEATURE_MESH_TESSELLATION -Property -{ - Name = %DISPLACEMENT_MAPPING - Mask = 0x10000000 - Property (Displacement mapping) - Description (Use displacement mapping (requires height map (_displ))) - //DependencySet = $TEX_Height - DependencyReset = $TEX_Normals -} - -Property -{ - Name = %PHONG_TESSELLATION - Mask = 0x20000000 - Property (Phong tessellation) - Description (Use rough approximation of smooth surface subdivision) -} - -Property -{ - Name = %PN_TESSELLATION - Mask = 0x40000000 - Property (PN triangles tessellation) - Description (Use rough approximation of smooth surface subdivision) -} -#endif \ No newline at end of file diff --git a/Assets/Engine/Shaders/Illum.ext b/Assets/Engine/Shaders/Illum.ext deleted file mode 100644 index edce2900d2..0000000000 --- a/Assets/Engine/Shaders/Illum.ext +++ /dev/null @@ -1,243 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: Illumination shader extension used by the editor -// for automatic shader generation (based on "Illumination" shader template) -// -//////////////////////////////////////////////////////////////////////////// - - - -Version (1.00) - -UsesCommonGlobalFlags - -Property -{ - Name = %NORMAL_MAP - Mask = 0x1 - Property (Normal map) - Description (Use normal-map texture) - DependencySet = $TEX_Normals - DependencyReset = $TEX_Normals - Hidden -} - -Property -{ - Name = %SPECULAR_MAP - Mask = 0x10 - Property (Specular map) - Description (Use specular map as separate texture) - DependencySet = $TEX_Specular - DependencyReset = $TEX_Specular - Hidden -} - -Property -{ - Name = %DETAIL_MAPPING - Mask = 0x4000 - Property (Detail mapping) - Description (Enables Detail Map texture to increase surface detail. Requires Detail map before enabling.) - DependencyReset = $TEX_Detail -} - -Property -{ - Name = %DETAIL_MAPPING_UV_SET_2 - Mask = 0x8000 - Property (Use uv set 2 for detail map) - Description (Detail map will be applied to second UV set on mesh) -} - -Property -{ - Name = %OFFSET_BUMP_MAPPING - Mask = 0x20000 - Property (Offset bump mapping) - Description (Simulates surface bump detail. Used in place of POM for lower spec configs. Requires height and normal maps before enabling.) - DependencyReset = $TEX_Normals -} - -Property -{ - Name = %FX_DISSOLVE - Mask = 0x20 - Property (Dissolve FX) - Description (Enables the use of an animated dissolve effect on the material) -} - -Property -{ - Name = %VERTCOLORS - Mask = 0x400000 - Property (Vertex Colors) - Description (Enables the use of vertex colors added to the mesh in the DCC tool) -} - -Property -{ - Name = %DECAL - Mask = 0x2000000 - Property (Decal) - Description (Enables the decal opacity map and used to prevent flickering and z-fighting) -} - -Property -{ - Name = %PARALLAX_OCCLUSION_MAPPING - Mask = 0x8000000 - Property (Parallax occlusion mapping) - Description (Simulates surface depth by parallaxing bump detail from camera view. Requires height and normal maps before enabling.) - DependencyReset = $TEX_Normals -} - -#ifdef FEATURE_MESH_TESSELLATION -Property -{ - Name = %DISPLACEMENT_MAPPING - Mask = 0x10000000 - Property (Displacement mapping) - Description (Displaces the vertices on the mesh to add depth. Requires height and normal maps before enabling.) - //DependencySet = $TEX_Height - DependencyReset = $TEX_Normals -} - -Property -{ - Name = %PHONG_TESSELLATION - Mask = 0x20000000 - Property (Phong tessellation) - Description (Tesselates geometry for smoother faces and displacement. Can suffer from inflation.) -} - -Property -{ - Name = %PN_TESSELLATION - Mask = 0x40000000 - Property (PN triangles tessellation) - Description (Best geometry tesselation for smoother faces and displacement at the cost of perfornmance) -} -#endif - -Property -{ - Name = %BLENDLAYER - Mask = 0x100 - Property (Blendlayer) - Description (Enables a second set of texture inputs and mask to be used for a layered material) -} - -Property -{ - Name = %BLENDLAYER_UV_SET_2 - Mask = 0x200 - Property (Use uv set 2 for blendlayer maps) - Description (Second blend layer maps will be applied to second UV set on mesh) -} - -Property -{ - Name = %EMITTANCE_MAP - Mask = 0x400 - Property (Emittance Map) - Description (Use emittance map texture) - DependencySet = $TEX_Emittance - DependencyReset = $TEX_Emittance - Hidden -} - -Property -{ - Name = %EMITTANCE_MAP_UV_SET_2 - Mask = 0x800 - Property (Use uv set 2 for emittance map) - Description (Emittance map will be applied to second UV set on mesh) -} - -Property -{ - Name = %ALPHAMASK_DETAILMAP - Mask = 0x800000 - Property (DetailMap mask in Diffuse alpha) - Description (Enables the diffuse map alpha to mask the detail map) -} - -Property -{ - Name = %SILHOUETTE_PARALLAX_OCCLUSION_MAPPING - Mask = 0x10000 - Property (Silhouette POM) - Description (Simulates surface depth by parallaxing bump detail and adds silhouette displacement to the mesh edge. Requires height and normal maps before enabling.) - DependencyReset = $TEX_Normals -} - -Property -{ - Name = %ALLOW_SILHOUETTE_POM - Mask = 0x40000 - DependencySet = $HW_SilhouettePom - DependencyReset = $HW_SilhouettePom - Hidden -} - -Property -{ - Name = %SUBSURFACE_SCATTERING - Mask = 0x80000 - DependencySet = $UserEnabled - Hidden -} - -Property -{ - Name = %DEPTH_FIXUP - Mask = 0x4000000 - Property (Depth Fixup) - Description (Enables to write and control depth for post-processing like depth of field) -} - -Property -{ - Name = %SAA_FILTERING - Mask = 0x80000000 - Property (Specular Antialiasing) - Description (Reduces antialiasing on bright specular meshes) -} - -Property -{ - Name = %ALLOW_SPECULAR_ANTIALIASING - Mask = 0x200000000 - DependencySet = $HW_SpecularAntialiasing - DependencyReset = $HW_SpecularAntialiasing - Hidden -} - -Property -{ - Name = %OCCLUSION_MAP - Mask = 0x40 - Property (Occlusion Map) - Description (Adds an additional texture slot for ambient occlusion) -} - -Property -{ - Name = %APPLY_FORWARD_DYNAMIC_LIGHTING - Mask = 0x400000000 - Property(Dynamic Lighting for Transparency) - Description(Adds a full forward lighting pass for transparent objects - less performant) -} diff --git a/Assets/Engine/Shaders/LensOptics.ext b/Assets/Engine/Shaders/LensOptics.ext deleted file mode 100644 index 5bd18e3895..0000000000 --- a/Assets/Engine/Shaders/LensOptics.ext +++ /dev/null @@ -1,16 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// -//////////////////////////////////////////////////////////////////////////// diff --git a/Assets/Engine/Shaders/LightBeam.ext b/Assets/Engine/Shaders/LightBeam.ext deleted file mode 100644 index 835a0dbc99..0000000000 --- a/Assets/Engine/Shaders/LightBeam.ext +++ /dev/null @@ -1,44 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: LightBeam shader extension used by the editor -// for automatic shader generation (based on "LightBeam" shader template) -// -//////////////////////////////////////////////////////////////////////////// - -Version (1.00) - -Property -{ - Name = %NOISE - Mask = 0x1 - Property (Noise map) - Description (Use 3D noise) -} - -Property -{ - Name = %FALLOFF - Mask = 0x2 - Property (Use Falloff) - Description (Use Falloff) -} - -Property -{ - Name = %DOUBLE_SAMPLING - Mask = 0x4 - Property (Extra Sampling) - Description (Add expense to reduce aliasing) -} diff --git a/Assets/Engine/Shaders/Monitor.ext b/Assets/Engine/Shaders/Monitor.ext deleted file mode 100644 index 2a20138c1f..0000000000 --- a/Assets/Engine/Shaders/Monitor.ext +++ /dev/null @@ -1,53 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: Monitor shader extension used by the editor -// for automatic shader generation -// -//////////////////////////////////////////////////////////////////////////// - - - -Version (1.00) - -Property -{ - Name = %NORMAL_MAP - Mask = 0x1 - Property (Normal map) - Description (Use normal-map texture) - DependencySet = $TEX_Normals - DependencyReset = $TEX_Normals - Hidden -} - -Property -{ - Name = %SPECULAR_MAP - Mask = 0x2 - Property (Specular map) - Description (Use specular map as separate texture) - DependencySet = $TEX_Specular - DependencyReset = $TEX_Specular - Hidden -} - -Property -{ - Name = %PIXELIZE - Mask = 0x4 - Property (Pixelized) - Description (Pixelize the diffuse texture.) -} - diff --git a/Assets/Engine/Shaders/ParticleImposter.ext b/Assets/Engine/Shaders/ParticleImposter.ext deleted file mode 100644 index 650541cfa3..0000000000 --- a/Assets/Engine/Shaders/ParticleImposter.ext +++ /dev/null @@ -1,37 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// -//////////////////////////////////////////////////////////////////////////// - -Version (1.00) - -Property -{ - Name = %NORMAL_MAP - Mask = 0x1 - Property (Normal map) - Description (Use normal-map texture) - DependencySet = $TEX_Normals - DependencyReset = $TEX_Normals - Hidden -} - -Property -{ - Name = %SOFT_PARTICLE - Mask = 0x2 - Property (Soft Particle) - Description (Soften particle intersections with world) -} diff --git a/Assets/Engine/Shaders/Particles.ext b/Assets/Engine/Shaders/Particles.ext deleted file mode 100644 index 3f24ea84b7..0000000000 --- a/Assets/Engine/Shaders/Particles.ext +++ /dev/null @@ -1,102 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: Particles shader extension used by the editor -// for automatic shader generation (based on "Particles" shader template) -// -//////////////////////////////////////////////////////////////////////////// - -Version (1.00) - -Property -{ - Name = %REFRACTION - Mask = 0x4 - Property (Refraction) - Description (Use normal-map texture as displacement for refraction) -} - -Property -{ - Name = %REFRACTION_TINTING - Mask = 0x800 - Property (Refraction Tinting) - Description (Use color texture to tint refraction) -} - -Property -{ - Name = %SCREEN_SPACE_DEFORMATION - Mask = 0x10 - Property (Screen space deformation) - Description (Use custom slot map for screen space particles deformation) - DependencyReset = $TEX_Custom -} - -Property -{ - Name = %DEFORMATION - Mask = 0x20 - Property (Deformation) - Description (Use custom slot map for per-particle deformation) - DependencyReset = $TEX_Custom -} - -Property -{ - Name = %COLOR_LOOKUP - Mask = 0x40 - Property (Color lookup) - Description (Use custom slot [1] map for applying color lookup) - DependencyReset = $TEX_CustomSecondary -} - -Property -{ - Name = %SPECULAR_LIGHTING - Mask = 0x100 - Property (Specular Lighting) - Description (Calculate specular lighting in addition to diffuse lighting) - DependencyReset = $TEX_Normals -} - -Property -{ - Name = %DEPTH_FIXUP - Mask = 0x200 - Property (Depth Fixup) - Description (Write depth for depth of field and postprocessing) -} - -Property -{ - Name = %NORMAL_MAP - Mask = 0x400 - Property (Normal map) - Description (Use normal-map texture) - DependencySet = $TEX_Normals - DependencyReset = $TEX_Normals - Hidden -} - -Property -{ - Name = %GLOW_MAP - Mask = 0x1000 - Property (Emissive map) - Description (Use this map to mask the particle emissive intensity) - DependencySet = $TEX_Detail - DependencyReset = $TEX_Detail - Hidden -} diff --git a/Assets/Engine/Shaders/RunTime.ext b/Assets/Engine/Shaders/RunTime.ext deleted file mode 100644 index 9cc96769d5..0000000000 --- a/Assets/Engine/Shaders/RunTime.ext +++ /dev/null @@ -1,1259 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: -// -//////////////////////////////////////////////////////////////////////////// - -Version (1.00) - -Property -{ - Name = %_RT_FOG - Mask = 0x1 // 1 << 0 - Precache = GeneralPS - Precache = GeneralVS - Precache = GeneralGS - Precache = GeneralDS - Precache = GeneralHS - Precache = TerrainPS - Precache = TerrainVS - Precache = VegetationVS - Precache = VegetationHS - Precache = VegetationDS - Precache = VegetationPS - Precache = SkinPS - Precache = SkinVS - Precache = HairPS - Precache = HairVS - Precache = EyePS - Precache = EyeVS - Precache = GlassPS - Precache = GlassVS - Precache = ParticleVS - Precache = ParticleHS - Precache = ParticleDS - Precache = ParticlePS - Precache = CustomRenderHS - Precache = CustomRenderDS - Precache = WaterSurfaceVS - Precache = WaterSurfacePS - Precache = WaterSurfaceHS - Precache = WaterSurfaceDS - Precache = WaterFogVolume_VS - Precache = WaterFogVolume_PS - Precache = MeshBakerPS - Precache = ParticleImposterVS - Precache = ParticleImposterPS -} - -Property -{ - Name = %_RT_AMBIENT - Mask = 0x2 // 1 << 1 - Precache = GeneralPS - Precache = SkinPS - Precache = HairPS - Precache = EyePS - Precache = GlassPS - Precache = TerrainPS - Precache = VegetationPS - Precache = ParticlePS - Precache = MeshBakerPS -} - -Property -{ - Name = %_RT_OCEAN_PARTICLE - Mask = 0x4 // 1 << 2 - Precache = PostProcessGamePS - Precache = DeferredLightPassPS - Precache = DeferredPassPS - - Precache = WaterSurfaceVS - Precache = WaterSurfacePS - Precache = WaterSurfaceHS - Precache = WaterSurfaceDS - Precache = WaterFogVolume_PS -} - -Property -{ - //Using the same mask as _RT_OCEAN_PARTICLE as we have run out of them. - //It should be safe to do this as this flag is not used in water rendering - Name = %_RT_DEPTHFIXUP - Mask = 0x4 // 1 << 2 - Precache = FurShellPS - Precache = GlassPS - Precache = IlluminationPS - Precache = GeneralPS -} - -Property -{ - Name = %_RT_DECAL_TEXGEN_2D - Mask = 0x8 // 1 << 3 - Precache = GeneralVS - Precache = GeneralDS - Precache = GeneralHS - Precache = ShadowGenVS - Precache = CausticsVS - Precache = GeneralPS - Precache = CustomRenderHS - Precache = CustomRenderDS - Precache = ZVS - Precache = ZPS - Precache = MeshBakerPS - Precache = TiledShadingCS -} - -Property -{ - Name = %_RT_DISSOLVE - Mask = 0x10 // 1 << 4 - Precache = ZPS - Precache = ZVS - Precache = ShadowGenVS - Precache = ShadowGenPS - Precache = GeneralPS - Precache = GeneralVS -} - -Property -{ - Name = %_RT_VOLUMETRIC_FOG - Mask = 0x20 // 1 << 5 - Precache = GeneralPS - Precache = GeneralVS - Precache = GeneralGS - Precache = GeneralDS - Precache = GeneralHS - Precache = TerrainPS - Precache = TerrainVS - Precache = VegetationVS - Precache = VegetationHS - Precache = VegetationDS - Precache = VegetationPS - Precache = SkinPS - Precache = SkinVS - Precache = HairPS - Precache = HairVS - Precache = EyePS - Precache = EyeVS - Precache = GlassPS - Precache = GlassVS - Precache = ParticleVS - Precache = ParticleHS - Precache = ParticleDS - Precache = ParticlePS - Precache = WaterSurfacePS - Precache = WaterFogVolume_VS - Precache = WaterFogVolume_PS - Precache = MeshBakerPS - Precache = CloudVS - Precache = CloudPS - Precache = FogPostProcessPS - Precache = ParticleImposterVS - Precache = ParticleImposterPS -} - -Property -{ - Name = %_RT_NEAREST - Mask = 0x40 // 1 << 6 - Precache = ShadowGenVS - Precache = ShadowMaskGenVS - Precache = ShadowMaskGenPS - Precache = ZVS - Precache = MotionBlurVS - Precache = SkinVS - Precache = GeneralVS - Precache = VegetationVS - Precache = CausticsVS - Precache = CustomRenderVS - Precache = DebugPassVS -} - -Property -{ - Name = %_RT_GLOBAL_ILLUMINATION - Mask = 0x80 // 1 << 7 - Precache = ParticleVS - Precache = ParticleHS - Precache = ParticleDS - Precache = ParticlePS -} - -Property -{ - Name = %_RT_ALPHATEST - Mask = 0x100 // 1 << 8 - Precache = ShadowGenVS - Precache = ShadowGenPS - Precache = ZPS - Precache = ZVS - Precache = MotionBlurPS - Precache = CustomRenderPS - Precache = VegetationVS - Precache = VegetationHS - Precache = VegetationDS - Precache = VegetationPS - Precache = GeneralPS - Precache = GlassPS - Precache = GlassVS - Precache = MotionBlurVS - Precache = MeshBakerPS -} - -Property -{ - Name = %_RT_SOFT_PARTICLE - Mask = 0x200 // 1 << 9 - Precache = ParticleVS - Precache = ParticleHS - Precache = ParticleDS - Precache = ParticlePS -} - -Property -{ - Name = %_RT_HDR_MODE - Mask = 0x400 // 1 << 10 - Precache = GeneralPS - Precache = TerrainPS - Precache = VegetationPS - Precache = SkinPS - Precache = HairPS - Precache = EyePS - Precache = GlassPS - Precache = ParticlePS - Precache = WaterSurfacePS - Precache = ParticleImposterPS -} - -Property -{ - Name = %_RT_PARTICLE_SHADOW - Mask = 0x800 // 1 << 11 - Precache = ParticlePS - Precache = ParticleVS - Precache = ParticleHS - Precache = ParticleDS -} - -Property -{ - Name = %_RT_SAMPLE1 - Mask = 0x1000 // 1 << 12 - Precache = CustomRenderHS - Precache = CustomRenderDS - Precache = CustomRenderVS - Precache = CustomRenderPS - Precache = SkinPS - Precache = HDRPostProcessPS - Precache = PostMotionBlurVS - Precache = PostMotionBlurPS - Precache = PostSunShaftsPS - Precache = PostProcessGamePS - Precache = PostDofPS - Precache = PostEffectsVS - Precache = PostEffectsPS - Precache = PostAA_PS - Precache = DeferredLightPassPS - Precache = DeferredPassPS - Precache = PostHUD3D_VS - Precache = PostHUD3D_PS - Precache = ParticlePS - Precache = ParticleVS - Precache = DeferredRainPS - Precache = ShadowMaskGenPS - - Precache = WaterFogVolume_VS - Precache = WaterFogVolume_PS - Precache = WaterSurfaceVS - Precache = WaterSurfacePS - Precache = WaterSurfaceHS - Precache = WaterSurfaceDS - - Precache = BeamPS - Precache = ParticleHS - Precache = ParticleDS - - Precache = TiledShadingCS - Precache = VolumeLightInjectionCS - Precache = ResolvePS - Precache = VideoPS -} - -Property -{ - Name = %_RT_SAMPLE2 - Mask = 0x2000 // 1 << 13 - - Precache = HDRPostProcessPS - Precache = PostMotionBlurVS - Precache = PostMotionBlurPS - Precache = PostAA_PS - Precache = PostSunShaftsPS - Precache = PostProcessGamePS - Precache = DeferredDecalPassPS - Precache = DeferredLightPassPS - Precache = DeferredPassPS - Precache = CustomRenderPS - Precache = DeferredRainPS - - Precache = ShadowMaskGenVS - Precache = ShadowMaskGenPS - Precache = ParticlePS - Precache = ParticleVS - Precache = ParticleHS - Precache = ParticleDS - - Precache = BeamPS - Precache = LensOpticsPS - Precache = WaterFogVolume_PS - Precache = TiledShadingCS - Precache = FogPostProcessPS - Precache = VideoPS -} - -Property -{ - Name = %_RT_SAMPLE3 - Mask = 0x4000 // 1 << 14 - Precache = ParticlePS - Precache = ParticleVS - Precache = ParticleHS - Precache = ParticleDS - Precache = PostAA_PS - Precache = HDRPostProcessPS - Precache = BeamPS - Precache = DeferredLightPassPS - Precache = DeferredPassPS - Precache = DeferredRainPS - Precache = ShadowMaskGenVS - Precache = ShadowMaskGenPS - Precache = DeferredDecalPassPS - Precache = DeferredDecalEmissivePassPS - Precache = ResolvePS - - Precache = LensOpticsPS - Precache = WaterFogVolume_PS - Precache = TiledShadingCS - Precache = VideoPS -} - -Property -{ - Name = %_RT_POINT_LIGHT - Mask = 0x8000 // 1 << 15 - Precache = FogPassVolShadowsInterleavePassPS - Precache = WaterFogVolume_PS - Precache = ConeTraceDiffusePS -} - -Property -{ - Name = %_RT_ALPHABLEND - Mask = 0x10000 // 1 << 16 - Precache = GeneralHS - Precache = GeneralDS - Precache = FogPassVolShadowsInterleavePassPS - Precache = ParticlePS - Precache = ParticleVS - Precache = HairVS - Precache = EyeVS - Precache = VegetationVS - Precache = GeneralVS - Precache = GlassVS - Precache = CustomRenderHS - Precache = CustomRenderDS - Precache = ZVS - Precache = ZPS - Precache = MotionBlurPS -} - -Property -{ - Name = %_RT_ANIM_BLEND - Mask = 0x20000 // 1 << 17 - Precache = ParticlePS - Precache = ParticleVS - Precache = ParticleHS - Precache = ParticleDS - Precache = ParticleGS -} - -Property -{ - Name = %_RT_QUALITY - Mask = 0x40000 // 1 << 18 - AutoPrecache - Precache = ShadowGenVS - Precache = ShadowGenPS - Precache = ZVS - Precache = ZPS - Precache = FogPassVolShadowsInterleavePassPS - Precache = GeneralPS - Precache = GeneralVS - Precache = SkinPS - Precache = SkinVS - Precache = HairPS - Precache = HairVS - Precache = EyePS - Precache = EyeVS - Precache = GlassPS - Precache = GlassVS - Precache = TerrainPS - Precache = TerrainVS - Precache = VegetationPS - Precache = VegetationHS - Precache = VegetationDS - Precache = VegetationVS - Precache = MotionBlurVS - Precache = MotionBlurPS - Precache = CausticsVS - Precache = ParticlePS - Precache = WaterSurfaceVS - Precache = WaterSurfacePS - - Precache = PostMotionBlurVS - Precache = PostMotionBlurPS - Precache = PostSunShaftsPS - Precache = SpriteDilatePS - Precache = ShadowMaskGenPS - - Precache = HDRPostProcessVS - Precache = HDRPostProcessPS - Precache = FogPostProcessPS - Precache = PostProcessGameVS - Precache = PostProcessGamePS - - Precache = DistanceCloudsPS - Precache = DeferredLightPassPS - Precache = VolumeLightInjectionCS -} - -Property -{ - Name = %_RT_QUALITY1 - Mask = 0x80000 // 1 << 19 - AutoPrecache - Precache = ShadowGenVS - Precache = ShadowGenPS - Precache = ZVS - Precache = ZPS - Precache = FogPassVolShadowsInterleavePassPS - Precache = GeneralPS - Precache = GeneralVS - Precache = SkinPS - Precache = SkinVS - Precache = HairPS - Precache = HairVS - Precache = EyePS - Precache = EyeVS - Precache = GlassPS - Precache = GlassVS - Precache = TerrainPS - Precache = TerrainVS - Precache = VegetationPS - Precache = VegetationHS - Precache = VegetationDS - Precache = VegetationVS - Precache = MotionBlurVS - Precache = MotionBlurPS - Precache = CausticsVS - Precache = ParticlePS - Precache = WaterSurfaceVS - Precache = WaterSurfacePS - - Precache = PostMotionBlurVS - Precache = PostMotionBlurPS - Precache = PostSunShaftsPS - Precache = SpriteDilatePS - Precache = ShadowMaskGenPS - - Precache = HDRPostProcessVS - Precache = HDRPostProcessPS - Precache = FogPostProcessPS - Precache = PostProcessGameVS - Precache = PostProcessGamePS - - Precache = DistanceCloudsPS - Precache = DeferredLightPassPS - Precache = VolumeLightInjectionCS -} - -Property -{ - Name = %_RT_INSTANCING_ATTR - Mask = 0x100000 // 1 << 20 - Precache = GeneralVS - Precache = SkinVS - Precache = HairVS - Precache = EyeVS - Precache = GlassVS - Precache = VegetationVS - Precache = ShadowGenVS - Precache = ZVS - Precache = MotionBlurVS - Precache = CausticsVS - Precache = CustomRenderVS - Precache = DebugPassVS -} - -Property -{ - Name = %_RT_ENVIRONMENT_CUBEMAP - Mask = 0x200000 // 1 << 21 - Precache = ParticlePS -} - -Property -{ - Name = %_RT_TILED_SHADING - Mask = 0x400000 // 1 << 22 - Precache = EyePS - Precache = HairPS - Precache = GlassPS - Precache = IlluminationPS -} - -Property -{ - Name = %_RT_NO_TESSELLATION - Mask = 0x800000 // 1 << 23 - Precache = GeneralVS - Precache = GeneralPS - Precache = ShadowGenVS - Precache = ShadowGenPS - Precache = ZVS - Precache = ZPS - Precache = MotionBlurVS - Precache = MotionBlurPS - Precache = CustomRenderVS - Precache = SkinVS - Precache = SkinPS - Precache = VegetationVS - Precache = VegetationHS - Precache = VegetationDS - Precache = VegetationPS - Precache = ParticleVS - Precache = ParticleHS - Precache = ParticleDS - Precache = ParticlePS - Precache = DebugPassVS -} - -Property -{ - Name = %_RT_APPLY_TOON_SHADING - Mask = 0x1000000 // 1 << 24 - Precache = DeferredLightPassPS - Precache = TiledShadingCS -} - - -Property -{ - Name = %_RT_LIGHT_TEX_PROJ - Mask = 0x2000000 // 1 << 25 - Precache = DeferredLightPassPS - Precache = ParticleVS - Precache = ParticleDS - Precache = ConeTraceDiffusePS -} - -Property -{ - Name = %_RT_VERTEX_VELOCITY - Mask = 0x4000000 // 1 << 26 - Precache = ZVS - Precache = MotionBlurVS -} - -Property -{ - Name = %_RT_SKINNING_DUAL_QUAT - Mask = 0x8000000 // 1 << 27 - Precache = GeneralVS - Precache = SkinVS - Precache = HairVS - Precache = EyeVS - Precache = GlassVS - Precache = VegetationVS - Precache = ShadowGenVS - Precache = ZVS - Precache = MotionBlurVS - Precache = CausticsVS - Precache = CustomRenderVS - Precache = DebugPassVS -} - -Property -{ - Name = %_RT_SKINNING_DQ_LINEAR - Mask = 0x10000000 // 1 << 28 - Precache = GeneralVS - Precache = SkinVS - Precache = HairVS - Precache = EyeVS - Precache = GlassVS - Precache = VegetationVS - Precache = ShadowGenVS - Precache = ZVS - Precache = MotionBlurVS - Precache = CausticsVS - Precache = CustomRenderVS - Precache = DebugPassVS -} - -Property -{ - Name = %_RT_BLEND_WITH_TERRAIN_COLOR - Mask = 0x20000000 // 1 << 29 - Precache = ZVS - Precache = ZPS - Precache = GeneralHS - Precache = GeneralDS - Precache = VegetationVS - Precache = VegetationHS - Precache = VegetationDS - Precache = VegetationPS - Precache = ConeTraceDiffusePS -} - -Property -{ - Name = %_RT_MOTION_BLUR - Mask = 0x40000000 // 1 << 30 - Precache = ParticleVS - Precache = ParticleHS - Precache = GPUParticleCS - Precache = ParticleDS - Precache = ParticlePS - Precache = ZVS - Precache = ZGS - Precache = ZPS - Precache = GeneralHS - Precache = GeneralDS -} - -Property -{ - Name = %_RT_LIGHTVOLUME0 - Mask = 0x80000000 // 1 << 31 - Precache = ParticleVS - Precache = ParticleHS - Precache = ParticleDS - Precache = ParticlePS - Precache = DeferredLightPassPS - Precache = DeferredPassPS - Precache = VolumeLightInjectionCS - Precache = RenderDownscaledShadowMapPS -} - -Property -{ - //Using the same mask as _RT_LIGHTVOLUME0 as we have run out of them. - //It should be safe to do this as this flag is not used in the deferred lighting pass or particles. - - Name = %_RT_SRGB0 - Mask = 0x80000000 // 1 << 31 - Precache = PostAA_PS - Precache = ResolvePS - Precache = HDRPostProcessPS -} - -Property -{ - Name = %_RT_LIGHTVOLUME1 - Mask = 0x100000000 // 1 << 32 - Precache = ParticleVS - Precache = ParticleHS - Precache = ParticleDS - Precache = ParticlePS - Precache = VolumeLightInjectionCS - Precache = RenderDownscaledShadowMapPS -} - -Property -{ - //Using the same mask as _RT_LIGHTVOLUME1 as we have run out of them. - //It should be safe to do this as this flag is not used in the deferred lighting pass or particles. - - Name = %_RT_SRGB1 - Mask = 0x100000000 // 1 << 32 - Precache = PostAA_PS - Precache = ResolvePS - Precache = HDRPostProcessPS -} - -Property -{ - Name = %_RT_NOZPASS - Mask = 0x200000000 // 1 << 33 - Precache = VegetationVS - Precache = VegetationHS - Precache = VegetationDS - Precache = VegetationPS -} - -Property -{ - //Using the same mask as _RT_NOZPASS as we have run out of them. - //It should be safe to do this as this flag is not used in vegetation pass. - - Name = %_RT_SRGB2 - Mask = 0x200000000 // 1 << 33 - Precache = PostAA_PS - Precache = ResolvePS - Precache = HDRPostProcessPS -} - -Property -{ - Name = %_RT_SHADOW_MIXED_MAP_G16R16 - Mask = 0x400000000 // 1 << 34 - Precache = FogPassVolShadowsInterleavePassPS - Precache = WaterFogVolume_PS - - Precache = ShadowMaskGenVS - Precache = ShadowMaskGenPS -} - -Property -{ - Name = %_RT_SHADOW_JITTERING - Mask = 0x800000000 // 1 << 35 - Precache = FogPassVolShadowsInterleavePassPS - Precache = TerrainPS - Precache = WaterFogVolume_PS - - Precache = ShadowMaskGenVS - Precache = ShadowMaskGenPS - - Precache = HairPS - Precache = EyePS -} - -Property -{ - Name = %_RT_ADDITIVE_BLENDING - Mask = 0x1000000000 // 1 << 36 - Precache = GlassPS - Precache = GeneralPS - Precache = HairPS - Precache = ParticlePS -} - -Property -{ - Name = %_RT_SAMPLE0 - Mask = 0x2000000000 // 1 << 37 - Precache = CustomRenderVS - Precache = CustomRenderPS - Precache = SkinPS - Precache = GlassPS - - Precache = FogPostProcessPS - Precache = HDRPostProcessPS - Precache = ParticleVS - Precache = ParticleHS - Precache = ParticleDS - Precache = ParticlePS - Precache = PostHUD3D_VS - Precache = PostHUD3D_PS - Precache = PostMotionBlurVS - Precache = PostMotionBlurPS - Precache = PostAA_PS - Precache = PostSunShaftsPS - Precache = PostProcessGameVS - Precache = PostProcessGamePS - Precache = PostDofPS - Precache = PostEffectsVS - Precache = PostEffectsPS - Precache = DeferredDecalPassPS - Precache = DeferredDecalEmissivePassPS - Precache = DeferredLightPassPS - Precache = DeferredPassPS - Precache = SceneRainVS - Precache = SceneRainPS - Precache = DeferredRainPS - Precache = ResolveVS - Precache = ResolvePS - - Precache = WaterSurfaceVS - Precache = WaterSurfacePS - Precache = WaterSurfaceHS - Precache = WaterSurfaceDS - Precache = WaterFogVolume_PS - - Precache = BeamPS - Precache = LensOpticsVS - Precache = TiledShadingCS - Precache = VolumeLightInjectionCS - Precache = VideoPS -} - -// Reserved for post processes/deferred - do not use for light/common shaders -Property -{ - Name = %_RT_SAMPLE5 - Mask = 0x4000000000 // 1 << 38 - - Precache = ZVS - Precache = GeneralVS - Precache = GeneralHS - Precache = GeneralDS - - Precache = HDRPostProcessPS - Precache = PostSunShaftsPS - Precache = PostProcessGamePS - Precache = DeferredPassPS - Precache = DeferredPassVS - Precache = DeferredLightPassPS - Precache = PostMotionBlurPS - Precache = DeferredDecalPassPS - Precache = DeferredDecalEmissivePassPS - Precache = ResolvePS - Precache = FogPassVolShadowsInterleavePassPS - Precache = CustomRenderHS - Precache = CustomRenderDS - Precache = CustomRenderPS - Precache = WaterSurfaceVS - Precache = WaterSurfacePS - Precache = WaterSurfaceHS - Precache = WaterSurfaceDS - Precache = WaterFogVolume_PS - Precache = PostAA_PS - - Precache = BeamPS - Precache = LensOpticsVS - Precache = TiledShadingCS - Precache = VolumeLightInjectionCS - Precache = ReprojectVolumetricFogCS -} - -Property -{ - Name = %_RT_HW_PCF_COMPARE - Mask = 0x8000000000 // 1 << 39 - Precache = FogPassVolShadowsInterleavePassPS - Precache = ShadowGenVS - Precache = ShadowGenPS - - Precache = DeferredLightPassPS - Precache = ShadowMaskGenVS - Precache = ShadowMaskGenPS - - Precache = WaterFogVolume_PS - Precache = ConeTraceDiffusePS -} - -Property -{ - Name = %_RT_REVERSE_DEPTH - Mask = 0x10000000000 // 1 << 40 - Precache = DistanceCloudsVS - Precache = PostProcessGamePS - Precache = TerrainVS - Precache = ZVS - Precache = ZPS - Precache = UnderwaterGodRays - Precache = WaterSurfaceVS - Precache = WaterFogVolume_VS - Precache = GeneralVS - Precache = LensOpticsVS -} - -Property -{ - Name = %_RT_DEBUG0 - Mask = 0x20000000000 // 1 << 41 - - Runtime -} - -Property -{ - Name = %_RT_DEBUG1 - Mask = 0x40000000000 // 1 << 42 - Runtime -} - -Property -{ - Name = %_RT_DEBUG2 - Mask = 0x80000000000 // 1 << 43 - Runtime -} - -Property -{ - Name = %_RT_DEBUG3 - Mask = 0x100000000000 // 1 << 44 - Runtime -} - -Property -{ - Name = %_RT_CUBEMAP0 - Mask = 0x200000000000 // 1 << 45 - Precache = ShadowGenPS - Precache = ShadowGenVS - - Precache = PostSunShaftsPS - Precache = PostProcessGamePS - - Precache = DeferredDecalPassVS - Precache = DeferredDecalPassPS - Precache = DeferredDecalEmissivePassPS - Precache = DeferredLightPassVS - Precache = DeferredLightPassPS - Precache = DeferredPassVS - Precache = DeferredPassPS - - Precache = WaterFogVolume_PS - Precache = TiledShadingCS -} - -Property -{ - Name = %_RT_SAMPLE4 - Mask = 0x400000000000 // 1 << 46 - Precache = ShadowGenVS - Precache = ShadowGenPS - Precache = PostAA_PS - - Precache = HDRPostProcessPS - Precache = PostProcessGamePS - Precache = DeferredLightPassPS - Precache = DeferredPassPS - Precache = PostSunShaftsPS - Precache = PostMotionBlurPS - - Precache = WaterSurfaceVS - Precache = WaterSurfacePS - Precache = WaterSurfaceHS - Precache = WaterSurfaceDS - Precache = WaterFogVolume_PS - Precache = DeferredDecalPassPS - Precache = DeferredDecalEmissivePassPS - - Precache = BeamPS - Precache = LensOpticsPS - - Precache = TiledShadingCS - - Precache = ConeTraceDiffusePS - Precache = VolumeLightInjectionCS -} - -Property -{ - Name = %_RT_SPRITE - Mask = 0x800000000000 // 1 << 47 - Precache = VegetationVS - Precache = VegetationPS - Precache = ParticleVS - Precache = ParticleHS - Precache = ParticleDS -} - -Property -{ - Name = %_RT_GPU_PARTICLE_SHADOW_PASS - Mask = 0x1000000000000 // 1 << 48 - Precache = ParticleVS - Precache = ParticlePS -} - -Property -{ - Name = %_RT_GPU_PARTICLE_DEPTH_COLLISION - Mask = 0x2000000000000 // 1 << 49 - Precache = GPUParticleCS -} - -Property -{ - Name = %_RT_GPU_PARTICLE_TURBULENCE - Mask = 0x4000000000000 // 1 << 50 - Precache = GPUParticleCS -} - -Property -{ - Name = %_RT_GPU_PARTICLE_UV_ANIMATION - Mask = 0x8000000000000 // 1 << 51 - Precache = ParticleVS - Precache = ParticlePS -} - -Property -{ - Name = %_RT_GPU_PARTICLE_NORMAL_MAP - Mask = 0x10000000000000 // 1 << 52 - Precache = ParticleVS - Precache = ParticlePS -} - -Property -{ - Name = %_RT_GPU_PARTICLE_GLOW_MAP - Mask = 0x20000000000000 // 1 << 53 - Precache = ParticleVS - Precache = ParticlePS -} - -Property -{ - Name = %_RT_GPU_PARTICLE_CUBEMAP_DEPTH_COLLISION - Mask = 0x40000000000000 // 1 << 54 - Precache = GPUParticleCS -} - -Property -{ - Name = %_RT_GPU_PARTICLE_WRITEBACK_DEATH_LOCATIONS - Mask = 0x80000000000000 // 1 << 55 - Precache = GPUParticleCS -} - -Property -{ - Name = %_RT_GPU_PARTICLE_TARGET_ATTRACTION - Mask = 0x100000000000000 // 1<< 56 - Precache = GPUParticleCS -} - -Property -{ - // Using the same mask as _RT_GPU_PARTICLE_TARGET_ATTRACTION as we have run out of them. - //It should be safe to do this as this flag is not used in the deferred lighting pass. - - Name = %_RT_DEFERRED_RENDER_TARGET_OPTIMIZATION - Mask = 0x100000000000000 // 1<< 56 - - Precache = DeferredPassPS - Precache = DeferredPassVS - Precache = DeferredLightPassPS - Precache = TiledShadingCS - Precache = LightPassPS - Precache = LightPassGmemPS - Precache = ConeTraceDiffusePS - Precache = VolumeLightInjectionCS - Precache = CubemapPassPS - Precache = CubemapPassGmemPS - Precache = DeferredShadowGmemPS - Precache = DeferredShadingPassPS - Precache = DeferredShadingPassGmemPS - Precache = AmbientPS - Precache = ShadowMaskGenVS - Precache = ShadowMaskGenPS - Precache = DeferredDecalPassPS - Precache = DeferredDecalEmissivePassPS -} - -Property -{ - Name = %_RT_GPU_PARTICLE_SHAPE_ANGLE - Mask = 0x200000000000000 // 1 << 57 - Precache = GPUParticleCS -} - -Property -{ - - // Using the same mask as _RT_GPU_PARTICLE_SHAPE_ANGLE as we have run out of them. - //It should be safe to do this as this flag is not used in the deferred lighting pass. - - Name = %_RT_SLIM_GBUFFER - Mask = 0x200000000000000 // 1 << 57 - AutoPrecache - Precache = ShadowGenVS - Precache = ShadowGenPS - Precache = ZVS - Precache = ZPS - Precache = FogPassVolShadowsInterleavePassPS - Precache = GeneralPS - Precache = GeneralVS - Precache = SkinPS - Precache = SkinVS - Precache = HairPS - Precache = HairVS - Precache = EyePS - Precache = EyeVS - Precache = GlassPS - Precache = GlassVS - Precache = TerrainPS - Precache = TerrainVS - Precache = VegetationPS - Precache = VegetationHS - Precache = VegetationDS - Precache = VegetationVS - Precache = MotionBlurVS - Precache = MotionBlurPS - Precache = CausticsVS - Precache = ParticlePS - Precache = WaterSurfaceVS - Precache = WaterSurfacePS - - Precache = PostMotionBlurVS - Precache = PostMotionBlurPS - Precache = PostSunShaftsPS - Precache = SpriteDilatePS - Precache = ShadowMaskGenPS - - Precache = HDRPostProcessVS - Precache = HDRPostProcessPS - Precache = FogPostProcessPS - Precache = PostProcessGameVS - Precache = PostProcessGamePS - - Precache = DistanceCloudsPS - Precache = DeferredLightPassPS - Precache = VolumeLightInjectionCS - Precache = HDRPostProcessPS - Precache = PostProcessGamePS - Precache = DeferredLightPassPS - Precache = DeferredPassPS - Precache = PostSunShaftsPS - Precache = PostMotionBlurPS - - Precache = WaterSurfaceVS - Precache = WaterSurfacePS - Precache = WaterSurfaceHS - Precache = WaterSurfaceDS - Precache = WaterFogVolume_PS - Precache = DeferredDecalPassPS - Precache = DeferredDecalEmissivePassPS - - Precache = BeamPS - Precache = LensOpticsPS - - Precache = DeferredShadingPassPS - Precache = DeferredPassVS - Precache = DeferredLightPassPS - Precache = TiledShadingCS - Precache = LightPassPS - Precache = ConeTraceDiffusePS - Precache = VolumeLightInjectionCS - Precache = CubemapPassPS - Precache = CubemapPassGmemPS - - Precache = DeferredSnowPS - Precache = DeferredRainPS - Precache = SSRCompositionPS - Precache = SSRRaytracePS -} - -Property -{ - Name = %_RT_GPU_PARTICLE_SHAPE_BOX - Mask = 0x400000000000000 // 1 << 58 - Precache = GPUParticleCS - Precache = ParticleVS -} - -Property -{ - // Using the same mask as _RT_GPU_PARTICLE_SHAPE_BOX as we have run out of them. - // It should be safe to do this as this (GPU Particles shouldn't have skinning) - - Name = %_RT_SKINNING_MATRIX - Mask = 0x400000000000000 // 1 << 58 - Precache = GeneralVS - Precache = SkinVS - Precache = HairVS - Precache = EyeVS - Precache = GlassVS - Precache = VegetationVS - Precache = ShadowGenVS - Precache = ZVS - Precache = MotionBlurVS - Precache = CausticsVS - Precache = CustomRenderVS - Precache = DebugPassVS -} - -Property -{ - Name = %_RT_GPU_PARTICLE_SHAPE_POINT - Mask = 0x800000000000000 // 1 << 59 - Precache = GPUParticleCS -} - -Property -{ - //Using the same mask as _RT_GPU_PARTICLE_SHAPE_POINT as we have run out of them. - //It should be safe to do this as this flag is not used in the deferred lighting pass. - Name = %_RT_APPLY_SSDO - Mask = 0x800000000000000 // 1 << 59. - Precache = DeferredPassPS - Precache = DeferredPassVS - Precache = DeferredLightPassPS - Precache = TiledShadingCS -} - -//Using the same mask as _RT_APPLY_SSDO as we have run out of them. -//It should be safe to do this as this flag is not used in particle shaders. -Property -{ - Name = %_RT_FOG_VOLUME_HIGH_QUALITY_SHADER - Mask = 0x800000000000000 // 1 << 59 - Precache = ParticleVS - Precache = ParticleHS - Precache = ParticleDS - Precache = ParticlePS -} - -Property -{ - Name = %_RT_GPU_PARTICLE_SHAPE_CIRCLE - Mask = 0x1000000000000000 // 1 << 60 - Precache = GPUParticleCS - Precache = ParticleVS -} - -Property -{ - Name = %_RT_GPU_PARTICLE_SHAPE_SPHERE - Mask = 0x2000000000000000 // 1 << 61 - Precache = GPUParticleCS - Precache = ParticleVS -} - -Property -{ - Name = %_RT_GPU_PARTICLE_WIND - Mask = 0x4000000000000000 // 1 << 62 - Precache = GPUParticleCS - -} -Property -{ - Name = %_RT_MULTI_LAYER_ALPHA_BLEND - Mask = 0x8000000000000000 // 1 << 63 - Precache = FurShellPS - Precache = FurFinsPS - Precache = GlassPS - Precache = GeneralPS - Precache = HairPS - Precache = ParticlePS - Precache = MultiLayerAlphaBlendResolvePS -} - - diff --git a/Assets/Engine/Shaders/Scopes.ext b/Assets/Engine/Shaders/Scopes.ext deleted file mode 100644 index ae84db49ca..0000000000 --- a/Assets/Engine/Shaders/Scopes.ext +++ /dev/null @@ -1,42 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// -//////////////////////////////////////////////////////////////////////////// - -Version (1.00) - -Property -{ - Name = %REFLEX_SIGHT - Mask = 0x2 - Property (Reflex sight) - Description (New reflex sight version) -} - -Property -{ - Name = %SCOPE_ZOOMED_REFRACTION - Mask = 0x4 - Property (Scope zoomed refraction) - Description (Scope zoomed in refraction) -} - -Property -{ - Name = %HOLO_SIGHT - Mask = 0x8 - Property (Use holo sight depth) - Description (Holographic sight with depth modifier) -} \ No newline at end of file diff --git a/Assets/Engine/Shaders/ShaderProfiles.txt b/Assets/Engine/Shaders/ShaderProfiles.txt deleted file mode 100644 index ae5178118e..0000000000 --- a/Assets/Engine/Shaders/ShaderProfiles.txt +++ /dev/null @@ -1,11 +0,0 @@ - -Version (1.00) - -Profile 'Low' -{ -} - -Profile 'High' -{ - UseNormalAlpha -} diff --git a/Assets/Engine/Shaders/ShadowMaskGen.ext b/Assets/Engine/Shaders/ShadowMaskGen.ext deleted file mode 100644 index 6c65742d63..0000000000 --- a/Assets/Engine/Shaders/ShadowMaskGen.ext +++ /dev/null @@ -1,21 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: -// -//////////////////////////////////////////////////////////////////////////// - - - -Version (1.00) diff --git a/Assets/Engine/Shaders/SketchTerrain.ext b/Assets/Engine/Shaders/SketchTerrain.ext deleted file mode 100644 index 3dfad9d1cf..0000000000 --- a/Assets/Engine/Shaders/SketchTerrain.ext +++ /dev/null @@ -1,27 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// -//////////////////////////////////////////////////////////////////////////// -Version (1.00) - -UsesCommonGlobalFlags - -Property -{ - Name = %TEMP_TERRAIN - Mask = 0x40000000 - DependencySet = $UserEnabled - Hidden -} diff --git a/Assets/Engine/Shaders/SkyHDR.ext b/Assets/Engine/Shaders/SkyHDR.ext deleted file mode 100644 index 87d1a00ca5..0000000000 --- a/Assets/Engine/Shaders/SkyHDR.ext +++ /dev/null @@ -1,39 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// -//////////////////////////////////////////////////////////////////////////// -Version (1.00) - -Property -{ - Name = %NO_MOON - Mask = 0x02 - Property (No moon) - Description (Sky dome doesn't render moon) -} -Property -{ - Name = %NO_NIGHT_SKY_GRADIENT - Mask = 0x04 - Property (No night sky gradient) - Description (Sky dome doesn't render night sky gradient) -} -Property -{ - Name = %NO_DAY_SKY_GRADIENT - Mask = 0x08 - Property (No day sky gradient) - Description (Sky dome doesn't render day sky gradient) -} diff --git a/Assets/Engine/Shaders/StarterGame_GeometryBeamScaling.ext b/Assets/Engine/Shaders/StarterGame_GeometryBeamScaling.ext deleted file mode 100644 index 25e395f42d..0000000000 --- a/Assets/Engine/Shaders/StarterGame_GeometryBeamScaling.ext +++ /dev/null @@ -1,43 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// -//////////////////////////////////////////////////////////////////////////// - - -Property -{ - Name = %NOISE - Mask = 0x1 - Property (Dust & Turbulence) - Description (Add a dust overlay [spec map for dust, bump map for turbulence] ) -} - - -Property -{ - Name = %RECEIVE_SHADOWS - Mask = 0x2 - Property (Receive Shadows) - Description (Enable shadow receiving) -} - - -Property -{ - Name = %UV_VIGNETTING - Mask = 0x4 - Property (UV Vignetting) - Description (Enabling this will cause contents to fade out at UV boundaries) -} \ No newline at end of file diff --git a/Assets/Engine/Shaders/Statics.ext b/Assets/Engine/Shaders/Statics.ext deleted file mode 100644 index e7d93fbeaf..0000000000 --- a/Assets/Engine/Shaders/Statics.ext +++ /dev/null @@ -1,74 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -//////////////////////////////////////////////////////////////////////////// - -Version (1.00) - -Property -{ - Name = %ST_GMEM_128BPP - Mask = 0x1 // 1 << 0 -} - -Property -{ - Name = %ST_GMEM_256BPP - Mask = 0x2 // 1 << 1 -} - -Property -{ - Name = %ST_GMEM_PLS - Mask = 0x4 // 1 << 2 -} - -Property -{ - Name = %ST_LLVM_DIRECTX_SHADER_COMPILER - Mask = 0x8 // 1 << 3 -} - -Property -{ - Name = %ST_FIXED_POINT - Mask = 0x10 // 1 << 4 -} - -Property -{ - Name = %ST_GMEM_RT_GREATER_FOUR - Mask = 0x20 // 1 << 5 -} - -Property -{ - Name = %ST_NO_DEPTH_CLIPPING - Mask = 0x40 // 1 << 6 -} - -Property -{ - Name = %ST_FEATURE_FETCH_DEPTHSTENCIL - Mask = 0x80 // 1 << 7 -} - -property -{ - Name = %ST_GMEM_VELOCITY_BUFFER - Mask = 0x100 // 1 << 8 -} - -property -{ - Name = %ST_GLES3_0 - Mask = 0x200 // 1 << 9 -} diff --git a/Assets/Engine/Shaders/TemplBeamProc.ext b/Assets/Engine/Shaders/TemplBeamProc.ext deleted file mode 100644 index 485702ace7..0000000000 --- a/Assets/Engine/Shaders/TemplBeamProc.ext +++ /dev/null @@ -1,34 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// -//////////////////////////////////////////////////////////////////////////// - -Version (1.00) - -Property -{ - Name = %NOISE - Mask = 0x1 - Property (Noise map) - Description (Use animated 3D noise) -} - -Property -{ - Name = %MUZZLEFLASH - Mask = 0x2 - Property (Muzzleflash) - Description (Use as muzzle flash) -} diff --git a/Assets/Engine/Shaders/Terrain.ext b/Assets/Engine/Shaders/Terrain.ext deleted file mode 100644 index 6e8b5dc6f3..0000000000 --- a/Assets/Engine/Shaders/Terrain.ext +++ /dev/null @@ -1,79 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: TerrainLayer shader extension used by the editor -// for automatic shader generation (based on "TerrainLayer" shader template) -// -//////////////////////////////////////////////////////////////////////////// - -Version (1.00) - -UsesCommonGlobalFlags - -Property -{ - Name = %NORMAL_MAP - Mask = 0x1 - Property (Normal map) - Description (Use normal-map texture) - DependencySet = $TEX_Normals - DependencyReset = $TEX_Normals - Hidden -} - -Property -{ - Name = %SPECULAR_MAP - Mask = 0x200 - Property (Specular map) - Description (Use specular map as separate texture) - DependencySet = $TEX_Specular - DependencyReset = $TEX_Specular - Hidden -} - -Property -{ - Name = %OFFSET_BUMP_MAPPING - Mask = 0x1000 - Property (Offset bump mapping) - Description (Use offset bump mapping (requires height map (_displ))) - DependencyReset = $TEX_Normals -} - -Property -{ - Name = %DETAIL_MAPPING - Mask = 0x8000 - Property (Detail mapping) - Description (Use detail mapping) - DependencyReset = $TEX_Detail -} - -Property -{ - Name = %PARALLAX_OCCLUSION_MAPPING - Mask = 0x8000000 - Property (Parallax occlusion mapping) - Description (Use parallax occlusion mapping (requires height map (_displ))) - DependencyReset = $TEX_Normals -} - -Property -{ - Name = %TEMP_TERRAIN - Mask = 0x40000000 - DependencySet = $UserEnabled - Hidden -} \ No newline at end of file diff --git a/Assets/Engine/Shaders/Vegetation.ext b/Assets/Engine/Shaders/Vegetation.ext deleted file mode 100644 index 5f98ec49e6..0000000000 --- a/Assets/Engine/Shaders/Vegetation.ext +++ /dev/null @@ -1,137 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: Vegetation extension used by the editor -// for automatic shader generation (based on "Vegetation" shader template) -// -//////////////////////////////////////////////////////////////////////////// - -Version (1.00) - -UsesCommonGlobalFlags - -Property -{ - Name = %NORMAL_MAP - Mask = 0x1 - Property (Normal map) - Description (Use normal-map texture) - DependencySet = $TEX_Normals - DependencyReset = $TEX_Normals - Hidden -} - -Property -{ - Name = %SPECULAR_MAP - Mask = 0x10 - Property (Specular map) - Description (Use specular map as separate texture) - DependencySet = $TEX_Specular - DependencyReset = $TEX_Specular - Hidden -} - -Property -{ - Name = %LEAVES - Mask = 0x100 - Property (Leaves) - Description (Activate for leaves only ! Use leaf shading and leaves animation) -} - -Property -{ - Name = %GRASS - Mask = 0x2000 - Property (Grass) - Description (Activate for grass only ! Use simple grass rendering) -} - -Property -{ - Name = %SPEEDTREE_BILLBOARD - Mask = 0x40000 - Property (SpeedTree billboard) - Description (Activate for SpeedTree billboards only! Enables removal of non-camera-facing geometry from the billboard mesh) -} - -Property -{ - Name = %DETAIL_BENDING - Mask = 0x10000 - Property (Detail bending) - Description (Activate for leaves and grass only. Make sure to paint required vertex colors also) -} - -Property -{ - Name = %DETAIL_MAPPING - Mask = 0x20000 - Property (Detail mapping) - Description (Use detail mapping) - DependencyReset = $TEX_Detail -} - -Property -{ - Name = %BLENDLAYER - Mask = 0x80000 - Property (Blendlayer) - Description (Normal-mapped diffuse layer blended on top of base material) -} - -Property -{ - Name = %VERTCOLORS - Mask = 0x100000 - DependencySet = $UserEnabled - Hidden -} - -Property -{ - Name = %TEMP_VEGETATION - Mask = 0x8000000 - DependencySet = $UserEnabled - Hidden -} - -#ifdef FEATURE_MESH_TESSELLATION -Property -{ - Name = %DISPLACEMENT_MAPPING - Mask = 0x10000000 - Property (Displacement mapping) - Description (Use displacement mapping (requires height map (_displ))) - //DependencySet = $TEX_Height - DependencyReset = $TEX_Normals -} - -Property -{ - Name = %PHONG_TESSELLATION - Mask = 0x20000000 - Property (Phong tessellation) - Description (Use rough approximation of smooth surface subdivision) -} - -Property -{ - Name = %PN_TESSELLATION - Mask = 0x40000000 - Property (PN triangles tessellation) - Description (Use rough approximation of smooth surface subdivision) -} -#endif \ No newline at end of file diff --git a/Assets/Engine/Shaders/VolumeObject.ext b/Assets/Engine/Shaders/VolumeObject.ext deleted file mode 100644 index 8cab9b4270..0000000000 --- a/Assets/Engine/Shaders/VolumeObject.ext +++ /dev/null @@ -1,57 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: -// -//////////////////////////////////////////////////////////////////////////// - - - -Version (1.00) - -Property -{ - Name = %SOFT_OBJECT_INTERSECTION - Mask = 0x1 - Property (Soft Intersections) - Description (Enables soft intersections with opaque scene geometry) -} -Property -{ - Name = %BACK_LIGHTING - Mask = 0x2 - Property (Back Lighting) - Description (Adds back lighting to volume silhouette when viewing against sun) -} -Property -{ - Name = %JITTERING - Mask = 0x4 - Property (Jittering) - Description (Enables jittering on cloud steps) -} -Property -{ - Name = %SOFT_JITTERING - Mask = 0x8 - Property (Soft Jittering) - Description (Softens the effect of jittering on volume objects) -} -Property -{ - Name = %CUSTOM_SETTINGS - Mask = 0x10 - Property (Use TOD Settings) - Description (Use the custom cloud settings from the TOD for lighting) -} \ No newline at end of file diff --git a/Assets/Engine/Shaders/Water.ext b/Assets/Engine/Shaders/Water.ext deleted file mode 100644 index d44e3598e5..0000000000 --- a/Assets/Engine/Shaders/Water.ext +++ /dev/null @@ -1,62 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: Water extension used by the editor -// for automatic shader generation (based on "Water" shader template) -// -//////////////////////////////////////////////////////////////////////////// - -Version (1.00) - -Property -{ - Name = %ENVIRONMENT_MAP - Mask = 0x4 - Property (Environment map) - Description (Use environment map instead of reflection) - DependencyReset = $TEX_EnvCM -} - -Property -{ - Name = %SUN_SHINE - Mask = 0x20 - Property (Sunshine) - Description (Activate for water sunshine) -} - -Property -{ - Name = %NO_REFRACTION_BUMP - Mask = 0x200 - Property (No refraction bump) - Description (Disables refraction bump) -} - -Property -{ - Name = %FOAM - Mask = 0x400 - Property (Foam) - Description (Enables foam) -} - -Property -{ - Name = %WATER_TESSELLATION_DX11 - Mask = 0x800 - DependencySet = $HW_WaterTessellation - DependencyReset = $HW_WaterTessellation - Hidden -} diff --git a/Assets/Engine/Shaders/WaterVolume.ext b/Assets/Engine/Shaders/WaterVolume.ext deleted file mode 100644 index dec9d1a531..0000000000 --- a/Assets/Engine/Shaders/WaterVolume.ext +++ /dev/null @@ -1,107 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: Water extension used by the editor -// for automatic shader generation (based on "Water" shader template) -// -//////////////////////////////////////////////////////////////////////////// - -Version (1.00) - -Property -{ - Name = %SSREFL - Mask = 0x1 - Property (Realtime Reflection) - Description (Approximate realtime reflections) -} - -Property -{ - Name = %FLOW - Mask = 0x2 - Property (Water flow) - Description (Enable water to flow along geometry uvs) -} - -Property -{ - Name = %FLOW_MAP - Mask = 0x4 - Property (Water flow map) - Description (Enable water flow along a flow map) -} - -Property -{ - Name = %FLOW_MAP_STRENGTH - Mask = 0x100 - Property (Water flow map strength) - Description (Enable additional water flow strength controls - requires blue channel for strength) -} - -Property -{ - Name = %SUN_SPECULAR - Mask = 0x8 - Property (Sun specular) - Description (Activate for water sunshine) -} - -Property -{ - Name = %SPECULAR_MAP - Mask = 0x10 - Property (Specular map) - Description (Use specular map as separate texture) - DependencySet = $TEX_Specular - DependencyReset = $TEX_Specular - Hidden -} - -Property -{ - Name = %DEBUG_FLOW_MAP - Mask = 0x20 - Property (Debug flow map) - Description (Enable visualizing flow map) -} - -Property -{ - Name = %FOAM - Mask = 0x40 - Property (Foam) - Description (Enables foam) -} - -Property -{ - Name = %DECAL_MAP - Mask = 0x80 - Property (Decal map) - Description (Use tiling decal map as separate texture) - DependencySet = $TEX_Custom - DependencyReset = $TEX_Custom - Hidden -} - -Property -{ - Name = %WATER_TESSELLATION_DX11 - Mask = 0x80000000 - DependencySet = $HW_WaterTessellation - DependencyReset = $HW_WaterTessellation - Hidden -} diff --git a/Assets/Engine/Shaders/Waterfall.ext b/Assets/Engine/Shaders/Waterfall.ext deleted file mode 100644 index 8d0ff2cc73..0000000000 --- a/Assets/Engine/Shaders/Waterfall.ext +++ /dev/null @@ -1,45 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -// its licensors. -// -// For complete copyright and license terms please see the LICENSE at the root of this -// distribution (the "License"). All use of this software is governed by the License, -// or, if provided, by the license below or the license accompanying this file. Do not -// remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// Original file Copyright Crytek GMBH or its affiliates, used under license. -// - -// Description: Water extension used by the editor -// for automatic shader generation (based on "Water" shader template) -// -//////////////////////////////////////////////////////////////////////////// - -Version (1.00) - -Property -{ - Name = %ENVIRONMENT_MAP - Mask = 0x1 - Property (Environment map) - Description (Use environment map as separate texture) - DependencyReset = $TEX_EnvCM -} - -Property -{ - Name = %SUN_SHADING - Mask = 0x2 - Property (Sun shading) - Description (Activate for water sunshading - for outdoors) -} - -Property -{ - Name = %FOAM - Mask = 0x4 - Property (Foam) - Description (Enables foam rendering) -} diff --git a/AutomatedTesting/Config/aws_resource_mappings.json b/AutomatedTesting/Config/aws_resource_mappings.json new file mode 100644 index 0000000000..03a611b749 --- /dev/null +++ b/AutomatedTesting/Config/aws_resource_mappings.json @@ -0,0 +1,6 @@ +{ + "AWSResourceMappings": {}, + "AccountId": "", + "Region": "us-west-2", + "Version": "1.0.0" +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Code/runtime_dependencies.cmake b/AutomatedTesting/Gem/Code/runtime_dependencies.cmake index 280c25bcf7..33c2bf8d5f 100644 --- a/AutomatedTesting/Gem/Code/runtime_dependencies.cmake +++ b/AutomatedTesting/Gem/Code/runtime_dependencies.cmake @@ -45,4 +45,7 @@ set(GEM_DEPENDENCIES Gem::Atom_AtomBridge Gem::NvCloth Gem::Blast + Gem::AWSCore + Gem::AWSClientAuth + Gem::AWSMetrics ) diff --git a/AutomatedTesting/Gem/Code/tool_dependencies.cmake b/AutomatedTesting/Gem/Code/tool_dependencies.cmake index fc50707c12..c8eccab947 100644 --- a/AutomatedTesting/Gem/Code/tool_dependencies.cmake +++ b/AutomatedTesting/Gem/Code/tool_dependencies.cmake @@ -52,7 +52,12 @@ set(GEM_DEPENDENCIES Gem::LandscapeCanvas.Editor Gem::EMotionFX.Editor Gem::ImGui.Editor + Gem::Atom_RHI.Private + Gem::Atom_Feature_Common.Editor Gem::Atom_AtomBridge.Editor Gem::NvCloth.Editor Gem::Blast.Editor + Gem::AWSCore.Editor + Gem::AWSClientAuth + Gem::AWSMetrics ) diff --git a/AutomatedTesting/Gem/PythonTests/AWS/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/AWS/CMakeLists.txt new file mode 100644 index 0000000000..b406ea77de --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/CMakeLists.txt @@ -0,0 +1,31 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +################################################################################ +# AWS Automated Tests +# Runs AWS Gems automation tests. +################################################################################ + +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) + # Enable after installing NodeJS and CDK on jenkins Windows AMI. + #ly_add_pytest( + # NAME AutomatedTesting::AWSTests + # TEST_SUITE periodic + # TEST_SERIAL + # PATH ${CMAKE_CURRENT_LIST_DIR}/AWS/${PAL_PLATFORM_NAME}/ + # RUNTIME_DEPENDENCIES + # Legacy::Editor + # AZ::AssetProcessor + # AutomatedTesting.Assets + # COMPONENT + # AWS + #) +endif() diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py new file mode 100644 index 0000000000..455b3f94cb --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py @@ -0,0 +1,155 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +import os +import pytest +import boto3 + +import ly_test_tools.environment.process_utils as process_utils +from typing import List + + +class Cdk: + """ + Cdk class that provides methods to run cdk application commands. + Expects system to have NodeJS, AWS CLI and CDK installed globally and have their paths setup as env variables. + """ + def __init__(self, cdk_path: str, project: str, account_id: str, + workspace: pytest.fixture, session: boto3.session.Session): + """ + :param cdk_path: Path where cdk app.py is stored. + :param project: Project name used for cdk project name env variable. + :param account_id: AWS account id to use with cdk application. + :param workspace: ly_test_tools workspace fixture. + """ + self._cdk_env = os.environ.copy() + self._cdk_env['O3DE_AWS_PROJECT_NAME'] = project + self._cdk_env['O3DE_AWS_DEPLOY_REGION'] = session.region_name + self._cdk_env['O3DE_AWS_DEPLOY_ACCOUNT'] = account_id + self._cdk_env['PATH'] = f'{workspace.paths.engine_root()}\\python;' + self._cdk_env['PATH'] + + credentials = session.get_credentials().get_frozen_credentials() + self._cdk_env['AWS_ACCESS_KEY_ID'] = credentials.access_key + self._cdk_env['AWS_SECRET_ACCESS_KEY'] = credentials.secret_key + self._cdk_env['AWS_SESSION_TOKEN'] = credentials.token + self._stacks = [] + self._cdk_path = cdk_path + + output = process_utils.check_output( + 'python -m pip install -r requirements.txt', + cwd=self._cdk_path, + env=self._cdk_env, + shell=True) + + def list(self) -> List[str]: + """ + lists cdk stack names + :return List of cdk stack names + """ + + if not self._cdk_path: + return [] + + list_cdk_application_cmd = ['cdk', 'list'] + output = process_utils.check_output( + list_cdk_application_cmd, + cwd=self._cdk_path, + env=self._cdk_env, + shell=True) + + return output.splitlines() + + def synthesize(self) -> None: + """ + Synthesizes all cdk stacks + """ + if not self._cdk_path: + return + + list_cdk_application_cmd = ['cdk', 'synth'] + + process_utils.check_output( + list_cdk_application_cmd, + cwd=self._cdk_path, + env=self._cdk_env, + shell=True) + + def deploy(self, context_variable: str = '') -> List[str]: + """ + Deploys all the CDK stacks. + :param context_variable: Context variable for enabling optional features. + :return List of deployed stack arns. + """ + if not self._cdk_path: + return [] + + deploy_cdk_application_cmd = ['cdk', 'deploy', '--require-approval', 'never'] + if context_variable: + deploy_cdk_application_cmd.extend(['-c', f'{context_variable}']) + + output = process_utils.check_output( + deploy_cdk_application_cmd, + cwd=self._cdk_path, + env=self._cdk_env, + shell=True) + + stacks = [] + for line in output.splitlines(): + line_sections = line.split('/') + assert len(line_sections), 3 + stacks.append(line.split('/')[-2]) + + return stacks + + def destroy(self) -> None: + """ + Destroys the cdk application. + """ + destroy_cdk_application_cmd = ['cdk', 'destroy', '-f'] + process_utils.check_output( + destroy_cdk_application_cmd, + cwd=self._cdk_path, + env=self._cdk_env, + shell=True) + + self._stacks = [] + self._cdk_path = '' + + +@pytest.fixture(scope='function') +def cdk( + request: pytest.fixture, + project: str, + feature_name: str, + workspace: pytest.fixture, + aws_utils: pytest.fixture, + destroy_stacks_on_teardown: bool = True) -> Cdk: + """ + Fixture for setting up a Cdk + :param request: _pytest.fixtures.SubRequest class that handles getting + a pytest fixture from a pytest function/fixture. + :param project: Project name used for cdk project name env variable. + :param feature_name: Feature gem name to expect cdk folder in. + :param workspace: ly_test_tools workspace fixture. + :param aws_utils: aws_utils fixture. + :param destroy_stacks_on_teardown: option to control calling destroy ot the end of test. + :return Cdk class object. + """ + + cdk_path = f'{workspace.paths.engine_root()}/Gems/{feature_name}/cdk' + cdk_obj = Cdk(cdk_path, project, aws_utils.assume_account_id(), workspace, aws_utils.assume_session()) + + def teardown(): + if destroy_stacks_on_teardown: + cdk_obj.destroy() + request.addfinalizer(teardown) + + return cdk_obj diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_anonymous_credentials.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_anonymous_credentials.py new file mode 100644 index 0000000000..5997701870 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_anonymous_credentials.py @@ -0,0 +1,78 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" +import pytest +import os +import logging +import ly_test_tools.log.log_monitor + +from AWS.Windows.resource_mappings.resource_mappings import resource_mappings +from AWS.Windows.cdk.cdk import cdk +from AWS.common.aws_utils import aws_utils +from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor as asset_processor + +AWS_PROJECT_NAME = 'AWS-AutomationTest' +AWS_CLIENT_AUTH_FEATURE_NAME = 'AWSClientAuth' +AWS_CLIENT_AUTH_DEFAULT_PROFILE_NAME = 'default' + +GAME_LOG_NAME = 'Game.log' + +logger = logging.getLogger(__name__) + + +@pytest.mark.SUITE_periodic +@pytest.mark.usefixtures('automatic_process_killer') +@pytest.mark.usefixtures('asset_processor') +@pytest.mark.usefixtures('workspace') +@pytest.mark.parametrize('project', ['AutomatedTesting']) +@pytest.mark.parametrize('level', ['AWS/ClientAuth']) +@pytest.mark.usefixtures('cdk') +@pytest.mark.parametrize('feature_name', [AWS_CLIENT_AUTH_FEATURE_NAME]) +@pytest.mark.usefixtures('resource_mappings') +@pytest.mark.parametrize('resource_mappings_filename', ['aws_resource_mappings.json']) +@pytest.mark.usefixtures('aws_utils') +@pytest.mark.parametrize('region_name', ['us-west-2']) +@pytest.mark.parametrize('assume_role_arn', ['arn:aws:iam::645075835648:role/o3de-automation-tests']) +@pytest.mark.parametrize('session_name', ['o3de-Automation-session']) +class TestAWSClientAuthAnonymousCredentials(object): + """ + Test class to verify AWS Cognito Identity pool anonymous authorization. + """ + + def test_anonymous_credentials(self, + level: str, + launcher: pytest.fixture, + cdk: pytest.fixture, + resource_mappings: pytest.fixture, + workspace: pytest.fixture, + asset_processor: pytest.fixture + ): + """ + Setup: Deploys cdk and updates resource mapping file. + Tests: Getting AWS credentials for no signed in user. + Verification: Log monitor looks for success credentials log. + """ + logger.info(f'Cdk stack names:\n{cdk.list()}') + stacks = cdk.deploy() + resource_mappings.populate_output_keys(stacks) + asset_processor.start() + asset_processor.wait_for_idle() + + file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), GAME_LOG_NAME) + log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=launcher, log_file_path=file_to_monitor) + + launcher.args = ['+LoadLevel', level] + + with launcher.start(launch_ap=False): + result = log_monitor.monitor_log_for_lines( + expected_lines=['(Script) - Success anonymous credentials'], + unexpected_lines=['(Script) - Fail anonymous credentials'], + halt_on_unexpected=True, + ) + assert result, 'Anonymous credentials fetched successfully.' diff --git a/Tests/BuildSystems/__init__.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from Tests/BuildSystems/__init__.py rename to AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/__init__.py diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py new file mode 100644 index 0000000000..c8d8cff828 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py @@ -0,0 +1,137 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +import os +import pytest +import json + +AWS_RESOURCE_MAPPINGS_KEY = 'AWSResourceMappings' +AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY = 'AccountId' +AWS_RESOURCE_MAPPINGS_REGION_KEY = 'Region' + + +class ResourceMappings: + """ + ResourceMappings class that handles writing Cloud formation outputs to resource mappings json file in a project. + """ + + def __init__(self, file_path: str, region: str, feature_name: str, account_id: str, workspace: pytest.fixture, + cloud_formation_client): + """ + :param file_path: Path for the resource mapping file. + :param region: Region value for the resource mapping file. + :param feature_name: Feature gem name to use to append name to mappings key. + :param account_id: AWS account id value for the resource mapping file. + :param workspace: ly_test_tools workspace fixture. + :param cloud_formation_client: AWS cloud formation client. + """ + self._cdk_env = os.environ.copy() + self._cdk_env['PATH'] = f'{workspace.paths.engine_root()}\\python;' + self._cdk_env['PATH'] + self._resource_mapping_file_path = file_path + self._region = region + self._feature_name = feature_name + self._account_id = account_id + + assert os.path.exists(self._resource_mapping_file_path), \ + f'Invalid resource mapping file path {self._resource_mapping_file_path}' + self._client = cloud_formation_client + + def populate_output_keys(self, stacks=[]) -> None: + """ + Calls describe stacks on cloud formation service and persists outputs to resource mappings file. + :param stacks List of stack arns to describe and populate resource mappings with. + """ + for stack_name in stacks: + response = self._client.describe_stacks( + StackName=stack_name + ) + stacks = response.get('Stacks', []) + assert len(stacks) == 1, f'{stack_name} is invalid.' + + self.__write_resource_mappings(stacks[0].get('Outputs', [])) + + def __write_resource_mappings(self, outputs, append_feature_name = True) -> None: + with open(self._resource_mapping_file_path) as file_content: + resource_mappings = json.load(file_content) + + resource_mappings[AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY] = self._account_id + resource_mappings[AWS_RESOURCE_MAPPINGS_REGION_KEY] = self._region + + # Append new mappings. + resource_mappings[AWS_RESOURCE_MAPPINGS_KEY] = resource_mappings.get(AWS_RESOURCE_MAPPINGS_KEY, {}) + + for output in outputs: + if append_feature_name: + resource_key = f'{self._feature_name}.{output.get("OutputKey", "InvalidKey")}' + else: + resource_key = output.get("OutputKey", "InvalidKey") + resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key] = resource_mappings[ + AWS_RESOURCE_MAPPINGS_KEY].get(resource_key, {}) + resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key]['Type'] = 'AutomationTestType' + resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key]['Name/ID'] = output.get('OutputValue', + 'InvalidId') + + with open(self._resource_mapping_file_path, 'w') as file_content: + json.dump(resource_mappings, file_content, indent=4) + + def clear_output_keys(self) -> None: + """ + Clears values of all resource mapping keys. Sets region to default to us-west-2 + """ + with open(self._resource_mapping_file_path) as file_content: + resource_mappings = json.load(file_content) + + resource_mappings[AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY] = '' + resource_mappings[AWS_RESOURCE_MAPPINGS_REGION_KEY] = 'us-west-2' + + # Append new mappings. + resource_mappings[AWS_RESOURCE_MAPPINGS_KEY] = resource_mappings.get(AWS_RESOURCE_MAPPINGS_KEY, {}) + resource_mappings[AWS_RESOURCE_MAPPINGS_KEY] = {} + + with open(self._resource_mapping_file_path, 'w') as file_content: + json.dump(resource_mappings, file_content, indent=4) + + self._resource_mapping_file_path = '' + self._region = '' + self._client = None + + +@pytest.fixture(scope='function') +def resource_mappings( + request: pytest.fixture, + project: str, + feature_name: str, + resource_mappings_filename: str, + workspace: pytest.fixture, + aws_utils: pytest.fixture) -> ResourceMappings: + """ + Fixture for setting up resource mappings file. + :param request: _pytest.fixtures.SubRequest class that handles getting + a pytest fixture from a pytest function/fixture. + :param project: Project to find resource mapping file. + :param feature_name: AWS Gem name that is prepended to resource mapping keys. + :param resource_mappings_filename: Name of resource mapping file. + :param workspace: ly_test_tools workspace fixture. + :param aws_utils: AWS utils fixture. + :return: ResourceMappings class object. + """ + + path = f'{workspace.paths.engine_root()}\\{project}\\Config\\{resource_mappings_filename}' + resource_mappings_obj = ResourceMappings(path, aws_utils.assume_session().region_name, feature_name, + aws_utils.assume_account_id(), workspace, + aws_utils.client('cloudformation')) + + def teardown(): + resource_mappings_obj.clear_output_keys() + + request.addfinalizer(teardown) + + return resource_mappings_obj diff --git a/Tests/graphics/__init__.py b/AutomatedTesting/Gem/PythonTests/AWS/__init__.py old mode 100755 new mode 100644 similarity index 99% rename from Tests/graphics/__init__.py rename to AutomatedTesting/Gem/PythonTests/AWS/__init__.py index 6ed3dc4bda..8caef52682 --- a/Tests/graphics/__init__.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/__init__.py @@ -7,4 +7,5 @@ distribution (the "License"). All use of this software is governed by the Licens or, if provided, by the license below or the license accompanying this file. Do not remove or modify any license notices. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" \ No newline at end of file +""" + diff --git a/AutomatedTesting/Gem/PythonTests/AWS/common/aws_utils.py b/AutomatedTesting/Gem/PythonTests/AWS/common/aws_utils.py new file mode 100644 index 0000000000..7a15ba0abe --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/common/aws_utils.py @@ -0,0 +1,82 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" +import boto3 +import pytest +import logging + +logger = logging.getLogger(__name__) + + +class AwsUtils: + + def __init__(self, arn: str, session_name: str, region_name: str): + local_session = boto3.Session(profile_name='default') + local_sts_client = local_session.client('sts') + self._local_account_id = local_sts_client.get_caller_identity()["Account"] + logger.info(f'Local Account Id: {self._local_account_id}') + + response = local_sts_client.assume_role(RoleArn=arn, RoleSessionName=session_name) + + self._assume_session = boto3.Session(aws_access_key_id=response['Credentials']['AccessKeyId'], + aws_secret_access_key=response['Credentials']['SecretAccessKey'], + aws_session_token=response['Credentials']['SessionToken'], + region_name=region_name) + + assume_sts_client = self._assume_session.client('sts') + assume_account_id = assume_sts_client.get_caller_identity()["Account"] + logger.info(f'Assume Account Id: {assume_account_id}') + self._assume_account_id = assume_account_id + + def client(self, service: str): + """ + Get the client for a specific AWS service from configured session + :return: Client for the AWS service. + """ + return self._assume_session.client(service) + + def assume_session(self): + return self._assume_session + + def local_account_id(self): + return self._local_account_id + + def assume_account_id(self): + return self._assume_account_id + + def destroy(self) -> None: + """ + clears stored session + """ + self._assume_session = None + + +@pytest.fixture(scope='function') +def aws_utils( + request: pytest.fixture, + assume_role_arn: str, + session_name: str, + region_name: str): + """ + Fixture for setting up a Cdk + :param request: _pytest.fixtures.SubRequest class that handles getting + a pytest fixture from a pytest function/fixture. + :param assume_role_arn: Role used to fetch temporary aws credentials, configure service clients with obtained credentials. + :param session_name: Session name to set. + :param region_name: AWS account region to set for session. + :return AWSUtils class object. + """ + aws_utils_obj = AwsUtils(assume_role_arn, session_name, region_name) + + def teardown(): + aws_utils_obj.destroy() + + request.addfinalizer(teardown) + + return aws_utils_obj diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index d6f9ecff4b..c6ed6c7538 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -56,5 +56,8 @@ add_subdirectory(editor) ## Streaming ## add_subdirectory(streaming) -## Streaming ## +## Smoke ## add_subdirectory(smoke) + +## AWS ## +add_subdirectory(AWS) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_test_helper.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_test_helper.py index 74f34659c0..d375e69ac2 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_test_helper.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_test_helper.py @@ -98,11 +98,6 @@ class EditorTestHelper: self.test_success = False success = False - # Turn off any display info like FPS, as that will mess up our image comparisons - # Turn off antialiasing as well - general.run_console("r_displayInfo=0") - general.run_console("r_antialiasingmode=0") - general.idle_wait(1.0) return success # Test Teardown diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt index 7ffc2072f1..91228bc71c 100644 --- a/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt @@ -20,7 +20,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT TEST_SUITE main PATH ${CMAKE_CURRENT_LIST_DIR}/test_Atom_MainSuite.py TEST_SERIAL - TIMEOUT 300 + TIMEOUT 400 RUNTIME_DEPENDENCIES AssetProcessor AutomatedTesting.Assets @@ -31,7 +31,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT TEST_SUITE sandbox PATH ${CMAKE_CURRENT_LIST_DIR}/test_Atom_SandboxSuite.py TEST_SERIAL - TIMEOUT 300 + TIMEOUT 400 RUNTIME_DEPENDENCIES AssetProcessor AutomatedTesting.Assets diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_hydra_scripts/hydra_AtomEditorComponents_AddedToEntity.py b/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_hydra_scripts/hydra_AtomEditorComponents_AddedToEntity.py index e701ff8d16..904075c747 100644 --- a/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_hydra_scripts/hydra_AtomEditorComponents_AddedToEntity.py +++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_hydra_scripts/hydra_AtomEditorComponents_AddedToEntity.py @@ -77,10 +77,10 @@ def run(): def verify_enter_exit_game_mode(component_name): general.enter_game_mode() - TestHelper.wait_for_condition(lambda: general.is_in_game_mode(), 1.0) + TestHelper.wait_for_condition(lambda: general.is_in_game_mode(), 2.0) general.log(f"{component_name}_test: Entered game mode: {general.is_in_game_mode()}") general.exit_game_mode() - TestHelper.wait_for_condition(lambda: not general.is_in_game_mode(), 1.0) + TestHelper.wait_for_condition(lambda: not general.is_in_game_mode(), 2.0) general.log(f"{component_name}_test: Exit game mode: {not general.is_in_game_mode()}") def verify_hide_unhide_entity(component_name, entity_obj): @@ -97,16 +97,16 @@ def run(): def verify_deletion_undo_redo(component_name, entity_obj): editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntityById", entity_obj.id) - TestHelper.wait_for_condition(lambda: not hydra.find_entity_by_name(entity_obj.name), 1.0) + TestHelper.wait_for_condition(lambda: not hydra.find_entity_by_name(entity_obj.name), 2.0) general.log(f"{component_name}_test: Entity deleted: {not hydra.find_entity_by_name(entity_obj.name)}") general.undo() - TestHelper.wait_for_condition(lambda: hydra.find_entity_by_name(entity_obj.name) is not None, 1.0) + TestHelper.wait_for_condition(lambda: hydra.find_entity_by_name(entity_obj.name) is not None, 2.0) general.log(f"{component_name}_test: UNDO entity deletion works: " f"{hydra.find_entity_by_name(entity_obj.name) is not None}") general.redo() - TestHelper.wait_for_condition(lambda: not hydra.find_entity_by_name(entity_obj.name), 1.0) + TestHelper.wait_for_condition(lambda: not hydra.find_entity_by_name(entity_obj.name), 2.0) general.log(f"{component_name}_test: REDO entity deletion works: " f"{not hydra.find_entity_by_name(entity_obj.name)}") @@ -120,7 +120,7 @@ def run(): f"{not is_component_enabled(entity_obj.components[0])}") for component in components_to_add: entity_obj.add_component(component) - TestHelper.wait_for_condition(lambda: is_component_enabled(entity_obj.components[0]), 1.0) + TestHelper.wait_for_condition(lambda: is_component_enabled(entity_obj.components[0]), 2.0) general.log( f"{component_name}_test: Entity enabled after adding " f"required components: {is_component_enabled(entity_obj.components[0])}" @@ -135,7 +135,9 @@ def run(): # Delete all existing entities initially search_filter = azlmbr.entity.SearchFilter() all_entities = entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter) + general.idle_wait_frames(1) editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntities", all_entities) + general.idle_wait_frames(1) class ComponentTests: """Test launcher for each component.""" @@ -147,9 +149,11 @@ def run(): def run_component_tests(self): # Run common and additional tests entity_obj = create_entity_undo_redo_component_addition(self.component_name) + general.idle_wait(0.5) # Enter/Exit game mode test verify_enter_exit_game_mode(self.component_name) + general.idle_wait(0.5) # Any additional tests are executed here for test in self.additional_tests: @@ -157,13 +161,16 @@ def run(): # Hide/Unhide entity test verify_hide_unhide_entity(self.component_name, entity_obj) + general.idle_wait(0.5) # Deletion/Undo/Redo test verify_deletion_undo_redo(self.component_name, entity_obj) + general.idle_wait(0.5) # DepthOfField Component camera_entity = hydra.Entity("camera_entity") camera_entity.create_entity(math.Vector3(512.0, 512.0, 34.0), ["Camera"]) + general.idle_wait(0.5) depth_of_field = "DepthOfField" ComponentTests( depth_of_field, diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite.py b/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite.py index 0b75760e05..3da1c27e67 100644 --- a/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite.py +++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite.py @@ -27,6 +27,7 @@ TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "atom_hydra_scripts") @pytest.mark.parametrize("level", ["auto_test"]) class TestAtomEditorComponentsMain(object): + @pytest.mark.xfail(reason="Timing out sporadically, LYN-3956") @pytest.mark.test_case_id( "C32078130", # Display Mapper "C32078129", # Light @@ -163,7 +164,8 @@ class TestAtomEditorComponentsMain(object): ] unexpected_lines = [ - "failed to open", + "Trace::Assert", + "Trace::Error", "Traceback (most recent call last):", ] diff --git a/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/Menus_EditMenuOptions.py b/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/Menus_EditMenuOptions.py index 208ef40d41..3eeb3ddf84 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/Menus_EditMenuOptions.py +++ b/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/Menus_EditMenuOptions.py @@ -64,8 +64,6 @@ class TestEditMenuOptions(EditorTestHelper): ("Modify", "Transform Mode", "Move"), ("Modify", "Transform Mode", "Rotate"), ("Modify", "Transform Mode", "Scale"), - ("Lock Selection",), - ("Unlock All Entities",), ("Editor Settings", "Global Preferences"), ("Editor Settings", "Graphics Settings"), ("Editor Settings", "Editor Settings Manager"), diff --git a/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/Menus_ViewMenuOptions.py b/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/Menus_ViewMenuOptions.py index 05ee802d19..173d658b3c 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/Menus_ViewMenuOptions.py +++ b/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/Menus_ViewMenuOptions.py @@ -50,7 +50,6 @@ class TestViewMenuOptions(EditorTestHelper): ("Center on Selection",), ("Show Quick Access Bar",), ("Viewport", "Wireframe"), - ("Viewport", "Grid Settings"), ("Viewport", "Go to Position"), ("Viewport", "Center on Selection"), ("Viewport", "Go to Location"), diff --git a/AutomatedTesting/Gem/PythonTests/editor/test_Docking.py b/AutomatedTesting/Gem/PythonTests/editor/test_Docking.py index a75b3b36cd..c2d515e250 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/test_Docking.py +++ b/AutomatedTesting/Gem/PythonTests/editor/test_Docking.py @@ -40,7 +40,7 @@ class TestDocking(object): @pytest.mark.test_case_id("C6376081") @pytest.mark.SUITE_periodic - def test_basic_docked_tools(self, request, editor, level, launcher_platform): + def test_Docking_BasicDockedTools(self, request, editor, level, launcher_platform): expected_lines = [ "The tools are all docked together in a tabbed widget", "Entity Outliner works when docked, can select an Entity", diff --git a/AutomatedTesting/Gem/PythonTests/editor/test_Menus.py b/AutomatedTesting/Gem/PythonTests/editor/test_Menus.py index 251bd7cfed..70a22f9e2a 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/test_Menus.py +++ b/AutomatedTesting/Gem/PythonTests/editor/test_Menus.py @@ -59,8 +59,6 @@ class TestMenus(object): "Move Action triggered", "Rotate Action triggered", "Scale Action triggered", - "Lock Selection Action triggered", - "Unlock All Entities Action triggered", "Global Preferences Action triggered", "Graphics Settings Action triggered", "Editor Settings Manager Action triggered", @@ -93,7 +91,6 @@ class TestMenus(object): "Center on Selection Action triggered", "Show Quick Access Bar Action triggered", "Wireframe Action triggered", - "Grid Settings Action triggered", "Go to Position Action triggered", "Center on Selection Action triggered", "Go to Location Action triggered", diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetBuilder_Works.py b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetBuilder_Works.py index 10df59e086..5381fd9fd8 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetBuilder_Works.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetBuilder_Works.py @@ -20,7 +20,6 @@ import subprocess @pytest.mark.SUITE_smoke class TestCLIToolAssetBuilderWorks(object): - @pytest.mark.xfail(reason="Ignoring failure temporarily - SPEC-6905") def test_CLITool_AssetBuilder_Works(self, build_directory): file_path = os.path.join(build_directory, "AssetBuilder") help_message = "AssetBuilder is part of the Asset Processor" diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/ClientAuth.ly b/AutomatedTesting/Levels/AWS/ClientAuth/ClientAuth.ly new file mode 100644 index 0000000000..af8a7f5c8e --- /dev/null +++ b/AutomatedTesting/Levels/AWS/ClientAuth/ClientAuth.ly @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0f4d4e0155feaa76c80a14128000a0fd9570ab76e79f4847eaef9006324a4d2 +size 9084 diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/ConitoAnonymousAuthorization.scriptcanvas b/AutomatedTesting/Levels/AWS/ClientAuth/ConitoAnonymousAuthorization.scriptcanvas new file mode 100644 index 0000000000..ef03c66b16 --- /dev/null +++ b/AutomatedTesting/Levels/AWS/ClientAuth/ConitoAnonymousAuthorization.scriptcanvas @@ -0,0 +1,2313 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/Environment.xml b/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/Environment.xml new file mode 100644 index 0000000000..d4e3d33551 --- /dev/null +++ b/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/Environment.xml @@ -0,0 +1 @@ + diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/TimeOfDay.xml b/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/TimeOfDay.xml new file mode 100644 index 0000000000..d827d4da29 --- /dev/null +++ b/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/TimeOfDay.xml @@ -0,0 +1 @@ + diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/filelist.xml b/AutomatedTesting/Levels/AWS/ClientAuth/filelist.xml new file mode 100644 index 0000000000..f69a99fe37 --- /dev/null +++ b/AutomatedTesting/Levels/AWS/ClientAuth/filelist.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/level.pak b/AutomatedTesting/Levels/AWS/ClientAuth/level.pak new file mode 100644 index 0000000000..1ae0bb1f7a --- /dev/null +++ b/AutomatedTesting/Levels/AWS/ClientAuth/level.pak @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4900bdf28654e21032e69957f2762fa0a3b93a4b82163267a1f10f19f6d78692 +size 3795 diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/tags.txt b/AutomatedTesting/Levels/AWS/ClientAuth/tags.txt new file mode 100644 index 0000000000..0d6c1880e7 --- /dev/null +++ b/AutomatedTesting/Levels/AWS/ClientAuth/tags.txt @@ -0,0 +1,12 @@ +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 diff --git a/AutomatedTesting/Registry/awscoreconfiguration.setreg b/AutomatedTesting/Registry/awscoreconfiguration.setreg new file mode 100644 index 0000000000..ca110eb103 --- /dev/null +++ b/AutomatedTesting/Registry/awscoreconfiguration.setreg @@ -0,0 +1,10 @@ +{ + "Amazon": + { + "AWSCore": + { + "ProfileName": "default", + "ResourceMappingConfigFileName": "aws_resource_mappings.json" + } + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ee08a8d16..63177e9d60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,13 +124,13 @@ foreach(external_directory ${LY_EXTERNAL_SUBDIRS}) string(SUBSTRING ${full_directory_hash} 0 8 full_directory_hash) # Use the last directory as the suffix path to use for the Binary Directory get_filename_component(directory_name ${external_directory} NAME) - add_subdirectory(${external_directory} ${CMAKE_BINARY_DIR}/${directory_name}-${full_directory_hash}) + add_subdirectory(${external_directory} ${CMAKE_BINARY_DIR}/External/${directory_name}-${full_directory_hash}) endforeach() # The following steps have to be done after all targets are registered: # 1. generate a settings registry .setreg file for all ly_add_project_dependencies() and ly_add_target_dependencies() calls # to provide applications with the filenames of gem modules to load -# This must be done before ly_delayed_target_link_libraries() as that inserts BUILD_DEPENDENCIE as MANUALLY_ADDED_DEPENDENCIES +# This must be done before ly_delayed_target_link_libraries() as that inserts BUILD_DEPENDENCIES as MANUALLY_ADDED_DEPENDENCIES # if the build dependency is a MODULE_LIBRARY. That would cause a false load dependency to be generated ly_delayed_generate_settings_registry() # 2. link targets where the dependency was yet not declared, we need to have the declaration so we do different diff --git a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp index 9dbe8f7264..7fd8a639c3 100644 --- a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp +++ b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp @@ -41,10 +41,6 @@ namespace AZ if (const char* homePath = std::getenv("HOME"); homePath != nullptr) { AZ::IO::FixedMaxPath path{homePath}; - if (!path.empty()) - { - path /= ".o3de"; - } return path.Native(); } return {}; diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h b/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h new file mode 100644 index 0000000000..47388c56c3 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h @@ -0,0 +1,78 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace AzFramework +{ + //! SessionConnectionConfig + //! The properties for handling join session request. + struct SessionConnectionConfig + { + // A unique identifier for registered player in session. + AZStd::string m_playerSessionId; + + // The DNS identifier assigned to the instance that is running the session. + AZStd::string m_dnsName; + + // The IP address of the session. + AZStd::string m_ipAddress; + + // The port number for the session. + uint16_t m_port; + }; + + //! SessionConnectionConfig + //! The properties for handling player connect/disconnect + struct PlayerConnectionConfig + { + // A unique identifier for player connection. + uint32_t m_playerConnectionId; + + // A unique identifier for registered player in session. + AZStd::string m_playerSessionId; + }; + + //! ISessionHandlingClientRequests + //! The session handling events to invoke multiplayer component handle the work on client side + class ISessionHandlingClientRequests + { + public: + // Handle the player join session process + // @param sessionConnectionConfig The required properties to handle the player join session process + // @return The result of player join session process + virtual bool HandlePlayerJoinSession(const SessionConnectionConfig& sessionConnectionConfig) = 0; + + // Handle the player leave session process + virtual void HandlePlayerLeaveSession() = 0; + }; + + //! ISessionHandlingServerRequests + //! The session handling events to invoke server provider handle the work on server side + class ISessionHandlingServerRequests + { + public: + // Handle the destroy session process + virtual void HandleDestroySession() = 0; + + // Validate the player join session process + // @param playerConnectionConfig The required properties to validate the player join session process + // @return The result of player join session validation + virtual bool ValidatePlayerJoinSession(const PlayerConnectionConfig& playerConnectionConfig) = 0; + + // Handle the player leave session process + // @param playerConnectionConfig The required properties to handle the player leave session process + virtual void HandlePlayerLeaveSession(const PlayerConnectionConfig& playerConnectionConfig) = 0; + }; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.cpp b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.cpp new file mode 100644 index 0000000000..4eb42ab815 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.cpp @@ -0,0 +1,130 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include +#include + +namespace AzFramework +{ + void CreateSessionRequest::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("creatorId", &CreateSessionRequest::m_creatorId) + ->Field("sessionProperties", &CreateSessionRequest::m_sessionProperties) + ->Field("sessionName", &CreateSessionRequest::m_sessionName) + ->Field("maxPlayer", &CreateSessionRequest::m_maxPlayer) + ; + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("CreateSessionRequest", "The container for CreateSession request parameters") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement(AZ::Edit::UIHandlers::Default, &CreateSessionRequest::m_creatorId, + "CreatorId", "A unique identifier for a player or entity creating the session") + ->DataElement(AZ::Edit::UIHandlers::Default, &CreateSessionRequest::m_sessionProperties, + "SessionProperties", "A collection of custom properties for a session") + ->DataElement(AZ::Edit::UIHandlers::Default, &CreateSessionRequest::m_sessionName, + "SessionName", "A descriptive label that is associated with a session") + ->DataElement(AZ::Edit::UIHandlers::Default, &CreateSessionRequest::m_maxPlayer, + "MaxPlayer", "The maximum number of players that can be connected simultaneously to the session") + ; + } + } + } + + void SearchSessionsRequest::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("filterExpression", &SearchSessionsRequest::m_filterExpression) + ->Field("sortExpression", &SearchSessionsRequest::m_sortExpression) + ->Field("maxResult", &SearchSessionsRequest::m_maxResult) + ->Field("nextToken", &SearchSessionsRequest::m_nextToken) + ; + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("SearchSessionsRequest", "The container for SearchSessions request parameters") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsRequest::m_filterExpression, + "FilterExpression", "String containing the search criteria for the session search") + ->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsRequest::m_sortExpression, + "SortExpression", "Instructions on how to sort the search results") + ->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsRequest::m_maxResult, + "MaxResult", "The maximum number of results to return") + ->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsRequest::m_nextToken, + "NextToken", "A token that indicates the start of the next sequential page of results") + ; + } + } + } + + void SearchSessionsResponse::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("sessionConfigs", &SearchSessionsResponse::m_sessionConfigs) + ->Field("nextToken", &SearchSessionsResponse::m_nextToken) + ; + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("SearchSessionsResponse", "The container for SearchSession request results") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsResponse::m_sessionConfigs, + "SessionConfigs", "A collection of sessions that match the search criteria and sorted in specific order") + ->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsResponse::m_nextToken, + "NextToken", "A token that indicates the start of the next sequential page of results") + ; + } + } + } + + void JoinSessionRequest::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("sessionId", &JoinSessionRequest::m_sessionId) + ->Field("playerId", &JoinSessionRequest::m_playerId) + ->Field("playerData", &JoinSessionRequest::m_playerData) + ; + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("JoinSessionRequest", "The container for JoinSession request parameters") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement(AZ::Edit::UIHandlers::Default, &JoinSessionRequest::m_sessionId, + "SessionId", "A unique identifier for the session") + ->DataElement(AZ::Edit::UIHandlers::Default, &JoinSessionRequest::m_playerId, + "PlayerId", "A unique identifier for a player. Player IDs are developer-defined") + ->DataElement(AZ::Edit::UIHandlers::Default, &JoinSessionRequest::m_playerData, + "PlayerData", "Developer-defined information related to a player") + ; + } + } + } +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h new file mode 100644 index 0000000000..9d21a7f282 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h @@ -0,0 +1,192 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace AzFramework +{ + struct SessionConfig; + + //! CreateSessionRequest + //! The container for CreateSession request parameters. + struct CreateSessionRequest + { + AZ_RTTI(CreateSessionRequest, "{E39C2A45-89C9-4CFB-B337-9734DC798930}"); + static void Reflect(AZ::ReflectContext* context); + + CreateSessionRequest() = default; + virtual ~CreateSessionRequest() = default; + + // A unique identifier for a player or entity creating the session. + AZStd::string m_creatorId; + + // A collection of custom properties for a session. + AZStd::unordered_map m_sessionProperties; + + // A descriptive label that is associated with a session. + AZStd::string m_sessionName; + + // The maximum number of players that can be connected simultaneously to the session. + uint64_t m_maxPlayer; + }; + + //! SearchSessionsRequest + //! The container for SearchSessions request parameters. + struct SearchSessionsRequest + { + AZ_RTTI(SearchSessionsRequest, "{B49207A8-8549-4ADB-B7D9-D7A4932F9B4B}"); + static void Reflect(AZ::ReflectContext* context); + + SearchSessionsRequest() = default; + virtual ~SearchSessionsRequest() = default; + + // String containing the search criteria for the session search. If no filter expression is included, the request returns results + // for all active sessions. + AZStd::string m_filterExpression; + + // Instructions on how to sort the search results. If no sort expression is included, the request returns results in random order. + AZStd::string m_sortExpression; + + // The maximum number of results to return. + uint8_t m_maxResult; + + // A token that indicates the start of the next sequential page of results. + AZStd::string m_nextToken; + }; + + //! SearchSessionsResponse + //! The container for SearchSession request results. + struct SearchSessionsResponse + { + AZ_RTTI(SearchSessionsResponse, "{F93DE7DC-D381-4E08-8A3B-0B08F7C38714}"); + static void Reflect(AZ::ReflectContext* context); + + SearchSessionsResponse() = default; + virtual ~SearchSessionsResponse() = default; + + // A collection of sessions that match the search criteria and sorted in specific order. + AZStd::vector m_sessionConfigs; + + // A token that indicates the start of the next sequential page of results. + AZStd::string m_nextToken; + }; + + //! JoinSessionRequest + //! The container for JoinSession request parameters. + struct JoinSessionRequest + { + AZ_RTTI(JoinSessionRequest, "{519769E8-3CDE-4385-A0D7-24DBB3685657}"); + static void Reflect(AZ::ReflectContext* context); + + JoinSessionRequest() = default; + virtual ~JoinSessionRequest() = default; + + // A unique identifier for the session. + AZStd::string m_sessionId; + + // A unique identifier for a player. Player IDs are developer-defined. + AZStd::string m_playerId; + + // Developer-defined information related to a player. + AZStd::string m_playerData; + }; + + //! ISessionRequests + //! Pure virtual session interface class to abstract the details of session handling from application code. + class ISessionRequests + { + public: + AZ_RTTI(ISessionRequests, "{D6C41A71-DD8D-47FE-8515-FAF90670AE2F}"); + + ISessionRequests() = default; + virtual ~ISessionRequests() = default; + + // Create a session for players to find and join. + // @param createSessionRequest The request of CreateSession operation + // @return The request id if session creation request succeeds; empty if it fails + virtual AZStd::string CreateSession(const CreateSessionRequest& createSessionRequest) = 0; + + // Retrieve all active sessions that match the given search criteria and sorted in specific order. + // @param searchSessionsRequest The request of SearchSessions operation + // @return The response of SearchSessions operation + virtual SearchSessionsResponse SearchSessions(const SearchSessionsRequest& searchSessionsRequest) const = 0; + + // Reserve an open player slot in a session, and perform connection from client to server. + // @param joinSessionRequest The request of JoinSession operation + // @return True if joining session succeeds; False otherwise + virtual bool JoinSession(const JoinSessionRequest& joinSessionRequest) = 0; + + // Disconnect player from session. + virtual void LeaveSession() = 0; + }; + + //! ISessionAsyncRequests + //! Async version of ISessionRequests + class ISessionAsyncRequests + { + public: + AZ_RTTI(ISessionAsyncRequests, "{471542AF-96B9-4930-82FE-242A4E68432D}"); + + ISessionAsyncRequests() = default; + virtual ~ISessionAsyncRequests() = default; + + // CreateSession Async + // @param createSessionRequest The request of CreateSession operation + virtual void CreateSessionAsync(const CreateSessionRequest& createSessionRequest) = 0; + + // SearchSessions Async + // @param searchSessionsRequest The request of SearchSessions operation + virtual void SearchSessionsAsync(const SearchSessionsRequest& searchSessionsRequest) const = 0; + + // JoinSession Async + // @param joinSessionRequest The request of JoinSession operation + virtual void JoinSessionAsync(const JoinSessionRequest& joinSessionRequest) = 0; + + // LeaveSession Async + virtual void LeaveSessionAsync() = 0; + }; + + //! SessionAsyncRequestNotifications + //! The notifications correspond to session async requests + class SessionAsyncRequestNotifications + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + + // OnCreateSessionAsyncComplete is fired once CreateSessionAsync completes + // @param createSessionResponse The request id if session creation request succeeds; empty if it fails + virtual void OnCreateSessionAsyncComplete(const AZStd::string& createSessionReponse) = 0; + + // OnSearchSessionsAsyncComplete is fired once SearchSessionsAsync completes + // @param searchSessionsResponse The response of SearchSessions call + virtual void OnSearchSessionsAsyncComplete(const SearchSessionsResponse& searchSessionsResponse) = 0; + + // OnJoinSessionAsyncComplete is fired once JoinSessionAsync completes + // @param joinSessionsResponse True if joining session succeeds; False otherwise + virtual void OnJoinSessionAsyncComplete(bool joinSessionsResponse) = 0; + + // OnLeaveSessionAsyncComplete is fired once LeaveSessionAsync completes + virtual void OnLeaveSessionAsyncComplete() = 0; + }; + using SessionAsyncRequestNotificationBus = AZ::EBus; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.cpp b/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.cpp new file mode 100644 index 0000000000..12c5163031 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.cpp @@ -0,0 +1,74 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include + +namespace AzFramework +{ + void SessionConfig::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("creationTime", &SessionConfig::m_creationTime) + ->Field("terminationTime", &SessionConfig::m_terminationTime) + ->Field("creatorId", &SessionConfig::m_creatorId) + ->Field("sessionProperties", &SessionConfig::m_sessionProperties) + ->Field("sessionId", &SessionConfig::m_sessionId) + ->Field("sessionName", &SessionConfig::m_sessionName) + ->Field("dnsName", &SessionConfig::m_dnsName) + ->Field("ipAddress", &SessionConfig::m_ipAddress) + ->Field("port", &SessionConfig::m_port) + ->Field("maxPlayer", &SessionConfig::m_maxPlayer) + ->Field("currentPlayer", &SessionConfig::m_currentPlayer) + ->Field("status", &SessionConfig::m_status) + ->Field("statusReason", &SessionConfig::m_statusReason) + ; + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("SessionConfig", "Properties describing a session") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_creationTime, + "CreationTime", "A time stamp indicating when this session was created. Format is a number expressed in Unix time as milliseconds.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_terminationTime, + "TerminationTime", "A time stamp indicating when this data object was terminated. Same format as creation time.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_creatorId, + "CreatorId", "A unique identifier for a player or entity creating the session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionProperties, + "SessionProperties", "A collection of custom properties for a session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionId, + "SessionId", "A unique identifier for the session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionName, + "SessionName", "A descriptive label that is associated with a session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_dnsName, + "DnsName", "The DNS identifier assigned to the instance that is running the session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_ipAddress, + "IpAddress", "The IP address of the session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_port, + "Port", "The port number for the session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_maxPlayer, + "MaxPlayer", "The maximum number of players that can be connected simultaneously to the session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_currentPlayer, + "CurrentPlayer", "Number of players currently in the session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_status, + "Status", "Current status of the session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_statusReason, + "StatusReason", "Provides additional information about session status."); + } + } + } +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h b/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h new file mode 100644 index 0000000000..22d1c9e875 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h @@ -0,0 +1,70 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include + +namespace AzFramework +{ + //! SessionConfig + //! Properties describing a session. + struct SessionConfig + { + AZ_RTTI(SessionConfig, "{992DD4BE-8BA5-4071-8818-B99FD2952086}"); + static void Reflect(AZ::ReflectContext* context); + + SessionConfig() = default; + virtual ~SessionConfig() = default; + + // A time stamp indicating when this session was created. Format is a number expressed in Unix time as milliseconds. + uint64_t m_creationTime; + + // A time stamp indicating when this data object was terminated. Same format as creation time. + uint64_t m_terminationTime; + + // A unique identifier for a player or entity creating the session. + AZStd::string m_creatorId; + + // A collection of custom properties for a session. + AZStd::unordered_map m_sessionProperties; + + // A unique identifier for the session. + AZStd::string m_sessionId; + + // A descriptive label that is associated with a session. + AZStd::string m_sessionName; + + // The DNS identifier assigned to the instance that is running the session. + AZStd::string m_dnsName; + + // The IP address of the session. + AZStd::string m_ipAddress; + + // The port number for the session. + uint16_t m_port; + + // The maximum number of players that can be connected simultaneously to the session. + uint64_t m_maxPlayer; + + // Number of players currently in the session. + uint64_t m_currentPlayer; + + // Current status of the session. + AZStd::string m_status; + + // Provides additional information about session status. + AZStd::string m_statusReason; + }; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h b/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h new file mode 100644 index 0000000000..a61c995db7 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h @@ -0,0 +1,47 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace AzFramework +{ + struct SessionConfig; + + //! SessionNotifications + //! The session notifications to listen for performing required operations + class SessionNotifications + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + + // OnSessionHealthCheck is fired in health check process + // @return The result of all OnSessionHealthCheck + virtual bool OnSessionHealthCheck() = 0; + + // OnCreateSessionBegin is fired at the beginning of session creation + // @param sessionConfig The properties to describe a session + // @return The result of all OnCreateSessionBegin notifications + virtual bool OnCreateSessionBegin(const SessionConfig& sessionConfig) = 0; + + // OnDestroySessionBegin is fired at the beginning of session termination + // @return The result of all OnDestroySessionBegin notifications + virtual bool OnDestroySessionBegin() = 0; + }; + using SessionNotificationBus = AZ::EBus; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 6799ea9353..8045766686 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -11,6 +11,7 @@ */ #include +#include #include #include #include @@ -215,6 +216,13 @@ namespace AzFramework return clone; } + AZ::Entity* SpawnableEntitiesManager::CloneSingleEntity(const AZ::Entity& entityTemplate, + EntityIdMap& templateToCloneEntityIdMap, AZ::SerializeContext& serializeContext) + { + return AZ::IdUtils::Remapper::CloneObjectAndGenerateNewIdsAndFixRefs( + &entityTemplate, templateToCloneEntityIdMap, &serializeContext); + } + bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext) { Ticket& ticket = GetTicketPayload(*request.m_ticket); @@ -230,48 +238,37 @@ namespace AzFramework const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities(); size_t entitiesToSpawnSize = entitiesToSpawn.size(); + // Map keeps track of ids from template (spawnable) to clone (instance) + // Allowing patch ups of fields referring to entityIds outside of a given entity + EntityIdMap templateToCloneEntityIdMap; + // Reserve buffers spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize); - ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesToSpawnSize); + spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize); + templateToCloneEntityIdMap.reserve(entitiesToSpawnSize); - // TEMP: To be replaced by IdUtils::Remapper - using EntityIdMap = AZStd::unordered_map; - EntityIdMap templateToCloneIdMap; - // \TEMP - - // Clone the entities from Spawnable + // Mark all indices as spawned for (size_t i = 0; i < entitiesToSpawnSize; ++i) { const AZ::Entity& entityTemplate = *entitiesToSpawn[i]; - AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate); + AZ::Entity* clone = CloneSingleEntity(entityTemplate, templateToCloneEntityIdMap, serializeContext); + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); - clone->SetId(AZ::Entity::MakeId()); - spawnedEntities.push_back(clone); + spawnedEntities.emplace_back(clone); spawnedEntityIndices.push_back(i); + } - // TEMP: To be replaced by IdUtils::Remapper - templateToCloneIdMap[entityTemplate.GetId()] = clone->GetId(); - - // Update TransformComponent parent Id. It is guaranteed for the entities array to be sorted from parent->child here. - auto* transformComponent = clone->FindComponent(); - AZ::EntityId parentId = transformComponent->GetParentId(); - if (parentId.IsValid()) - { - auto it = templateToCloneIdMap.find(parentId); - if (it != templateToCloneIdMap.end()) - { - transformComponent->SetParentRelative(it->second); - } - else - { - AZ_Warning( - "SpawnableEntitiesManager", false, "Entity %s doesn't have the parent entity %s present in the spawnable", - clone->GetName().c_str(), parentId.ToString().data()); - } - } - // \TEMP + // loadAll is true if every entity has been spawned only once + if (spawnedEntities.size() == entitiesToSpawnSize) + { + ticket.m_loadAll = true; + } + else + { + // Case where there were already spawns from a previous request + ticket.m_loadAll = false; } // Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context. @@ -437,10 +434,23 @@ namespace AzFramework // to load every, simply start over. ticket.m_spawnedEntityIndices.clear(); - size_t entitiesSize = entities.size(); - for (size_t i = 0; i < entitiesSize; ++i) + size_t entitiesToSpawnSize = entities.size(); + + // Map keeps track of ids from template (spawnable) to clone (instance) + // Allowing patch ups of fields referring to entityIds outside of a given entity + EntityIdMap templateToCloneEntityIdMap; + templateToCloneEntityIdMap.reserve(entitiesToSpawnSize); + + // Mark all indices as spawned + for (size_t i = 0; i < entitiesToSpawnSize; ++i) { - ticket.m_spawnedEntities.push_back(SpawnSingleEntity(*entities[i], serializeContext)); + const AZ::Entity& entityTemplate = *entities[i]; + + AZ::Entity* clone = CloneSingleEntity(entityTemplate, templateToCloneEntityIdMap, serializeContext); + + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + + ticket.m_spawnedEntities.emplace_back(clone); ticket.m_spawnedEntityIndices.push_back(i); } } diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index 54f48055fd..e20f58ac76 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -29,6 +29,8 @@ namespace AZ namespace AzFramework { + using EntityIdMap = AZStd::unordered_map; + class SpawnableEntitiesManager : public SpawnableEntitiesInterface::Registrar { @@ -142,7 +144,11 @@ namespace AzFramework using Requests = AZStd::variant; - AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate, AZ::SerializeContext& serializeContext); + AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate, + AZ::SerializeContext& serializeContext); + + AZ::Entity* CloneSingleEntity(const AZ::Entity& entityTemplate, + EntityIdMap& templateToCloneEntityIdMap, AZ::SerializeContext& serializeContext); bool ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext); bool ProcessRequest(SpawnEntitiesCommand& request, AZ::SerializeContext& serializeContext); diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp index 79c1a28e5d..bd826544a1 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp @@ -193,13 +193,12 @@ namespace AzFramework bool handling = false; for (auto& cameraInput : m_activeCameraInputs) { - cameraInput->HandleEvents(event, cursorDelta, scrollDelta); - handling = !cameraInput->Idle() || handling; + handling = cameraInput->HandleEvents(event, cursorDelta, scrollDelta) || handling; } for (auto& cameraInput : m_idleCameraInputs) { - cameraInput->HandleEvents(event, cursorDelta, scrollDelta); + handling = cameraInput->HandleEvents(event, cursorDelta, scrollDelta) || handling; } return handling; @@ -262,17 +261,26 @@ namespace AzFramework { m_activeCameraInputs[i]->Reset(); m_idleCameraInputs.push_back(m_activeCameraInputs[i]); - m_activeCameraInputs[i] = m_activeCameraInputs[m_activeCameraInputs.size() - 1]; + using AZStd::swap; + swap(m_activeCameraInputs[i], m_activeCameraInputs[m_activeCameraInputs.size() - 1]); m_activeCameraInputs.pop_back(); } } + void Cameras::Clear() + { + Reset(); + AZ_Assert(m_activeCameraInputs.empty(), "Active Camera Inputs is not empty"); + + m_idleCameraInputs.clear(); + } + RotateCameraInput::RotateCameraInput(const InputChannelId rotateChannelId) : m_rotateChannelId(rotateChannelId) { } - void RotateCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta) + bool RotateCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta) { const ClickDetector::ClickEvent clickEvent = [&event, this] { if (const auto& input = AZStd::get_if(&event)) @@ -304,6 +312,11 @@ namespace AzFramework // noop break; } + + // note - must also check !ending to ensure the mouse up (release) event + // is not consumed and can be propagated to other systems. + // (don't swallow mouse up events) + return !Idle() && !Ending(); } Camera RotateCameraInput::StepCamera( @@ -330,7 +343,7 @@ namespace AzFramework { } - void PanCameraInput::HandleEvents( + bool PanCameraInput::HandleEvents( const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta) { if (const auto& input = AZStd::get_if(&event)) @@ -347,6 +360,8 @@ namespace AzFramework } } } + + return !Idle(); } Camera PanCameraInput::StepCamera( @@ -411,7 +426,7 @@ namespace AzFramework { } - void TranslateCameraInput::HandleEvents( + bool TranslateCameraInput::HandleEvents( const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta) { if (const auto& input = AZStd::get_if(&event)) @@ -429,7 +444,8 @@ namespace AzFramework m_boost = true; } } - else if (input->m_state == InputChannel::State::Ended) + // ensure we don't process end events in the idle state + else if (input->m_state == InputChannel::State::Ended && !Idle()) { m_translation &= ~(translationFromKey(input->m_channelId)); if (m_translation == TranslationType::Nil) @@ -442,6 +458,8 @@ namespace AzFramework } } } + + return !Idle(); } Camera TranslateCameraInput::StepCamera( @@ -503,7 +521,7 @@ namespace AzFramework m_boost = false; } - void OrbitCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) + bool OrbitCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) { if (const auto* input = AZStd::get_if(&event)) { @@ -522,8 +540,10 @@ namespace AzFramework if (Active()) { - m_orbitCameras.HandleEvents(event, cursorDelta, scrollDelta); + return m_orbitCameras.HandleEvents(event, cursorDelta, scrollDelta); } + + return !Idle(); } Camera OrbitCameraInput::StepCamera( @@ -533,7 +553,7 @@ namespace AzFramework if (Beginning()) { - const auto hasLookAt = [&nextCamera, &targetCamera, lookAtFn = m_lookAtFn] { + const auto hasLookAt = [&nextCamera, &targetCamera, &lookAtFn = m_lookAtFn] { if (lookAtFn) { if (const auto lookAt = lookAtFn()) @@ -585,13 +605,15 @@ namespace AzFramework return nextCamera; } - void OrbitDollyScrollCameraInput::HandleEvents( + bool OrbitDollyScrollCameraInput::HandleEvents( const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta) { if (const auto* scroll = AZStd::get_if(&event)) { BeginActivation(); } + + return !Idle(); } Camera OrbitDollyScrollCameraInput::StepCamera( @@ -609,7 +631,7 @@ namespace AzFramework { } - void OrbitDollyCursorMoveCameraInput::HandleEvents( + bool OrbitDollyCursorMoveCameraInput::HandleEvents( const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta) { if (const auto& input = AZStd::get_if(&event)) @@ -626,6 +648,8 @@ namespace AzFramework } } } + + return !Idle(); } Camera OrbitDollyCursorMoveCameraInput::StepCamera( @@ -637,13 +661,15 @@ namespace AzFramework return nextCamera; } - void ScrollTranslationCameraInput::HandleEvents( + bool ScrollTranslationCameraInput::HandleEvents( const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta) { if (const auto* scroll = AZStd::get_if(&event)) { BeginActivation(); } + + return !Idle(); } Camera ScrollTranslationCameraInput::StepCamera( diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h index b6b2bc1e6a..582fb5a6de 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h @@ -149,7 +149,7 @@ namespace AzFramework ResetImpl(); } - virtual void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) = 0; + virtual bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) = 0; virtual Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) = 0; virtual bool Exclusive() const @@ -171,16 +171,29 @@ namespace AzFramework class Cameras { public: - void AddCamera(AZStd::shared_ptr cameraInput); bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta); Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime); + + void AddCamera(AZStd::shared_ptr cameraInput); + //! Reset the state of all cameras. void Reset(); + //! Remove all cameras that were added. + void Clear(); + //! Is one of the cameras in the active camera inputs marked as 'exclusive'. + //! @note This implies no other sibling cameras can begin while the exclusive camera is running. + bool Exclusive() const; private: AZStd::vector> m_activeCameraInputs; AZStd::vector> m_idleCameraInputs; }; + inline bool Cameras::Exclusive() const + { + return AZStd::any_of( + m_activeCameraInputs.begin(), m_activeCameraInputs.end(), [](const auto& cameraInput) { return cameraInput->Exclusive(); }); + } + class CameraSystem { public: @@ -200,7 +213,7 @@ namespace AzFramework explicit RotateCameraInput(InputChannelId rotateChannelId); // CameraInput overrides ... - void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; + bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; private: @@ -241,7 +254,7 @@ namespace AzFramework PanCameraInput(InputChannelId panChannelId, PanAxesFn panAxesFn); // CameraInput overrides ... - void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; + bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; private: @@ -282,7 +295,7 @@ namespace AzFramework explicit TranslateCameraInput(TranslationAxesFn translationAxesFn); // CameraInput overrides ... - void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; + bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; void ResetImpl() override; @@ -352,7 +365,7 @@ namespace AzFramework { public: // CameraInput overrides ... - void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; + bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; }; @@ -362,7 +375,7 @@ namespace AzFramework explicit OrbitDollyCursorMoveCameraInput(InputChannelId dollyChannelId); // CameraInput overrides ... - void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; + bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; private: @@ -373,7 +386,7 @@ namespace AzFramework { public: // CameraInput overrides ... - void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; + bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; }; @@ -383,7 +396,7 @@ namespace AzFramework using LookAtFn = AZStd::function()>; // CameraInput overrides ... - void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; + bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; bool Exclusive() const override; diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp index 4b8fbca36a..c276463554 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp @@ -17,6 +17,17 @@ namespace AzFramework { ClickDetector::ClickOutcome ClickDetector::DetectClick(const ClickEvent clickEvent, const ScreenVector& cursorDelta) { + const auto previousDetectionState = m_detectionState; + if (previousDetectionState == DetectionState::WaitingForMove) + { + // only allow the action to begin if the mouse has been moved a small amount + m_moveAccumulator += ScreenVectorLength(cursorDelta); + if (m_moveAccumulator > m_deadZone) + { + m_detectionState = DetectionState::Moved; + } + } + if (clickEvent == ClickEvent::Down) { const auto now = std::chrono::steady_clock::now(); @@ -52,15 +63,9 @@ namespace AzFramework return clickOutcome; } - if (m_detectionState == DetectionState::WaitingForMove) + if (previousDetectionState == DetectionState::WaitingForMove && m_detectionState == DetectionState::Moved) { - // only allow the action to begin if the mouse has been moved a small amount - m_moveAccumulator += ScreenVectorLength(cursorDelta); - if (m_moveAccumulator > m_deadZone) - { - m_detectionState = DetectionState::Moved; - return ClickOutcome::Move; - } + return ClickOutcome::Move; } return ClickOutcome::Nil; diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h index 997ccd07d9..a595735d28 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h @@ -50,7 +50,11 @@ namespace AzFramework //! Called from any type of 'handle event' function. ClickOutcome DetectClick(ClickEvent clickEvent, const ScreenVector& cursorDelta); + //! Override the default double click interval. + //! @note Default is 400ms - system default. void SetDoubleClickInterval(float doubleClickInterval); + //! Override the dead zone before a 'move' outcome will be triggered. + void SetDeadZone(float deadZone); private: //! Internal state of ClickDetector based on incoming events. @@ -72,4 +76,9 @@ namespace AzFramework { m_doubleClickInterval = doubleClickInterval; } + + inline void ClickDetector::SetDeadZone(const float deadZone) + { + m_deadZone = deadZone; + } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake index 8cff479ec8..13dff43f68 100644 --- a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake +++ b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake @@ -188,6 +188,12 @@ set(FILES Script/ScriptDebugMsgReflection.h Script/ScriptRemoteDebugging.cpp Script/ScriptRemoteDebugging.h + Session/ISessionHandlingRequests.h + Session/ISessionRequests.cpp + Session/ISessionRequests.h + Session/SessionConfig.cpp + Session/SessionConfig.h + Session/SessionNotifications.h StreamingInstall/StreamingInstall.h StreamingInstall/StreamingInstall.cpp StreamingInstall/StreamingInstallRequests.h diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h index ee95412376..8e91dc945d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -304,4 +305,24 @@ namespace AzToolsFramework return entityContextId; } + + //! Maps a mouse interaction event to a ClickDetector event. + //! @note Function only cares about up or down events, all other events are mapped to Nil (ignored). + inline AzFramework::ClickDetector::ClickEvent ClickDetectorEventFromViewportInteraction( + const ViewportInteraction::MouseInteractionEvent& mouseInteraction) + { + if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left()) + { + if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down) + { + return AzFramework::ClickDetector::ClickEvent::Down; + } + + if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Up) + { + return AzFramework::ClickDetector::ClickEvent::Up; + } + } + return AzFramework::ClickDetector::ClickEvent::Nil; + } } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.cpp index 2e467caa4c..531cffb561 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.cpp @@ -14,6 +14,7 @@ #include #include +#include #include @@ -27,8 +28,11 @@ namespace AzToolsFramework { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework); - if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() && - mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down) + m_cursorState.SetCurrentPosition(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates); + + const auto selectClickEvent = ClickDetectorEventFromViewportInteraction(mouseInteraction); + const auto clickOutcome = m_clickDetector.DetectClick(selectClickEvent, m_cursorState.CursorDelta()); + if (clickOutcome == AzFramework::ClickDetector::ClickOutcome::Move) { if (m_leftMouseDown) { @@ -58,8 +62,7 @@ namespace AzToolsFramework } } - if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() && - mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Up) + if (clickOutcome == AzFramework::ClickDetector::ClickOutcome::Release) { if (m_leftMouseUp) { @@ -77,6 +80,8 @@ namespace AzToolsFramework { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework); + m_cursorState.Update(); + if (m_boxSelectRegion) { debugDisplay.DepthTestOff(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.h index 7f50b16325..c115220755 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.h @@ -14,6 +14,8 @@ #include #include +#include +#include #include #include @@ -26,49 +28,49 @@ namespace AzFramework namespace AzToolsFramework { - /// Utility to provide box select (click and drag) support for viewport types. - /// Users can override the mouse event callbacks and display scene function to customize behavior. + //! Utility to provide box select (click and drag) support for viewport types. + //! Users can override the mouse event callbacks and display scene function to customize behavior. class EditorBoxSelect { public: EditorBoxSelect() = default; - /// Return if a box select action is currently taking place. + //! Return if a box select action is currently taking place. bool Active() const { return m_boxSelectRegion.has_value(); } - /// Update the box select for various mouse events. - /// Call HandleMouseInteraction from type/system implementing MouseViewportRequests interface. + //! Update the box select for various mouse events. + //! Call HandleMouseInteraction from type/system implementing MouseViewportRequests interface. void HandleMouseInteraction( const ViewportInteraction::MouseInteractionEvent& mouseInteraction); - /// Responsible for drawing the 2d box representing the selection in screen space. + //! Responsible for drawing the 2d box representing the selection in screen space. void Display2d(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay); - /// Custom drawing behavior to happen during a box select. + //! Custom drawing behavior to happen during a box select. void DisplayScene( const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay); - /// Set the left mouse down callback. + //! Set the left mouse down callback. void InstallLeftMouseDown( const AZStd::function& leftMouseDown); - /// Set the mouse move callback. + //! Set the mouse move callback. void InstallMouseMove( const AZStd::function& mouseMove); - /// Set the left mouse up callback. + //! Set the left mouse up callback. void InstallLeftMouseUp( const AZStd::function& leftMouseUp); - /// Set the display scene callback. + //! Set the display scene callback. void InstallDisplayScene( const AZStd::function& displayScene); - /// Return the box select region. - /// If a box selection is being made, return the current rectangle representing the area. - /// If there is currently no active box select, then the Maybe type will be empty (there will be no region/area). + //! Return the box select region. + //! If a box selection is being made, return the current rectangle representing the area. + //! If there is currently no active box select, then the Maybe type will be empty (there will be no region/area). const AZStd::optional& BoxRegion() const { return m_boxSelectRegion; } - /// Return the active modifiers from the previous frame. + //! Return the active modifiers from the previous frame. ViewportInteraction::KeyboardModifiers PreviousModifiers() const { return m_previousModifiers; } private: @@ -79,7 +81,9 @@ namespace AzToolsFramework AZStd::function m_displayScene; - AZStd::optional m_boxSelectRegion; ///< Maybe/optional value to store box select region while active. - ViewportInteraction::KeyboardModifiers m_previousModifiers; ///< Modifier keys active on the previous frame. + AZStd::optional m_boxSelectRegion; //!< Maybe/optional value to store box select region while active. + ViewportInteraction::KeyboardModifiers m_previousModifiers; //!< Modifier keys active on the previous frame. + AzFramework::ClickDetector m_clickDetector; //!< Utility type to detect if a mouse click or move has occurred. + AzFramework::CursorState m_cursorState; //!< Utility type to track the current cursor position (and movement/delta). }; } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index 6e49f7c601..91644dc6ac 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -1782,22 +1782,7 @@ namespace AzToolsFramework m_cachedEntityIdUnderCursor = m_editorHelpers->HandleMouseInteraction(cameraState, mouseInteraction); - const AzFramework::ClickDetector::ClickEvent selectClickEvent = [&mouseInteraction] { - if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left()) - { - if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down) - { - return AzFramework::ClickDetector::ClickEvent::Down; - } - - if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Up) - { - return AzFramework::ClickDetector::ClickEvent::Up; - } - } - return AzFramework::ClickDetector::ClickEvent::Nil; - }(); - + const auto selectClickEvent = ClickDetectorEventFromViewportInteraction(mouseInteraction); m_cursorState.SetCurrentPosition(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates); const auto clickOutcome = m_clickDetector.DetectClick(selectClickEvent, m_cursorState.CursorDelta()); diff --git a/Code/Framework/Tests/CameraInputTests.cpp b/Code/Framework/Tests/CameraInputTests.cpp new file mode 100644 index 0000000000..6fe9837c22 --- /dev/null +++ b/Code/Framework/Tests/CameraInputTests.cpp @@ -0,0 +1,90 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + class CameraInputFixture : public AllocatorsTestFixture + { + public: + AzFramework::Camera m_camera; + AzFramework::Camera m_targetCamera; + AZStd::shared_ptr m_cameraSystem; + + bool HandleEventAndUpdate(const AzFramework::InputEvent& event) + { + constexpr float deltaTime = 0.01666f; // 60fps + const bool consumed = m_cameraSystem->HandleEvents(event); + m_camera = m_cameraSystem->StepCamera(m_targetCamera, deltaTime); + return consumed; + } + + void SetUp() override + { + AllocatorsTestFixture::SetUp(); + + AzFramework::ReloadCameraKeyBindings(); + + m_cameraSystem = AZStd::make_shared(); + + auto firstPersonRotateCamera = AZStd::make_shared(AzFramework::InputDeviceMouse::Button::Right); + auto firstPersonTranslateCamera = AZStd::make_shared(AzFramework::LookTranslation); + + auto orbitCamera = AZStd::make_shared(); + auto orbitRotateCamera = AZStd::make_shared(AzFramework::InputDeviceMouse::Button::Left); + auto orbitTranslateCamera = AZStd::make_shared(AzFramework::OrbitTranslation); + + orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera); + orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera); + + m_cameraSystem->m_cameras.AddCamera(firstPersonRotateCamera); + m_cameraSystem->m_cameras.AddCamera(firstPersonTranslateCamera); + m_cameraSystem->m_cameras.AddCamera(orbitCamera); + } + + void TearDown() override + { + m_cameraSystem->m_cameras.Clear(); + m_cameraSystem.reset(); + + AllocatorsTestFixture::TearDown(); + } + }; + + TEST_F(CameraInputFixture, BeginEndOrbitCameraConsumesCorrectEvents) + { + // set initial mouse position + const bool consumed1 = HandleEventAndUpdate(AzFramework::CursorEvent{AzFramework::ScreenPoint(5, 5)}); + // begin orbit camera + const bool consumed2 = HandleEventAndUpdate( + AzFramework::DiscreteInputEvent{AzFramework::InputDeviceKeyboard::Key::ModifierAltL, AzFramework::InputChannel::State::Began}); + // begin listening for orbit rotate (click detector) - event is not consumed + const bool consumed3 = HandleEventAndUpdate( + AzFramework::DiscreteInputEvent{AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began}); + // begin orbit rotate (mouse has moved sufficient distance to initiate) + const bool consumed4 = HandleEventAndUpdate(AzFramework::CursorEvent{AzFramework::ScreenPoint(10, 10)}); + // end orbit (mouse up) - event is not consumed + const bool consumed5 = HandleEventAndUpdate( + AzFramework::DiscreteInputEvent{AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Ended}); + + const auto allConsumed = AZStd::vector{consumed1, consumed2, consumed3, consumed4, consumed5}; + + using ::testing::ElementsAre; + EXPECT_THAT(allConsumed, ElementsAre(false, true, false, true, false)); + } +} // namespace UnitTest diff --git a/Code/Framework/Tests/ClickDetectorTests.cpp b/Code/Framework/Tests/ClickDetectorTests.cpp index 7e6f9634c8..64f06ee66c 100644 --- a/Code/Framework/Tests/ClickDetectorTests.cpp +++ b/Code/Framework/Tests/ClickDetectorTests.cpp @@ -139,4 +139,21 @@ namespace UnitTest EXPECT_THAT(secondaryDownOutcome, Eq(ClickDetector::ClickOutcome::Nil)); // ignored double click EXPECT_THAT(secondaryUpOutcome, Eq(ClickDetector::ClickOutcome::Nil)); // click not registered } + + // if the click detector registers a mouse down event, but then all intermediate calls are ignored + // (another system may start intercepting events and swallowing them) then when we do receive a mouse + // up event we should ensure we take into account the current delta - if the delta is large, then the + // outcome will be release + TEST_F(ClickDetectorFixture, ClickIsNotRegisteredAfterIgnoringMouseMovesBeforeMouseUpWithLargeDelta) + { + using ::testing::Eq; + + const ClickDetector::ClickOutcome downOutcome = + m_clickDetector.DetectClick(ClickDetector::ClickEvent::Down, ScreenVector(0, 0)); + const ClickDetector::ClickOutcome upOutcome = + m_clickDetector.DetectClick(ClickDetector::ClickEvent::Up, ScreenVector(50, 50)); + + EXPECT_THAT(downOutcome, Eq(ClickDetector::ClickOutcome::Nil)); + EXPECT_THAT(upOutcome, Eq(ClickDetector::ClickOutcome::Release)); + } } // namespace UnitTest diff --git a/Code/Framework/Tests/frameworktests_files.cmake b/Code/Framework/Tests/frameworktests_files.cmake index e249cf6e64..197bcc9fce 100644 --- a/Code/Framework/Tests/frameworktests_files.cmake +++ b/Code/Framework/Tests/frameworktests_files.cmake @@ -17,6 +17,7 @@ set(FILES BinToTextEncode.cpp ComponentAddRemove.cpp ComponentAdapterTests.cpp + CameraInputTests.cpp ClickDetectorTests.cpp CursorStateTests.cpp EntityContext.cpp diff --git a/Code/Sandbox/Editor/ModernViewportCameraController.cpp b/Code/Sandbox/Editor/ModernViewportCameraController.cpp index af161af493..83ab2ef0b5 100644 --- a/Code/Sandbox/Editor/ModernViewportCameraController.cpp +++ b/Code/Sandbox/Editor/ModernViewportCameraController.cpp @@ -97,17 +97,38 @@ namespace SandboxEditor AzFramework::ViewportDebugDisplayEventBus::Handler::BusDisconnect(); } + // should the camera system respond to this particular event + static bool ShouldHandle(const AzFramework::ViewportControllerPriority priority, const bool exclusive) + { + // ModernViewportCameraControllerInstance receives events at all priorities, it should only respond + // to normal priority events if it is not in 'exclusive' mode and when in 'exclusive' mode it should + // only respond to the highest priority events + return !exclusive && priority == AzFramework::ViewportControllerPriority::Normal || + exclusive && priority == AzFramework::ViewportControllerPriority::Highest; + } + bool ModernViewportCameraControllerInstance::HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event) { AzFramework::WindowSize windowSize; AzFramework::WindowRequestBus::EventResult( windowSize, event.m_windowHandle, &AzFramework::WindowRequestBus::Events::GetClientAreaSize); - return m_cameraSystem.HandleEvents(AzFramework::BuildInputEvent(event.m_inputChannel, windowSize)); + if (ShouldHandle(event.m_priority, m_cameraSystem.m_cameras.Exclusive())) + { + return m_cameraSystem.HandleEvents(AzFramework::BuildInputEvent(event.m_inputChannel, windowSize)); + } + + return false; } void ModernViewportCameraControllerInstance::UpdateViewport(const AzFramework::ViewportControllerUpdateEvent& event) { + // only update for a single priority (normal is the default) + if (event.m_priority != AzFramework::ViewportControllerPriority::Normal) + { + return; + } + if (auto viewportContext = RetrieveViewportContext(GetViewportId())) { m_updatingTransform = true; diff --git a/Code/Sandbox/Editor/ModernViewportCameraController.h b/Code/Sandbox/Editor/ModernViewportCameraController.h index 066c8efaa8..39e3c9cbb3 100644 --- a/Code/Sandbox/Editor/ModernViewportCameraController.h +++ b/Code/Sandbox/Editor/ModernViewportCameraController.h @@ -22,7 +22,9 @@ namespace SandboxEditor { class ModernViewportCameraControllerInstance; - class ModernViewportCameraController : public AzFramework::MultiViewportController + class ModernViewportCameraController + : public AzFramework::MultiViewportController< + ModernViewportCameraControllerInstance, AzFramework::ViewportControllerPriority::DispatchToAllPriorities> { public: using CameraListBuilder = AZStd::function; diff --git a/Code/Tools/ProjectManager/Resources/Backgrounds/FirstTimeBackgroundImage.jpg b/Code/Tools/ProjectManager/Resources/Backgrounds/FirstTimeBackgroundImage.jpg new file mode 100644 index 0000000000..bfa5f83cf6 --- /dev/null +++ b/Code/Tools/ProjectManager/Resources/Backgrounds/FirstTimeBackgroundImage.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7088e902885d98953f6a1715efab319c063a4ab8918fd0e810251c8ed82b8514 +size 542983 diff --git a/Code/Tools/ProjectManager/Resources/Backgrounds/LICENSE.TXT b/Code/Tools/ProjectManager/Resources/Backgrounds/LICENSE.TXT new file mode 100644 index 0000000000..5adf81a289 --- /dev/null +++ b/Code/Tools/ProjectManager/Resources/Backgrounds/LICENSE.TXT @@ -0,0 +1,4 @@ +SPDX-FileCopyrightText: Unsplash grants you an irrevocable, nonexclusive, worldwide copyright license to download, copy, +SPDX-FileCopyrightText: modify, distribute, perform, and use photos from Unsplash for free, including for commercial +SPDX-FileCopyrightText: purposes, without permission from or attributing the photographer or Unsplash. This license does +SPDX-FileCopyrightText: not include the right to compile photos from Unsplash to replicate a similar or competing service. \ No newline at end of file diff --git a/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.cpp b/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.cpp index aa4dee1aa6..2c96078d43 100644 --- a/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.cpp +++ b/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.cpp @@ -12,18 +12,64 @@ #include -#include +#include +#include +#include +#include +#include +#include namespace O3DE::ProjectManager { + inline constexpr static int s_contentMargins = 80; + inline constexpr static int s_buttonSpacing = 30; + inline constexpr static int s_iconSize = 24; + inline constexpr static int s_spacerSize = 20; + inline constexpr static int s_boxButtonWidth = 210; + inline constexpr static int s_boxButtonHeight = 280; + FirstTimeUseScreen::FirstTimeUseScreen(QWidget* parent) : ScreenWidget(parent) - , m_ui(new Ui::FirstTimeUseClass()) { - m_ui->setupUi(this); + QVBoxLayout* vLayout = new QVBoxLayout(); + setLayout(vLayout); + vLayout->setContentsMargins(s_contentMargins, s_contentMargins, s_contentMargins, s_contentMargins); + + QLabel* titleLabel = new QLabel(this); + titleLabel->setText(tr("Ready. Set. Create!")); + titleLabel->setStyleSheet("font-size: 60px"); + vLayout->addWidget(titleLabel); + + QLabel* introLabel = new QLabel(this); + introLabel->setTextFormat(Qt::AutoText); + introLabel->setText(tr("

Welcome to O3DE! Start something new by creating a project. Not sure what to create?

Explore what\342\200\231s available by downloading our sample project.

")); + introLabel->setStyleSheet("font-size: 14px"); + vLayout->addWidget(introLabel); + + QHBoxLayout* buttonLayout = new QHBoxLayout(); + buttonLayout->setSpacing(s_buttonSpacing); + + m_createProjectButton = CreateLargeBoxButton(QIcon(":/Resources/Add.svg"), tr("Create Project"), this); + m_createProjectButton->setIconSize(QSize(s_iconSize, s_iconSize)); + buttonLayout->addWidget(m_createProjectButton); + + m_addProjectButton = CreateLargeBoxButton(QIcon(":/Resources/Select_Folder.svg"), tr("Add a Project"), this); + m_addProjectButton->setIconSize(QSize(s_iconSize, s_iconSize)); + buttonLayout->addWidget(m_addProjectButton); + + QSpacerItem* buttonSpacer = new QSpacerItem(s_spacerSize, s_spacerSize, QSizePolicy::Expanding, QSizePolicy::Minimum); + buttonLayout->addItem(buttonSpacer); + + vLayout->addItem(buttonLayout); - connect(m_ui->createProjectButton, &QPushButton::pressed, this, &FirstTimeUseScreen::HandleNewProjectButton); - connect(m_ui->openProjectButton, &QPushButton::pressed, this, &FirstTimeUseScreen::HandleOpenProjectButton); + QSpacerItem* verticalSpacer = new QSpacerItem(s_spacerSize, s_spacerSize, QSizePolicy::Minimum, QSizePolicy::Expanding); + vLayout->addItem(verticalSpacer); + + // Using border-image allows for scaling options background-image does not support + setStyleSheet("O3DE--ProjectManager--ScreenWidget { border-image: url(:/Resources/Backgrounds/FirstTimeBackgroundImage.jpg) repeat repeat; }"); + + connect(m_createProjectButton, &QPushButton::pressed, this, &FirstTimeUseScreen::HandleNewProjectButton); + connect(m_addProjectButton, &QPushButton::pressed, this, &FirstTimeUseScreen::HandleAddProjectButton); } ProjectManagerScreen FirstTimeUseScreen::GetScreenEnum() @@ -36,9 +82,21 @@ namespace O3DE::ProjectManager emit ResetScreenRequest(ProjectManagerScreen::NewProjectSettingsCore); emit ChangeScreenRequest(ProjectManagerScreen::NewProjectSettingsCore); } - void FirstTimeUseScreen::HandleOpenProjectButton() + void FirstTimeUseScreen::HandleAddProjectButton() { emit ChangeScreenRequest(ProjectManagerScreen::ProjectsHome); } + QPushButton* FirstTimeUseScreen::CreateLargeBoxButton(const QIcon& icon, const QString& text, QWidget* parent) + { + QPushButton* largeBoxButton = new QPushButton(icon, text, parent); + + largeBoxButton->setFixedSize(s_boxButtonWidth, s_boxButtonHeight); + largeBoxButton->setFlat(true); + largeBoxButton->setFocusPolicy(Qt::FocusPolicy::NoFocus); + largeBoxButton->setStyleSheet("QPushButton { font-size: 14px; background-color: rgba(0, 0, 0, 191); }"); + + return largeBoxButton; + } + } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.h b/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.h index 4b4a99f16a..b6b57dc16b 100644 --- a/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.h +++ b/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.h @@ -15,10 +15,8 @@ #include #endif -namespace Ui -{ - class FirstTimeUseClass; -} +QT_FORWARD_DECLARE_CLASS(QIcon) +QT_FORWARD_DECLARE_CLASS(QPushButton) namespace O3DE::ProjectManager { @@ -32,10 +30,13 @@ namespace O3DE::ProjectManager protected slots: void HandleNewProjectButton(); - void HandleOpenProjectButton(); + void HandleAddProjectButton(); private: - QScopedPointer m_ui; + QPushButton* CreateLargeBoxButton(const QIcon& icon, const QString& text, QWidget* parent = nullptr); + + QPushButton* m_createProjectButton; + QPushButton* m_addProjectButton; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.ui b/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.ui deleted file mode 100644 index fdc195731f..0000000000 --- a/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.ui +++ /dev/null @@ -1,93 +0,0 @@ - - - FirstTimeUseClass - - - - 0 - 0 - 881 - 555 - - - - Form - - - - - - - - - 30 - - - - READY. SET. CREATE! - - - - - - - <html><head/><body><p>Welcome to O3DE! Start something new by creating a project. Not sure what to create? </p><p>Explore what’s available by downloading our sample project.</p></body></html> - - - Qt::AutoText - - - - - - - - - - - - 0 - 0 - - - - Create Project - - - - :/Resources/Add.svg:/Resources/Add.svg - - - - 16 - 16 - - - - - - - - - 0 - 0 - - - - Open a Project - - - - :/Resources/Select_Folder.svg:/Resources/Select_Folder.svg - - - - - - - - - - - - diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp index 977667071f..6b9d268564 100644 --- a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp @@ -27,6 +27,12 @@ namespace O3DE::ProjectManager , m_ui(new Ui::ProjectManagerWindowClass()) { m_ui->setupUi(this); + QLayout* layout = m_ui->centralWidget->layout(); + layout->setMargin(0); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); + + setFixedSize(this->geometry().width(), this->geometry().height()); m_pythonBindings = AZStd::make_unique(engineRootPath); diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui index 789dd1b656..a71ed3aabf 100644 --- a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui +++ b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui @@ -6,10 +6,16 @@ 0 0 - 800 - 600 + 1200 + 800 + + + 0 + 0 + + O3DE Project Manager @@ -21,7 +27,7 @@ 0 0 - 800 + 1200 36 diff --git a/Code/Tools/ProjectManager/Source/ScreenWidget.h b/Code/Tools/ProjectManager/Source/ScreenWidget.h index ae235daf2b..483066e031 100644 --- a/Code/Tools/ProjectManager/Source/ScreenWidget.h +++ b/Code/Tools/ProjectManager/Source/ScreenWidget.h @@ -15,18 +15,20 @@ #include #include +#include +#include #endif namespace O3DE::ProjectManager { class ScreenWidget - : public QWidget + : public QFrame { Q_OBJECT public: explicit ScreenWidget(QWidget* parent = nullptr) - : QWidget(parent) + : QFrame(parent) { } ~ScreenWidget() = default; diff --git a/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp b/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp index 69af09f496..b8a38ed155 100644 --- a/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp @@ -22,6 +22,9 @@ namespace O3DE::ProjectManager : QWidget(parent) { QVBoxLayout* vLayout = new QVBoxLayout(); + vLayout->setMargin(0); + vLayout->setSpacing(0); + vLayout->setContentsMargins(0, 0, 0, 0); setLayout(vLayout); m_screenStack = new QStackedWidget(); diff --git a/Code/Tools/ProjectManager/project_manager.qrc b/Code/Tools/ProjectManager/project_manager.qrc index 6509a9f940..3c23bc24ff 100644 --- a/Code/Tools/ProjectManager/project_manager.qrc +++ b/Code/Tools/ProjectManager/project_manager.qrc @@ -9,5 +9,6 @@ Resources/iOS.svg Resources/Linux.svg Resources/macOS.svg + Resources/Backgrounds/FirstTimeBackgroundImage.jpg diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake index 9ffdb6029d..3594d1e079 100644 --- a/Code/Tools/ProjectManager/project_manager_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_files.cmake @@ -22,7 +22,6 @@ set(FILES Source/EngineInfo.cpp Source/FirstTimeUseScreen.h Source/FirstTimeUseScreen.cpp - Source/FirstTimeUseScreen.ui Source/ProjectManagerWindow.h Source/ProjectManagerWindow.cpp Source/ProjectTemplateInfo.h diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp index 3dc14814de..d2818f3653 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp @@ -41,18 +41,6 @@ namespace AZ static AZ::SceneAPI::FbxSceneImporter::FbxImportRequestHandler* g_fbxImporter = nullptr; static AZStd::vector g_componentDescriptors; - void Initialize() - { - // Currently it's still needed to explicitly create an instance of this instead of letting - // it be a normal component. This is because ResourceCompilerScene needs to return - // the list of available extensions before it can start the application. - if (!g_fbxImporter) - { - g_fbxImporter = aznew AZ::SceneAPI::FbxSceneImporter::FbxImportRequestHandler(); - g_fbxImporter->Activate(); - } - } - void Reflect(AZ::SerializeContext* /*context*/) { // Descriptor registration is done in Reflect instead of Initialize because the ResourceCompilerScene initializes the libraries before @@ -64,6 +52,7 @@ namespace AZ { // Global importer and behavior g_componentDescriptors.push_back(FbxSceneBuilder::FbxImporter::CreateDescriptor()); + g_componentDescriptors.push_back(FbxSceneImporter::FbxImportRequestHandler::CreateDescriptor()); // Node and attribute importers g_componentDescriptors.push_back(AssImpBitangentStreamImporter::CreateDescriptor()); @@ -125,7 +114,6 @@ namespace AZ extern "C" AZ_DLL_EXPORT void InitializeDynamicModule(void* env) { AZ::Environment::Attach(static_cast(env)); - AZ::SceneAPI::FbxSceneBuilder::Initialize(); } extern "C" AZ_DLL_EXPORT void Reflect(AZ::SerializeContext* context) { diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp index 155209f1b5..a43f1e16b8 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp @@ -10,12 +10,16 @@ * */ +#include +#include #include -#include +#include +#include +#include +#include #include #include #include -#include namespace AZ { @@ -23,10 +27,25 @@ namespace AZ { namespace FbxSceneImporter { - const char* FbxImportRequestHandler::s_extension = ".fbx"; + void SceneImporterSettings::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context); serializeContext) + { + serializeContext->Class() + ->Version(1) + ->Field("SupportedFileTypeExtensions", &SceneImporterSettings::m_supportedFileTypeExtensions); + } + } void FbxImportRequestHandler::Activate() { + auto settingsRegistry = AZ::SettingsRegistry::Get(); + + if (settingsRegistry) + { + settingsRegistry->GetObject(m_settings, "/O3DE/SceneAPI/AssetImporter"); + } + BusConnect(); } @@ -37,21 +56,29 @@ namespace AZ void FbxImportRequestHandler::Reflect(ReflectContext* context) { + SceneImporterSettings::Reflect(context); + SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) { - serializeContext->Class()->Version(1); + serializeContext->Class()->Version(1)->Attribute( + AZ::Edit::Attributes::SystemComponentTags, + AZStd::vector({AssetBuilderSDK::ComponentTags::AssetBuilder})); + } } void FbxImportRequestHandler::GetSupportedFileExtensions(AZStd::unordered_set& extensions) { - extensions.insert(s_extension); + extensions.insert(m_settings.m_supportedFileTypeExtensions.begin(), m_settings.m_supportedFileTypeExtensions.end()); } Events::LoadingResult FbxImportRequestHandler::LoadAsset(Containers::Scene& scene, const AZStd::string& path, const Uuid& guid, [[maybe_unused]] RequestingApplication requester) { - if (!AzFramework::StringFunc::Path::IsExtension(path.c_str(), s_extension)) + AZStd::string extension; + StringFunc::Path::GetExtension(path.c_str(), extension); + + if (!m_settings.m_supportedFileTypeExtensions.contains(extension)) { return Events::LoadingResult::Ignored; } @@ -73,6 +100,11 @@ namespace AZ return Events::LoadingResult::AssetFailure; } } + + void FbxImportRequestHandler::GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) + { + provided.emplace_back(AZ_CRC_CE("AssetImportRequestHandler")); + } } // namespace Import } // namespace SceneAPI } // namespace AZ diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h index 8b33051f1e..12c7c6f877 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h @@ -21,12 +21,21 @@ namespace AZ { namespace FbxSceneImporter { + struct SceneImporterSettings + { + AZ_TYPE_INFO(SceneImporterSettings, "{8BB6C7AD-BF99-44DC-9DA1-E7AD3F03DC10}"); + + static void Reflect(AZ::ReflectContext* context); + + AZStd::unordered_set m_supportedFileTypeExtensions; + }; + class FbxImportRequestHandler - : public SceneCore::BehaviorComponent + : public AZ::Component , public Events::AssetImportRequestBus::Handler { public: - AZ_COMPONENT(FbxImportRequestHandler, "{9F4B189C-0A96-4F44-A5F0-E087FF1561F8}", SceneCore::BehaviorComponent); + AZ_COMPONENT(FbxImportRequestHandler, "{9F4B189C-0A96-4F44-A5F0-E087FF1561F8}"); ~FbxImportRequestHandler() override = default; @@ -38,8 +47,13 @@ namespace AZ Events::LoadingResult LoadAsset(Containers::Scene& scene, const AZStd::string& path, const Uuid& guid, RequestingApplication requester) override; + static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided); + private: - static const char* s_extension; + + SceneImporterSettings m_settings; + + static constexpr const char* SettingsFilename = "AssetImporterSettings.json"; }; } // namespace FbxSceneImporter } // namespace SceneAPI diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpAnimationImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpAnimationImporter.cpp index 0ee25195bc..38f7de89c6 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpAnimationImporter.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpAnimationImporter.cpp @@ -260,7 +260,7 @@ namespace AZ { AZ_TraceContext("Importer", "Animation"); - aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); + const aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiScene* scene = context.m_sourceScene.GetAssImpScene(); // Add check for animation layers at the scene level. @@ -387,11 +387,10 @@ namespace AZ } Events::ProcessingResultCombiner combinedAnimationResult; - for (AZ::u32 meshIndex = 0; meshIndex < currentNode->mNumMeshes; ++meshIndex) + if (context.m_sourceNode.ContainsMesh()) { - aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[meshIndex]]; - - if (NodeToChannelToMorphAnim::iterator channelsForMeshName = meshMorphAnimations.find(mesh->mName.C_Str()); + const aiMesh* firstMesh = scene->mMeshes[currentNode->mMeshes[0]]; + if (NodeToChannelToMorphAnim::iterator channelsForMeshName = meshMorphAnimations.find(firstMesh->mName.C_Str()); channelsForMeshName != meshMorphAnimations.end()) { const auto [nodeIterName, channels] = *channelsForMeshName; @@ -399,7 +398,7 @@ namespace AZ { const auto& [animation, morphAnimation] = animAndMorphAnim; combinedAnimationResult += ImportBlendShapeAnimation( - context, animation, morphAnimation, mesh); + context, animation, morphAnimation, firstMesh); } } } @@ -413,32 +412,39 @@ namespace AZ if (boneAnimations.empty() && !meshMorphAnimations.empty()) { const aiAnimation* animation = scene->mAnimations[0]; - - // Morph animations need a regular animation on the node, as well. - // If there is no bone animation on the current node, then generate one here. - AZStd::shared_ptr createdAnimationData = - AZStd::make_shared(); - - const size_t numKeyframes = animation->mDuration + 1; // +1 because we start at 0 and the last keyframe is at mDuration instead of mDuration-1 - createdAnimationData->ReserveKeyFrames(numKeyframes); - - const double timeStepBetweenFrames = 1.0 / animation->mTicksPerSecond; - createdAnimationData->SetTimeStepBetweenFrames(timeStepBetweenFrames); - - // Set every frame of the animation to the start location of the node. - aiMatrix4x4 combinedTransform = GetConcatenatedLocalTransform(currentNode); - DataTypes::MatrixType localTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(combinedTransform); - context.m_sourceSceneSystem.SwapTransformForUpAxis(localTransform); - context.m_sourceSceneSystem.ConvertUnit(localTransform); - for (AZ::u32 time = 0; time <= animation->mDuration; ++time) + for (AZ::u32 channelIndex = 0; channelIndex < animation->mNumMorphMeshChannels; ++channelIndex) { - createdAnimationData->AddKeyFrame(localTransform); - } - - Containers::SceneGraph::NodeIndex addNode = context.m_scene.GetGraph().AddChild( - context.m_currentGraphPosition, nodeName.c_str(), AZStd::move(createdAnimationData)); - context.m_scene.GetGraph().MakeEndPoint(addNode); + const aiMeshMorphAnim* nodeAnim = animation->mMorphMeshChannels[channelIndex]; + // Morph animations need a regular animation on the node, as well. + // If there is no bone animation on the current node, then generate one here. + AZStd::shared_ptr createdAnimationData = + AZStd::make_shared(); + + const size_t numKeyframes = GetNumKeyFrames( + nodeAnim->mNumKeys, + animation->mDuration, + animation->mTicksPerSecond); + createdAnimationData->ReserveKeyFrames(numKeyframes); + + const double timeStepBetweenFrames = 1.0 / animation->mTicksPerSecond; + createdAnimationData->SetTimeStepBetweenFrames(timeStepBetweenFrames); + + // Set every frame of the animation to the start location of the node. + aiMatrix4x4 combinedTransform = GetConcatenatedLocalTransform(currentNode); + DataTypes::MatrixType localTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(combinedTransform); + context.m_sourceSceneSystem.SwapTransformForUpAxis(localTransform); + context.m_sourceSceneSystem.ConvertUnit(localTransform); + for (AZ::u32 time = 0; time <= numKeyframes; ++time) + { + createdAnimationData->AddKeyFrame(localTransform); + } + const AZStd::string stubBoneAnimForMorphName(AZStd::string::format("%s%s", nodeName.c_str(), nodeAnim->mName.C_Str())); + Containers::SceneGraph::NodeIndex addNode = context.m_scene.GetGraph().AddChild( + context.m_currentGraphPosition, stubBoneAnimForMorphName.c_str(), AZStd::move(createdAnimationData)); + context.m_scene.GetGraph().MakeEndPoint(addNode); + } + return combinedAnimationResult.GetResult(); } decltype(boneAnimations) parentFillerAnimations; @@ -446,8 +452,8 @@ namespace AZ // Go through all the animations and make sure we create animations for bones who's parents don't have an animation for (auto&& anim : boneAnimations) { - aiNode* node = scene->mRootNode->FindNode(anim.first.c_str()); - aiNode* parent = node->mParent; + const aiNode* node = scene->mRootNode->FindNode(anim.first.c_str()); + const aiNode* parent = node->mParent; while (parent && parent != scene->mRootNode) { @@ -598,7 +604,8 @@ namespace AZ // Keyframes generated for every single frame of the animation. typedef AZStd::map> ValueToKeyDataMap; ValueToKeyDataMap valueToKeyDataMap; - + // Key time can be less than zero, normalize to have zero be the lowest time. + double keyOffset = 0; for (int keyIdx = 0; keyIdx < meshMorphAnim->mNumKeys; keyIdx++) { aiMeshMorphKey& key = meshMorphAnim->mKeys[keyIdx]; @@ -609,6 +616,10 @@ namespace AZ valueToKeyDataMap[currentValue].insert( AZStd::upper_bound(valueToKeyDataMap[currentValue].begin(), valueToKeyDataMap[currentValue].end(),thisKey), thisKey); + if (key.mTime < keyOffset) + { + keyOffset = key.mTime; + } } } @@ -631,7 +642,7 @@ namespace AZ const double time = GetTimeForFrame(frame, animation->mTicksPerSecond); float weight = 0; - if (!SampleKeyFrame(weight, keys, keys.size(), time, keyIdx)) + if (!SampleKeyFrame(weight, keys, keys.size(), time + keyOffset, keyIdx)) { return Events::ProcessingResult::Failure; } diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBitangentStreamImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBitangentStreamImporter.cpp index c2b1f20035..2ce9bc14f4 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBitangentStreamImporter.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBitangentStreamImporter.cpp @@ -25,7 +25,6 @@ #include #include - namespace AZ { namespace SceneAPI @@ -44,7 +43,7 @@ namespace AZ SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) { - serializeContext->Class()->Version(2); // LYN-2576 + serializeContext->Class()->Version(3); // LYN-3250 } } @@ -55,62 +54,79 @@ namespace AZ { return Events::ProcessingResult::Ignored; } - aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); + const aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiScene* scene = context.m_sourceScene.GetAssImpScene(); - GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context)); - if (!meshDataResult.IsSuccess()) + const auto meshHasTangentsAndBitangents = [&scene](const unsigned int meshIndex) { - return meshDataResult.GetError(); - } - const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue()); - - size_t vertexCount = parentMeshData->GetVertexCount(); + return scene->mMeshes[meshIndex]->HasTangentsAndBitangents(); + }; - int sdkMeshIndex = parentMeshData->GetSdkMeshIndex(); - if (sdkMeshIndex < 0 || sdkMeshIndex >= currentNode->mNumMeshes) + // If there are no bitangents on any meshes, there's nothing to import in this function. + const bool anyMeshHasTangentsAndBitangents = AZStd::any_of(currentNode->mMeshes, currentNode->mMeshes + currentNode->mNumMeshes, meshHasTangentsAndBitangents); + if (!anyMeshHasTangentsAndBitangents) { - AZ_Error(Utilities::ErrorWindow, false, - "Tried to construct bitangent stream attribute for invalid or non-mesh parent data, mesh index is invalid"); - return Events::ProcessingResult::Failure; + return Events::ProcessingResult::Ignored; } - aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]]; - - if (!mesh->HasTangentsAndBitangents()) + // AssImp nodes with multiple meshes on them occur when AssImp split a mesh on material. + // This logic recombines those meshes to minimize the changes needed to replace FBX SDK with AssImp, FBX SDK did not separate meshes, + // and the engine has code to do this later. + const bool allMeshesHaveTangentsAndBitangents = AZStd::all_of(currentNode->mMeshes, currentNode->mMeshes + currentNode->mNumMeshes, meshHasTangentsAndBitangents); + if (!allMeshesHaveTangentsAndBitangents) { - return Events::ProcessingResult::Ignored; + const char* mixedBitangentsError = + "Node with name %s has meshes with and without bitangents. " + "Placeholder incorrect bitangents will be generated to allow the data to process, " + "but the source art needs to be fixed to correct this. Either apply bitangents to all meshes on this node, " + "or remove all bitangents from all meshes on this node."; + AZ_Error( + Utilities::ErrorWindow, false, mixedBitangentsError, currentNode->mName.C_Str()); } + const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene); + AZStd::shared_ptr bitangentStream = AZStd::make_shared(); - // AssImp only has one bitangentStream per mesh. bitangentStream->SetBitangentSetIndex(0); bitangentStream->SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace::FromFbx); bitangentStream->ReserveContainerSpace(vertexCount); - - for (int v = 0; v < mesh->mNumVertices; ++v) + for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex) { - const Vector3 bitangent( - AssImpSDKWrapper::AssImpTypeConverter::ToVector3(mesh->mBitangents[v])); - bitangentStream->AppendBitangent(bitangent); + const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]]; + + for (int v = 0; v < mesh->mNumVertices; ++v) + { + if (!mesh->HasTangentsAndBitangents()) + { + // This node has mixed meshes with and without bitangents. + // An error was already thrown above. Output stub bitangents so + // the mesh can still be output in some form, even if the data isn't correct. + // The bitangent count needs to match the vertex count on the associated mesh node. + bitangentStream->AppendBitangent(Vector3::CreateAxisY()); + } + else + { + const Vector3 bitangent( + AssImpSDKWrapper::AssImpTypeConverter::ToVector3(mesh->mBitangents[v])); + bitangentStream->AppendBitangent(bitangent); + } + } } - AZStd::string nodeName(AZStd::string::format("%s",m_defaultNodeName)); Containers::SceneGraph::NodeIndex newIndex = - context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str()); + context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, m_defaultNodeName); Events::ProcessingResult bitangentResults; - AssImpSceneAttributeDataPopulatedContext dataPopulated(context, bitangentStream, newIndex, nodeName.c_str()); + AssImpSceneAttributeDataPopulatedContext dataPopulated(context, bitangentStream, newIndex, m_defaultNodeName); bitangentResults = Events::Process(dataPopulated); if (bitangentResults != Events::ProcessingResult::Failure) { bitangentResults = AddAttributeDataNodeWithContexts(dataPopulated); } - return bitangentResults; } diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBlendShapeImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBlendShapeImporter.cpp index c0399329d3..34266a3e29 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBlendShapeImporter.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBlendShapeImporter.cpp @@ -74,37 +74,51 @@ namespace AZ { return meshDataResult.GetError(); } - const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue()); - int parentMeshIndex = parentMeshData->GetSdkMeshIndex(); Events::ProcessingResultCombiner combinedBlendShapeResult; + // 1. Loop through meshes & anims + // Create storage: Anim to meshes + // 2. Loop through anims & meshes + // Create an anim mesh for each anim, with meshes re-combined. + // AssImp separates meshes that have multiple materials. + // This code re-combines them to match previous FBX SDK behavior, + // so they can be separated by engine code instead. + AZStd::map>> animToMeshToAnimMeshIndices; for (int nodeMeshIdx = 0; nodeMeshIdx < numMesh; nodeMeshIdx++) { int sceneMeshIdx = context.m_sourceNode.GetAssImpNode()->mMeshes[nodeMeshIdx]; const aiMesh* aiMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[sceneMeshIdx]; - - // Each mesh gets its own node in the scene graph, so only generate - // morph targets for the current mesh. - if (parentMeshIndex != nodeMeshIdx || !aiMesh->mNumAnimMeshes) + for (int animIdx = 0; animIdx < aiMesh->mNumAnimMeshes; animIdx++) { - continue; + aiAnimMesh* aiAnimMesh = aiMesh->mAnimMeshes[animIdx]; + animToMeshToAnimMeshIndices[aiAnimMesh->mName.C_Str()].emplace_back(nodeMeshIdx, animIdx); } + } - for (int animIdx = 0; animIdx < aiMesh->mNumAnimMeshes; animIdx++) + for (const auto& animToMeshIndex : animToMeshToAnimMeshIndices) + { + AZStd::shared_ptr blendShapeData = + AZStd::make_shared(); + + // Some DCC tools, like Maya, include a full path separated by '.' in the node names. + // For example, "cone_skin_blendShapeNode.cone_squash" + // Downstream processing doesn't want anything but the last part of that node name, + // so find the last '.' and remove anything before it. + AZStd::string nodeName(animToMeshIndex.first); + size_t dotIndex = nodeName.rfind('.'); + if (dotIndex != AZStd::string::npos) { - AZStd::shared_ptr blendShapeData = - AZStd::make_shared(); - - aiAnimMesh* aiAnimMesh = aiMesh->mAnimMeshes[animIdx]; - AZStd::string nodeName(aiAnimMesh->mName.C_Str()); - size_t dotIndex = nodeName.rfind('.'); - if (dotIndex != AZStd::string::npos) - { - nodeName.erase(0, dotIndex + 1); - } - RenamedNodesMap::SanitizeNodeName(nodeName, context.m_scene.GetGraph(), context.m_currentGraphPosition, "BlendShape"); - AZ_TraceContext("Blend shape name", nodeName); + nodeName.erase(0, dotIndex + 1); + } + int vertexOffset = 0; + RenamedNodesMap::SanitizeNodeName(nodeName, context.m_scene.GetGraph(), context.m_currentGraphPosition, "BlendShape"); + AZ_TraceContext("Blend shape name", nodeName); + for (const auto& meshIndex : animToMeshIndex.second) + { + int sceneMeshIdx = context.m_sourceNode.GetAssImpNode()->mMeshes[meshIndex.first]; + const aiMesh* aiMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[sceneMeshIdx]; + const aiAnimMesh* aiAnimMesh = aiMesh->mAnimMeshes[meshIndex.second]; AZStd::bitset uvSetUsedFlags; for (AZ::u8 uvSetIndex = 0; uvSetIndex < SceneData::GraphData::BlendShapeData::MaxNumUVSets; ++uvSetIndex) @@ -128,7 +142,7 @@ namespace AZ context.m_sourceSceneSystem.ConvertUnit(vertex); blendShapeData->AddPosition(vertex); - blendShapeData->SetVertexIndexToControlPointIndexMap(vertIdx, vertIdx); + blendShapeData->SetVertexIndexToControlPointIndexMap(vertIdx + vertexOffset, vertIdx + vertexOffset); // Add normals if (aiAnimMesh->HasNormals()) @@ -191,33 +205,36 @@ namespace AZ } for (int idx = 0; idx < face.mNumIndices; ++idx) { - blendFace.vertexIndex[idx] = face.mIndices[idx]; + blendFace.vertexIndex[idx] = face.mIndices[idx] + vertexOffset; } blendShapeData->AddFace(blendFace); } + vertexOffset += aiMesh->mNumVertices; - // Report problem if no vertex or face converted to MeshData - if (blendShapeData->GetVertexCount() <= 0 || blendShapeData->GetFaceCount() <= 0) - { - AZ_Error(Utilities::ErrorWindow, false, "Missing geometry data in blendshape node %s.", nodeName.c_str()); - return Events::ProcessingResult::Failure; - } - Containers::SceneGraph::NodeIndex newIndex = - context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str()); + } - Events::ProcessingResult blendShapeResult; - AssImpSceneAttributeDataPopulatedContext dataPopulated(context, blendShapeData, newIndex, nodeName); - blendShapeResult = Events::Process(dataPopulated); - if (blendShapeResult != Events::ProcessingResult::Failure) - { - blendShapeResult = AddAttributeDataNodeWithContexts(dataPopulated); - } - combinedBlendShapeResult += blendShapeResult; + // Report problem if no vertex or face converted to MeshData + if (blendShapeData->GetVertexCount() <= 0 || blendShapeData->GetFaceCount() <= 0) + { + AZ_Error(Utilities::ErrorWindow, false, "Missing geometry data in blendshape node %s.", nodeName.c_str()); + return Events::ProcessingResult::Failure; } + Containers::SceneGraph::NodeIndex newIndex = + context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str()); + + Events::ProcessingResult blendShapeResult; + AssImpSceneAttributeDataPopulatedContext dataPopulated(context, blendShapeData, newIndex, nodeName); + blendShapeResult = Events::Process(dataPopulated); + + if (blendShapeResult != Events::ProcessingResult::Failure) + { + blendShapeResult = AddAttributeDataNodeWithContexts(dataPopulated); + } + combinedBlendShapeResult += blendShapeResult; } return combinedBlendShapeResult.GetResult(); diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBoneImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBoneImporter.cpp index 4467d6933b..5b43941715 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBoneImporter.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBoneImporter.cpp @@ -46,8 +46,8 @@ namespace AZ } void EnumBonesInNode( - const aiScene* scene, const aiNode* node, AZStd::unordered_map& mainBoneList, - AZStd::unordered_map& boneLookup) + const aiScene* scene, const aiNode* node, AZStd::unordered_map& mainBoneList, + AZStd::unordered_map& boneLookup) { /* From AssImp Documentation a) Create a map or a similar container to store which nodes are necessary for the skeleton. Pre-initialise it for all nodes with a "no". @@ -62,14 +62,14 @@ namespace AZ for (unsigned meshIndex = 0; meshIndex < node->mNumMeshes; ++meshIndex) { - aiMesh* mesh = scene->mMeshes[node->mMeshes[meshIndex]]; + const aiMesh* mesh = scene->mMeshes[node->mMeshes[meshIndex]]; for (unsigned boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex) { - aiBone* bone = mesh->mBones[boneIndex]; + const aiBone* bone = mesh->mBones[boneIndex]; - aiNode* boneNode = scene->mRootNode->FindNode(bone->mName); - aiNode* boneParent = boneNode->mParent; + const aiNode* boneNode = scene->mRootNode->FindNode(bone->mName); + const aiNode* boneParent = boneNode->mParent; mainBoneList[bone->mName.C_Str()] = boneNode; boneLookup[bone->mName.C_Str()] = bone; @@ -85,8 +85,8 @@ namespace AZ } void EnumChildren( - const aiScene* scene, const aiNode* node, AZStd::unordered_map& mainBoneList, - AZStd::unordered_map& boneLookup) + const aiScene* scene, const aiNode* node, AZStd::unordered_map& mainBoneList, + AZStd::unordered_map& boneLookup) { EnumBonesInNode(scene, node, mainBoneList, boneLookup); @@ -102,7 +102,7 @@ namespace AZ { AZ_TraceContext("Importer", "Bone"); - aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); + const aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiScene* scene = context.m_sourceScene.GetAssImpScene(); if (IsPivotNode(currentNode->mName)) @@ -118,8 +118,8 @@ namespace AZ } else { - AZStd::unordered_map mainBoneList; - AZStd::unordered_map boneLookup; + AZStd::unordered_map mainBoneList; + AZStd::unordered_map boneLookup; EnumChildren(scene, scene->mRootNode, mainBoneList, boneLookup); if (mainBoneList.find(currentNode->mName.C_Str()) != mainBoneList.end()) @@ -172,7 +172,7 @@ namespace AZ } aiMatrix4x4 transform = currentNode->mTransformation; - aiNode* parent = currentNode->mParent; + const aiNode* parent = currentNode->mParent; while (parent) { diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpColorStreamImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpColorStreamImporter.cpp index 7ebdb55363..75fa39105f 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpColorStreamImporter.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpColorStreamImporter.cpp @@ -11,6 +11,7 @@ */ #include +#include #include #include #include @@ -44,7 +45,7 @@ namespace AZ SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) { - serializeContext->Class()->Version(2); // LYN-2576 + serializeContext->Class()->Version(3); // LYN-3250 } } @@ -55,43 +56,64 @@ namespace AZ { return Events::ProcessingResult::Ignored; } - aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); + const aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiScene* scene = context.m_sourceScene.GetAssImpScene(); - GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context)); - if (!meshDataResult.IsSuccess()) + // This node has at least one mesh, verify that the color channel counts are the same for all meshes. + const int expectedColorChannels = scene->mMeshes[currentNode->mMeshes[0]]->GetNumColorChannels(); + const bool allMeshesHaveSameNumberOfColorChannels = + AZStd::all_of(currentNode->mMeshes + 1, currentNode->mMeshes + currentNode->mNumMeshes, [scene, expectedColorChannels](const unsigned int meshIndex) + { + return scene->mMeshes[meshIndex]->GetNumColorChannels() == expectedColorChannels; + }); + + AZ_Error( + Utilities::ErrorWindow, + allMeshesHaveSameNumberOfColorChannels, + "Color channel counts for node %s has meshes with different color channel counts. " + "The color channel count for the first mesh will be used, and placeholder incorrect color values " + "will be generated to allow the data to process, but the source art needs to be fixed to correct this. " + "All meshes on this node should have the same number of color channels.", + currentNode->mName.C_Str()); + + if (expectedColorChannels == 0) { - return meshDataResult.GetError(); - } - const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue()); - - size_t vertexCount = parentMeshData->GetVertexCount(); - - int sdkMeshIndex = parentMeshData->GetSdkMeshIndex(); - if (sdkMeshIndex < 0) - { - AZ_Error(Utilities::ErrorWindow, false, - "Tried to construct color stream attribute for invalid or non-mesh parent data, mesh index is missing"); - return Events::ProcessingResult::Failure; + return Events::ProcessingResult::Ignored; } - aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]]; + const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene); Events::ProcessingResultCombiner combinedVertexColorResults; - for (int colorSetIndex = 0; colorSetIndex < mesh->GetNumColorChannels(); ++colorSetIndex) + for (int colorSetIndex = 0; colorSetIndex < expectedColorChannels; ++colorSetIndex) { + AZStd::shared_ptr vertexColors = AZStd::make_shared(); vertexColors->ReserveContainerSpace(vertexCount); - for (int v = 0; v < mesh->mNumVertices; ++v) + for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex) { - AZ::SceneAPI::DataTypes::Color vertexColor( - AssImpSDKWrapper::AssImpTypeConverter::ToColor(mesh->mColors[colorSetIndex][v])); - vertexColors->AppendColor(vertexColor); + const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]]; + for (int v = 0; v < mesh->mNumVertices; ++v) + { + if (colorSetIndex < mesh->GetNumColorChannels()) + { + AZ::SceneAPI::DataTypes::Color vertexColor( + AssImpSDKWrapper::AssImpTypeConverter::ToColor(mesh->mColors[colorSetIndex][v])); + vertexColors->AppendColor(vertexColor); + } + else + { + // An error was already emitted if this mesh has less color channels + // than other meshes on the parent node. Append an arbitrary color value, fully opaque black, + // so the mesh can still be processed. + // It's better to let the engine load a partially valid mesh than to completely fail. + vertexColors->AppendColor(AZ::SceneAPI::DataTypes::Color(0.0f,0.0f,0.0f,1.0f)); + } + } } - AZStd::string nodeName(AZStd::string::format("%s%d",m_defaultNodeName,colorSetIndex)); + AZStd::string nodeName(AZStd::string::format("%s%d", m_defaultNodeName, colorSetIndex)); Containers::SceneGraph::NodeIndex newIndex = context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str()); @@ -106,9 +128,7 @@ namespace AZ combinedVertexColorResults += colorMapResults; } - return combinedVertexColorResults.GetResult(); - } } // namespace FbxSceneBuilder diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpImporterUtilities.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpImporterUtilities.cpp index 80ac01ebdf..e79eaa09aa 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpImporterUtilities.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpImporterUtilities.cpp @@ -69,7 +69,7 @@ namespace AZ aiMatrix4x4 GetConcatenatedLocalTransform(const aiNode* currentNode) { - aiNode* parent = currentNode->mParent; + const aiNode* parent = currentNode->mParent; aiMatrix4x4 combinedTransform = currentNode->mTransformation; while (parent) diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpMaterialImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpMaterialImporter.cpp index e314804ea1..a912b90e34 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpMaterialImporter.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpMaterialImporter.cpp @@ -62,7 +62,7 @@ namespace AZ for (int idx = 0; idx < context.m_sourceNode.m_assImpNode->mNumMeshes; ++idx) { int meshIndex = context.m_sourceNode.m_assImpNode->mMeshes[idx]; - aiMesh* assImpMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[meshIndex]; + const aiMesh* assImpMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[meshIndex]; AZ_Assert(assImpMesh, "Asset Importer Mesh should not be null."); int materialIndex = assImpMesh->mMaterialIndex; AZ_TraceContext("Material Index", materialIndex); diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpMeshImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpMeshImporter.cpp index cafb96934d..193a1f9fd5 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpMeshImporter.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpMeshImporter.cpp @@ -45,7 +45,7 @@ namespace AZ { AZ_TraceContext("Importer", "Mesh"); - aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); + const aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiScene* scene = context.m_sourceScene.GetAssImpScene(); if (!context.m_sourceNode.ContainsMesh() || IsSkinnedMesh(*currentNode, *scene)) diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinImporter.cpp index 145dc9a457..f4a5fd0f93 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinImporter.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinImporter.cpp @@ -45,7 +45,7 @@ namespace AZ { AZ_TraceContext("Importer", "Skin"); - aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); + const aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiScene* scene = context.m_sourceScene.GetAssImpScene(); if (!context.m_sourceNode.ContainsMesh() || !IsSkinnedMesh(*currentNode, *scene)) diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinWeightsImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinWeightsImporter.cpp index abcbf10b4a..d8503857cc 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinWeightsImporter.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinWeightsImporter.cpp @@ -51,7 +51,7 @@ namespace AZ { AZ_TraceContext("Importer", "Skin Weights"); - aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); + const aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiScene* scene = context.m_sourceScene.GetAssImpScene(); if(currentNode->mNumMeshes <= 0) @@ -59,35 +59,21 @@ namespace AZ return Events::ProcessingResult::Ignored; } - GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context)); - if (!meshDataResult.IsSuccess()) - { - return meshDataResult.GetError(); - } - const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue()); + Events::ProcessingResultCombiner combinedSkinWeightsResult; - int parentMeshIndex = parentMeshData->GetSdkMeshIndex(); + // Don't create this until a bone with weights is encountered + Containers::SceneGraph::NodeIndex weightsIndexForMesh; + AZStd::string skinWeightName; + AZStd::shared_ptr skinWeightData; - Events::ProcessingResultCombiner combinedSkinWeightsResult; + const uint64_t totalVertices = GetVertexCountForAllMeshesOnNode(*currentNode, *scene); + int vertexCount = 0; for(unsigned nodeMeshIndex = 0; nodeMeshIndex < currentNode->mNumMeshes; ++nodeMeshIndex) { - if (nodeMeshIndex != parentMeshIndex) - { - // Only generate skinning data for the parent mesh. - // Each AssImp mesh is assigned to a unique node, - // so the skinning data should be generated as a child node - // for the associated parent mesh. - continue; - } int sceneMeshIndex = currentNode->mMeshes[nodeMeshIndex]; const aiMesh* mesh = scene->mMeshes[sceneMeshIndex]; - // Don't create this until a bone with weights is encountered - Containers::SceneGraph::NodeIndex weightsIndexForMesh; - AZStd::string skinWeightName; - AZStd::shared_ptr skinWeightData; - for(unsigned b = 0; b < mesh->mNumBones; ++b) { const aiBone* bone = mesh->mBones[b]; @@ -100,7 +86,6 @@ namespace AZ if (!weightsIndexForMesh.IsValid()) { skinWeightName = s_skinWeightName; - skinWeightName += AZStd::to_string(nodeMeshIndex); RenamedNodesMap::SanitizeNodeName(skinWeightName, context.m_scene.GetGraph(), context.m_currentGraphPosition); weightsIndexForMesh = @@ -116,23 +101,25 @@ namespace AZ } Pending pending; pending.m_bone = bone; - pending.m_numVertices = mesh->mNumVertices; + pending.m_numVertices = totalVertices; pending.m_skinWeightData = skinWeightData; + pending.m_vertOffset = vertexCount; m_pendingSkinWeights.push_back(pending); } + vertexCount += mesh->mNumVertices; + } - Events::ProcessingResult skinWeightsResult; - AssImpSceneAttributeDataPopulatedContext dataPopulated(context, skinWeightData, weightsIndexForMesh, skinWeightName); - skinWeightsResult = Events::Process(dataPopulated); - - if (skinWeightsResult != Events::ProcessingResult::Failure) - { - skinWeightsResult = AddAttributeDataNodeWithContexts(dataPopulated); - } + Events::ProcessingResult skinWeightsResult; + AssImpSceneAttributeDataPopulatedContext dataPopulated(context, skinWeightData, weightsIndexForMesh, skinWeightName); + skinWeightsResult = Events::Process(dataPopulated); - combinedSkinWeightsResult += skinWeightsResult; + if (skinWeightsResult != Events::ProcessingResult::Failure) + { + skinWeightsResult = AddAttributeDataNodeWithContexts(dataPopulated); } + combinedSkinWeightsResult += skinWeightsResult; + return combinedSkinWeightsResult.GetResult(); } @@ -153,7 +140,7 @@ namespace AZ link.boneId = boneId; link.weight = it.m_bone->mWeights[weight].mWeight; - it.m_skinWeightData->AddAndSortLink(it.m_bone->mWeights[weight].mVertexId, link); + it.m_skinWeightData->AddAndSortLink(it.m_bone->mWeights[weight].mVertexId + it.m_vertOffset, link); } } const auto result = m_pendingSkinWeights.empty() ? Events::ProcessingResult::Ignored : Events::ProcessingResult::Success; diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinWeightsImporter.h b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinWeightsImporter.h index f048fe0af2..655c838701 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinWeightsImporter.h +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinWeightsImporter.h @@ -61,6 +61,7 @@ namespace AZ { const aiBone* m_bone = nullptr; unsigned m_numVertices = 0; + unsigned m_vertOffset = 0; AZStd::shared_ptr m_skinWeightData; }; diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTangentStreamImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTangentStreamImporter.cpp index 992a6a6ab1..47b7e410b4 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTangentStreamImporter.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTangentStreamImporter.cpp @@ -11,6 +11,7 @@ */ #include +#include #include #include #include @@ -44,7 +45,7 @@ namespace AZ SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) { - serializeContext->Class()->Version(2); // LYN-2576 + serializeContext->Class()->Version(3); // LYN-3250 } } @@ -55,62 +56,79 @@ namespace AZ { return Events::ProcessingResult::Ignored; } - aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); + const aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiScene* scene = context.m_sourceScene.GetAssImpScene(); - - GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context)); - if (!meshDataResult.IsSuccess()) + + const auto meshHasTangentsAndBitangents = [&scene](const unsigned int meshIndex) { - return meshDataResult.GetError(); - } - const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue()); - - size_t vertexCount = parentMeshData->GetVertexCount(); + return scene->mMeshes[meshIndex]->HasTangentsAndBitangents(); + }; - int sdkMeshIndex = parentMeshData->GetSdkMeshIndex(); - if (sdkMeshIndex < 0 || sdkMeshIndex >= currentNode->mNumMeshes) + // If there are no tangents on any meshes, there's nothing to import in this function. + const bool anyMeshHasTangentsAndBitangents = AZStd::any_of(currentNode->mMeshes, currentNode->mMeshes + currentNode->mNumMeshes, meshHasTangentsAndBitangents); + if (!anyMeshHasTangentsAndBitangents) { - AZ_Error(Utilities::ErrorWindow, false, - "Tried to construct tangent stream attribute for invalid or non-mesh parent data, mesh index is invalid"); - return Events::ProcessingResult::Failure; + return Events::ProcessingResult::Ignored; } - aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]]; - - if (!mesh->HasTangentsAndBitangents()) + // AssImp nodes with multiple meshes on them occur when AssImp split a mesh on material. + // This logic recombines those meshes to minimize the changes needed to replace FBX SDK with AssImp, FBX SDK did not separate meshes, + // and the engine has code to do this later. + const bool allMeshesHaveTangentsAndBitangents = AZStd::all_of(currentNode->mMeshes, currentNode->mMeshes + currentNode->mNumMeshes, meshHasTangentsAndBitangents); + if (!allMeshesHaveTangentsAndBitangents) { - return Events::ProcessingResult::Ignored; + const char* mixedTangentsError = + "Node with name %s has meshes with and without tangents. " + "Placeholder incorrect tangents will be generated to allow the data to process, " + "but the source art needs to be fixed to correct this. Either apply tangents to all meshes on this node, " + "or remove all tangents from all meshes on this node."; + AZ_Error( + Utilities::ErrorWindow, false, mixedTangentsError, currentNode->mName.C_Str()); } + const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene); + AZStd::shared_ptr tangentStream = AZStd::make_shared(); - // AssImp only has one tangentStream per mesh. tangentStream->SetTangentSetIndex(0); tangentStream->SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace::FromFbx); tangentStream->ReserveContainerSpace(vertexCount); - - for (int v = 0; v < mesh->mNumVertices; ++v) + for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex) { - // Vector4's constructor that takes in a vector3 sets w to 1.0f automatically. - const Vector4 tangent(AssImpSDKWrapper::AssImpTypeConverter::ToVector3(mesh->mTangents[v])); - tangentStream->AppendTangent(tangent); + const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]]; + + for (int v = 0; v < mesh->mNumVertices; ++v) + { + if (!mesh->HasTangentsAndBitangents()) + { + // This node has mixed meshes with and without tangents. + // An error was already thrown above. Output stub tangents so + // the mesh can still be output in some form, even if the data isn't correct. + // The tangent count needs to match the vertex count on the associated mesh node. + tangentStream->AppendTangent(Vector4(0.f, 1.f, 0.f, 1.f)); + } + else + { + const Vector4 tangent( + AssImpSDKWrapper::AssImpTypeConverter::ToVector3(mesh->mTangents[v])); + tangentStream->AppendTangent(tangent); + } + } } - AZStd::string nodeName(AZStd::string::format("%s", m_defaultNodeName)); Containers::SceneGraph::NodeIndex newIndex = - context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str()); + context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, m_defaultNodeName); Events::ProcessingResult tangentResults; - AssImpSceneAttributeDataPopulatedContext dataPopulated(context, tangentStream, newIndex, nodeName.c_str()); + AssImpSceneAttributeDataPopulatedContext dataPopulated(context, tangentStream, newIndex, m_defaultNodeName); tangentResults = Events::Process(dataPopulated); if (tangentResults != Events::ProcessingResult::Failure) { tangentResults = AddAttributeDataNodeWithContexts(dataPopulated); } - return tangentResults; } diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTransformImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTransformImporter.cpp index 84c0e3e18c..5357c32fa9 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTransformImporter.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTransformImporter.cpp @@ -50,7 +50,7 @@ namespace AZ Events::ProcessingResult AssImpTransformImporter::ImportTransform(AssImpSceneNodeAppendedContext& context) { AZ_TraceContext("Importer", "transform"); - aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); + const aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiScene* scene = context.m_sourceScene.GetAssImpScene(); if (currentNode == scene->mRootNode || IsPivotNode(currentNode->mName)) diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpUvMapImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpUvMapImporter.cpp index f5f47b233d..e37a4f4285 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpUvMapImporter.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpUvMapImporter.cpp @@ -12,17 +12,19 @@ #include #include +#include +#include #include #include #include #include #include #include -#include -#include +#include #include #include -#include +#include +#include #include #include @@ -45,7 +47,7 @@ namespace AZ SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) { - serializeContext->Class()->Version(3); // LYN-2506 + serializeContext->Class()->Version(4); // LYN-3250 } } @@ -56,28 +58,53 @@ namespace AZ { return Events::ProcessingResult::Ignored; } - aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); + const aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiScene* scene = context.m_sourceScene.GetAssImpScene(); - GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context)); - if (!meshDataResult.IsSuccess()) + // AssImp separates meshes that have multiple materials. + // This code re-combines them to match previous FBX SDK behavior, + // so they can be separated by engine code instead. + bool foundTextureCoordinates = false; + AZStd::array meshesPerTextureCoordinateIndex = {}; + for (int localMeshIndex = 0; localMeshIndex < currentNode->mNumMeshes; ++localMeshIndex) { - return meshDataResult.GetError(); + aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[localMeshIndex]]; + for (int texCoordIndex = 0; texCoordIndex < meshesPerTextureCoordinateIndex.size(); ++texCoordIndex) + { + if (!mesh->mTextureCoords[texCoordIndex]) + { + continue; + } + ++meshesPerTextureCoordinateIndex[texCoordIndex]; + foundTextureCoordinates = true; + } } - const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue()); - size_t vertexCount = parentMeshData->GetVertexCount(); + if (!foundTextureCoordinates) + { + return Events::ProcessingResult::Ignored; + } - int sdkMeshIndex = parentMeshData->GetSdkMeshIndex(); - AZ_Assert(sdkMeshIndex >= 0, - "Tried to construct uv stream attribute for invalid or non-mesh parent data, mesh index is missing"); + const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene); - aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]]; + for (int texCoordIndex = 0; texCoordIndex < meshesPerTextureCoordinateIndex.size(); ++texCoordIndex) + { + int meshesWithIndex = meshesPerTextureCoordinateIndex[texCoordIndex]; + AZ_Error( + Utilities::ErrorWindow, + meshesWithIndex == 0 || meshesWithIndex == currentNode->mNumMeshes, + "Texture coordinate index %d for node %s is not on all meshes on this node. " + "Placeholder arbitrary texture values will be generated to allow the data to process, but the source art " + "needs to be fixed to correct this. All meshes on this node should have the same number of texture coordinate channels.", + texCoordIndex, + currentNode->mName.C_Str()); + } Events::ProcessingResultCombiner combinedUvMapResults; - for (int texCoordIndex = 0; texCoordIndex < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++texCoordIndex) + for (int texCoordIndex = 0; texCoordIndex < meshesPerTextureCoordinateIndex.size(); ++texCoordIndex) { - if (!mesh->mTextureCoords[texCoordIndex]) + // No meshes have this texture coordinate index, skip it. + if (meshesPerTextureCoordinateIndex[texCoordIndex] == 0) { continue; } @@ -85,24 +112,55 @@ namespace AZ AZStd::shared_ptr uvMap = AZStd::make_shared(); uvMap->ReserveContainerSpace(vertexCount); - + bool customNameFound = false; AZStd::string name(AZStd::string::format("%s%d", m_defaultNodeName, texCoordIndex)); - if (mesh->mTextureCoordsNames[texCoordIndex].length) + for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex) { - name = mesh->mTextureCoordsNames[texCoordIndex].C_Str(); + const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]]; + if(mesh->mTextureCoords[texCoordIndex]) + { + if (mesh->mTextureCoordsNames[texCoordIndex].length > 0) + { + if (!customNameFound) + { + name = mesh->mTextureCoordsNames[texCoordIndex].C_Str(); + customNameFound = true; + } + else + { + AZ_Warning(Utilities::WarningWindow, + strcmp(name.c_str(), mesh->mTextureCoordsNames[texCoordIndex].C_Str()) == 0, + "Node %s has conflicting mesh coordinate names at index %d, %s and %s. Using %s.", + currentNode->mName.C_Str(), + texCoordIndex, + name.c_str(), + mesh->mTextureCoordsNames[texCoordIndex].C_Str(), + name.c_str()); + } + } + } + + for (int v = 0; v < mesh->mNumVertices; ++v) + { + if (mesh->mTextureCoords[texCoordIndex]) + { + AZ::Vector2 vertexUV( + mesh->mTextureCoords[texCoordIndex][v].x, + // The engine's V coordinate is reverse of how it's stored in the FBX file. + 1.0f - mesh->mTextureCoords[texCoordIndex][v].y); + uvMap->AppendUV(vertexUV); + } + else + { + // An error was already emitted if the UV channels for all meshes on this node do not match. + // Append an arbitrary UV value so that the mesh can still be processed. + // It's better to let the engine load a partially valid mesh than to completely fail. + uvMap->AppendUV(AZ::Vector2::CreateZero()); + } + } } uvMap->SetCustomName(name.c_str()); - - for (int v = 0; v < mesh->mNumVertices; ++v) - { - AZ::Vector2 vertexUV( - mesh->mTextureCoords[texCoordIndex][v].x, - // The engine's V coordinate is reverse of how it's stored in the FBX file. - 1.0f - mesh->mTextureCoords[texCoordIndex][v].y); - uvMap->AppendUV(vertexUV); - } - Containers::SceneGraph::NodeIndex newIndex = context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, name.c_str()); @@ -116,6 +174,7 @@ namespace AZ } combinedUvMapResults += uvMapResults; + } return combinedUvMapResults.GetResult(); diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.cpp index 59821336e0..c90fe7d1f3 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -24,7 +25,7 @@ namespace AZ::SceneAPI::FbxSceneBuilder { - bool BuildSceneMeshFromAssImpMesh(aiNode* currentNode, const aiScene* scene, const FbxSceneSystem& sceneSystem, AZStd::vector>& meshes, + bool BuildSceneMeshFromAssImpMesh(const aiNode* currentNode, const aiScene* scene, const FbxSceneSystem& sceneSystem, AZStd::vector>& meshes, const AZStd::function()>& makeMeshFunc) { AZStd::unordered_map assImpMatIndexToLYIndex; @@ -34,17 +35,18 @@ namespace AZ::SceneAPI::FbxSceneBuilder { return false; } + auto newMesh = makeMeshFunc(); + newMesh->SetUnitSizeInMeters(sceneSystem.GetUnitSizeInMeters()); + newMesh->SetOriginalUnitSizeInMeters(sceneSystem.GetOriginalUnitSizeInMeters()); + + // AssImp separates meshes that have multiple materials. + // This code re-combines them to match previous FBX SDK behavior, + // so they can be separated by engine code instead. + int vertOffset = 0; for (int m = 0; m < currentNode->mNumMeshes; ++m) { - auto newMesh = makeMeshFunc(); - - newMesh->SetUnitSizeInMeters(sceneSystem.GetUnitSizeInMeters()); - newMesh->SetOriginalUnitSizeInMeters(sceneSystem.GetOriginalUnitSizeInMeters()); - - newMesh->SetSdkMeshIndex(m); - - aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[m]]; + const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[m]]; // Lumberyard materials are created in order based on mesh references in the scene if (assImpMatIndexToLYIndex.find(mesh->mMaterialIndex) == assImpMatIndexToLYIndex.end()) @@ -59,7 +61,7 @@ namespace AZ::SceneAPI::FbxSceneBuilder sceneSystem.SwapVec3ForUpAxis(vertex); sceneSystem.ConvertUnit(vertex); newMesh->AddPosition(vertex); - newMesh->SetVertexIndexToControlPointIndexMap(vertIdx, vertIdx); + newMesh->SetVertexIndexToControlPointIndexMap(vertIdx + vertOffset, vertIdx + vertOffset); if (mesh->HasNormals()) { @@ -86,14 +88,15 @@ namespace AZ::SceneAPI::FbxSceneBuilder } for (int idx = 0; idx < face.mNumIndices; ++idx) { - meshFace.vertexIndex[idx] = face.mIndices[idx]; + meshFace.vertexIndex[idx] = face.mIndices[idx] + vertOffset; } newMesh->AddFace(meshFace, assImpMatIndexToLYIndex[mesh->mMaterialIndex]); } + vertOffset += mesh->mNumVertices; - meshes.push_back(newMesh); } + meshes.push_back(newMesh); return true; } @@ -127,4 +130,13 @@ namespace AZ::SceneAPI::FbxSceneBuilder azrtti_cast(parentData); return AZ::Success(parentMeshData); } + + uint64_t GetVertexCountForAllMeshesOnNode(const aiNode& node, const aiScene& scene) + { + return AZStd::accumulate(node.mMeshes, node.mMeshes + node.mNumMeshes, uint64_t{ 0u }, + [&scene](auto runningTotal, unsigned int meshIndex) + { + return runningTotal + scene.mMeshes[meshIndex]->mNumVertices; + }); + } } diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h index c0b5c044cb..3c7d3d2102 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h @@ -44,11 +44,16 @@ namespace AZ namespace FbxSceneBuilder { - bool BuildSceneMeshFromAssImpMesh(aiNode* currentNode, const aiScene* scene, const FbxSceneSystem& sceneSystem, AZStd::vector>& meshes, + bool BuildSceneMeshFromAssImpMesh(const aiNode* currentNode, const aiScene* scene, const FbxSceneSystem& sceneSystem, AZStd::vector>& meshes, const AZStd::function()>& makeMeshFunc); typedef AZ::Outcome GetMeshDataFromParentResult; GetMeshDataFromParentResult GetMeshDataFromParent(AssImpSceneNodeAppendedContext& context); + + // If a node in the original scene file has a mesh with multiple materials on it, the associated AssImp + // node will have multiple meshes on it, broken apart per material. This returns the total number + // of vertices on all meshes on the given node. + uint64_t GetVertexCountForAllMeshesOnNode(const aiNode& node, const aiScene& scene); } } } diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/Utilities/RenamedNodesMap.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/Utilities/RenamedNodesMap.cpp index abc095d583..b67696e3cc 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/Utilities/RenamedNodesMap.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/Utilities/RenamedNodesMap.cpp @@ -27,6 +27,7 @@ namespace AZ Containers::SceneGraph::NodeIndex parentNode, const char* defaultName) { AZ_TraceContext("Node name", name); + const AZStd::string originalNodeName(name); bool isNameUpdated = false; // Nodes can't have an empty name, except of the root, otherwise nodes can't be referenced. @@ -56,7 +57,7 @@ namespace AZ // can't reference the same parent in that case. This is to make sure the node can be quickly found as // the full path will be unique. To fix any issues, an index is appended. size_t index = 1; - size_t offset = name.length(); + const size_t offset = name.length(); while (graph.Find(parentNode, name).IsValid()) { // Remove the previously tried extension. @@ -71,7 +72,8 @@ namespace AZ if (isNameUpdated) { AZ_TraceContext("New node name", name); - AZ_TracePrintf(Utilities::WarningWindow, "The name of the node was invalid or conflicting and was updated."); + AZ_TracePrintf(Utilities::WarningWindow, "The name of the node '%s' was invalid or conflicting and was updated to '%s'.", + originalNodeName.c_str(), name.c_str()); } return isNameUpdated; diff --git a/Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp b/Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp index eccbe729c6..2cda1e68ae 100644 --- a/Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp +++ b/Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp @@ -38,12 +38,14 @@ namespace AZ { AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "AssImpSceneWrapper::LoadSceneFromFile %s", fileName); AZ_TraceContext("Filename", fileName); + // aiProcess_JoinIdenticalVertices is not enabled because O3DE has a mesh optimizer that also does this, + // this flag is disabled to keep AssImp output similar to FBX SDK to reduce downstream bugs for the initial AssImp release. + // There's currently a minimum of properties and flags set to maximize compatibility with the existing node graph. m_importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false); m_importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, false); m_sceneFileName = fileName; m_assImpScene = m_importer.ReadFile(fileName, aiProcess_Triangulate //Triangulates all faces of all meshes - | aiProcess_JoinIdenticalVertices //Identifies and joins identical vertex data sets for the imported meshes | aiProcess_LimitBoneWeights //Limits the number of bones that can affect a vertex to a maximum value //dropping the least important and re-normalizing | aiProcess_GenNormals); //Generate normals for meshes diff --git a/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.cpp b/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.cpp index e99788d570..f3c01df2e3 100644 --- a/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.cpp @@ -19,6 +19,16 @@ namespace AZ::SceneAPI::Utilities m_output += AZStd::string::format("\t%s: %s\n", name, data); } + void DebugOutput::WriteArray(const char* name, const unsigned int* data, int size) + { + m_output += AZStd::string::format("\t%s: ", name); + for (int index = 0; index < size; ++index) + { + m_output += AZStd::string::format("%d, ", data[index]); + } + m_output += AZStd::string::format("\n"); + } + void DebugOutput::Write(const char* name, const AZStd::string& data) { Write(name, data.c_str()); diff --git a/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.h b/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.h index a598c996c9..83dc9dd6ab 100644 --- a/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.h +++ b/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.h @@ -29,6 +29,7 @@ namespace AZ::SceneAPI::Utilities void Write(const char* name, const AZStd::vector>& data); SCENE_CORE_API void Write(const char* name, const char* data); + SCENE_CORE_API void WriteArray(const char* name, const unsigned int* data, int size); SCENE_CORE_API void Write(const char* name, const AZStd::string& data); SCENE_CORE_API void Write(const char* name, double data); SCENE_CORE_API void Write(const char* name, uint64_t data); diff --git a/Code/Tools/SceneAPI/SceneData/GraphData/BlendShapeData.cpp b/Code/Tools/SceneAPI/SceneData/GraphData/BlendShapeData.cpp index 902928d404..7d81166829 100644 --- a/Code/Tools/SceneAPI/SceneData/GraphData/BlendShapeData.cpp +++ b/Code/Tools/SceneAPI/SceneData/GraphData/BlendShapeData.cpp @@ -285,8 +285,26 @@ namespace AZ void BlendShapeData::GetDebugOutput(SceneAPI::Utilities::DebugOutput& output) const { output.Write("Positions", m_positions); + int index = 0; + for (const auto& position : m_positions) + { + output.Write(AZStd::string::format("\t%d", index).c_str(), position); + ++index; + } + index = 0; output.Write("Normals", m_normals); + for (const auto& normal : m_normals) + { + output.Write(AZStd::string::format("\t%d", index).c_str(), normal); + ++index; + } + index = 0; output.Write("Faces", m_faces); + for (const auto& face : m_faces) + { + output.WriteArray(AZStd::string::format("\t%d", index).c_str(), face.vertexIndex, 3); + ++index; + } } } // GraphData } // SceneData diff --git a/Code/Tools/SceneAPI/SceneData/GraphData/MeshData.cpp b/Code/Tools/SceneAPI/SceneData/GraphData/MeshData.cpp index 8bf49b2898..8f464240c9 100644 --- a/Code/Tools/SceneAPI/SceneData/GraphData/MeshData.cpp +++ b/Code/Tools/SceneAPI/SceneData/GraphData/MeshData.cpp @@ -45,7 +45,6 @@ namespace AZ behaviorContext->Class() ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) ->Attribute(AZ::Script::Attributes::Module, "scene") - ->Method("GetSdkMeshIndex", &MeshData::GetSdkMeshIndex) ->Method("GetControlPointIndex", &MeshData::GetControlPointIndex) ->Method("GetUsedControlPointCount", &MeshData::GetUsedControlPointCount) ->Method("GetUsedPointIndexForControlPoint", &MeshData::GetUsedPointIndexForControlPoint) @@ -77,10 +76,6 @@ namespace AZ void MeshData::CloneAttributesFrom(const IGraphObject* sourceObject) { IMeshData::CloneAttributesFrom(sourceObject); - if (const auto* typedSource = azrtti_cast(sourceObject)) - { - SetSdkMeshIndex(typedSource->GetSdkMeshIndex()); - } } void MeshData::AddPosition(const AZ::Vector3& position) @@ -111,15 +106,6 @@ namespace AZ m_faceMaterialIds.push_back(faceMaterialId); } - void MeshData::SetSdkMeshIndex(int sdkMeshIndex) - { - m_sdkMeshIndex = sdkMeshIndex; - } - int MeshData::GetSdkMeshIndex() const - { - return m_sdkMeshIndex; - } - void MeshData::SetVertexIndexToControlPointIndexMap(int vertexIndex, int controlPointIndex) { m_vertexIndexToControlPointIndexMap[vertexIndex] = controlPointIndex; @@ -206,8 +192,26 @@ namespace AZ void MeshData::GetDebugOutput(SceneAPI::Utilities::DebugOutput& output) const { output.Write("Positions", m_positions); + int index = 0; + for (const auto& position : m_positions) + { + output.Write(AZStd::string::format("\t%d", index).c_str(), position); + ++index; + } + index = 0; output.Write("Normals", m_normals); + for (const auto& normal : m_normals) + { + output.Write(AZStd::string::format("\t%d", index).c_str(), normal); + ++index; + } + index = 0; output.Write("FaceList", m_faceList); + for (const auto& face : m_faceList) + { + output.WriteArray(AZStd::string::format("\t%d", index).c_str(), face.vertexIndex, 3); + ++index; + } output.Write("FaceMaterialIds", m_faceMaterialIds); } } diff --git a/Code/Tools/SceneAPI/SceneData/GraphData/MeshData.h b/Code/Tools/SceneAPI/SceneData/GraphData/MeshData.h index c5197d6cb4..4321096857 100644 --- a/Code/Tools/SceneAPI/SceneData/GraphData/MeshData.h +++ b/Code/Tools/SceneAPI/SceneData/GraphData/MeshData.h @@ -49,9 +49,6 @@ namespace AZ SCENE_DATA_API void AddFace(const AZ::SceneAPI::DataTypes::IMeshData::Face& face, unsigned int faceMaterialId = AZ::SceneAPI::DataTypes::IMeshData::s_invalidMaterialId); - SCENE_DATA_API void SetSdkMeshIndex(int sdkMeshIndex); - SCENE_DATA_API int GetSdkMeshIndex() const; - SCENE_DATA_API void SetVertexIndexToControlPointIndexMap(int vertexIndex, int controlPointIndex); SCENE_DATA_API size_t GetUsedControlPointCount() const override; SCENE_DATA_API int GetControlPointIndex(int vertexIndex) const override; @@ -80,8 +77,6 @@ namespace AZ AZStd::unordered_map m_vertexIndexToControlPointIndexMap; AZStd::unordered_map m_controlPointToUsedVertexIndexMap; - - int m_sdkMeshIndex = -1; }; } } diff --git a/Code/Tools/SceneAPI/SceneData/Tests/GraphData/GraphDataBehaviorTests.cpp b/Code/Tools/SceneAPI/SceneData/Tests/GraphData/GraphDataBehaviorTests.cpp index 79c6cfea7e..84018bbc9a 100644 --- a/Code/Tools/SceneAPI/SceneData/Tests/GraphData/GraphDataBehaviorTests.cpp +++ b/Code/Tools/SceneAPI/SceneData/Tests/GraphData/GraphDataBehaviorTests.cpp @@ -57,7 +57,6 @@ namespace AZ meshData->AddNormal(Vector3{0.1f, 0.2f, 0.3f}); meshData->AddNormal(Vector3{0.4f, 0.5f, 0.6f}); meshData->SetOriginalUnitSizeInMeters(10.0f); - meshData->SetSdkMeshIndex(1337); meshData->SetUnitSizeInMeters(0.5f); meshData->SetVertexIndexToControlPointIndexMap(0, 10); meshData->SetVertexIndexToControlPointIndexMap(1, 11); @@ -252,7 +251,6 @@ namespace AZ ExpectExecute("TestExpectFloatEquals(meshData:GetNormal(1).z, 0.6)"); ExpectExecute("TestExpectFloatEquals(meshData:GetOriginalUnitSizeInMeters(), 10.0)"); ExpectExecute("TestExpectFloatEquals(meshData:GetUnitSizeInMeters(), 0.5)"); - ExpectExecute("TestExpectIntegerEquals(meshData:GetSdkMeshIndex(), 1337)"); ExpectExecute("TestExpectIntegerEquals(meshData:GetUsedControlPointCount(), 4)"); ExpectExecute("TestExpectIntegerEquals(meshData:GetControlPointIndex(0), 10)"); ExpectExecute("TestExpectIntegerEquals(meshData:GetControlPointIndex(1), 11)"); diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslBuilder.cpp index 0ce7109067..668d6866d3 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslBuilder.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslBuilder.cpp @@ -185,7 +185,8 @@ namespace AZ // we can't use a temporary folder because CreateJobs API does not warrant side effects, and does not prepare a temp folder. // we can't use the OS temp folder anyway, because many includes (eg #include "../RPI/Shadow.h") are relative and will only work from the original location AZStd::string prependedPath = ShaderBuilderUtility::DumpAzslPrependedCode( - BuilderName, prependedAzslSourceCode, originalLocation, ShaderBuilderUtility::ExtractStemName(fullPath.c_str()), shaderPlatformInterface->GetAPIName().GetStringView()); + BuilderName, prependedAzslSourceCode, originalLocation, ShaderBuilderUtility::ExtractStemName(fullPath.c_str()), + shaderPlatformInterface->GetAPIName().GetStringView()); // run mcpp PreprocessorData preprocessorData = PreprocessSource(prependedPath, fullPath, buildOptions.m_preprocessorSettings); jobDescriptor.m_jobParameters[(u32)JobParameterIndices::PreprocessorError] = preprocessorData.diagnostics; // save for ProcessJob @@ -221,7 +222,7 @@ namespace AZ } // eg: ("D:/p/x.a", "D:/p/x.b") -> yes - static bool HasSameStemName(const AZStd::string& lhsPath, const AZStd::string& rhsPath) + static bool HasSameFileName(const AZStd::string& lhsPath, const AZStd::string& rhsPath) { using namespace StringFunc::Path; AZStd::string stem1; @@ -307,7 +308,8 @@ namespace AZ buildOptions.m_compilerArguments.Merge(shaderAssetSource.m_compiler); // Earlier, we declared a job dependency on the .azsl's job, let's access the produced assets: - uint32_t subId = ShaderBuilderUtility::MakeAzslBuildProductSubId(RPI::ShaderAssetSubId::GeneratedSource, platformInterface->GetAPIType()); + uint32_t subId = ShaderBuilderUtility::MakeAzslBuildProductSubId( + RPI::ShaderAssetSubId::GeneratedHlslSource, platformInterface->GetAPIType()); auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(inputFiles->m_azslSourceFullPath, subId); AZ_Warning(BuilderName, assetIdOutcome.IsSuccess(), "Product of dependency %s not found: this is an oddity but build can continue.", inputFiles->m_azslSourceFullPath.c_str()); if (assetIdOutcome.IsSuccess()) @@ -325,7 +327,7 @@ namespace AZ AZ_TracePrintf(BuilderName, "Product output already built by %s is not reusable because of incompatible azslc CompilerHints: launching independent build", inputFiles->m_azslSourceFullPath.c_str()); } - if (HasSameStemName(fullSourcePath, inputFiles->m_azslSourceFullPath)) + if (HasSameFileName(fullSourcePath, inputFiles->m_azslSourceFullPath)) { // let's add a "distinguisher" to the names of the outproduct artifacts of this build round.* // Because otherwise the asset processor is not going to accept an overwrite of the ones output by the .azsl job diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.cpp index 417a5b4a88..e3b482742b 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.cpp @@ -26,6 +26,7 @@ #include #include +#include // [GFX TODO] Remove when [ATOM-15472] #include #include @@ -122,7 +123,7 @@ namespace AZ namespace SubProducts = ShaderBuilderUtility::AzslSubProducts; - Outcome AzslCompiler::EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile /* = ""*/) const + Outcome AzslCompiler::EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile /* = ""*/, const char * addSuffix) const { bool success = Compile("--full " + parameters, outputFile); if (!success) @@ -133,11 +134,22 @@ namespace AZ SubProducts::Paths productPaths = SubProducts::Paths(SubProducts::Paths::capacity()); for (auto subProduct : SubProducts::SuffixListMembers) { - productPaths[subProduct.m_value] = outputFile.empty() ? m_inputFilePath : outputFile; // that's a reproduction of azslc's behavior (no "-o" = input name is used) - AzFramework::StringFunc::Path::ReplaceExtension(productPaths[subProduct.m_value], subProduct.m_string.data()); + AZStd::string subProductFilePath = outputFile.empty() ? m_inputFilePath : outputFile; // that's a reproduction of azslc's behavior (no "-o" = input name is used) + AzFramework::StringFunc::Path::ReplaceExtension(subProductFilePath, subProduct.m_string.data()); // append .json if it's one of those subs: auto listOfJsons = { SubProducts::ia, SubProducts::om, SubProducts::srg, SubProducts::options, SubProducts::bindingdep }; - productPaths[subProduct.m_value] += AZStd::any_of(AZ_BEGIN_END(listOfJsons), [&](auto v) { return v == subProduct.m_value; }) ? ".json" : ""; + subProductFilePath += AZStd::any_of(AZ_BEGIN_END(listOfJsons), [&](auto v) { return v == subProduct.m_value; }) ? ".json" : ""; + + // [GFX TODO] Remove when [ATOM-15472] + if (addSuffix) + { + // Rename the product file. + AZStd::string finalSubProductFilePath = AZStd::string::format("%s%s", subProductFilePath.c_str(), addSuffix); + AZ::IO::Move(subProductFilePath.c_str(), finalSubProductFilePath.c_str()); + subProductFilePath = finalSubProductFilePath; + } + + productPaths[subProduct.m_value] = subProductFilePath; } productPaths[SubProducts::azslin] = GetInputFilePath(); // post-fixup this one after the loop, because it's not an output of azslc, it's an output of the builder though. return { productPaths }; diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.h index cb30da068f..8da03cd6e5 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.h +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.h @@ -38,8 +38,9 @@ namespace AZ //! @param inputFilePath The target input file to compile. Should be a valid AZSL file with no preprocessing directives. AzslCompiler(const AZStd::string& inputFilePath); + //! [GFX TODO] Remove @addSuffix when [ATOM-15472] //! compile with --full and generate all .json files - Outcome EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile = "") const; + Outcome EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile = "", const char * addSuffix = nullptr) const; //! compile to HLSL independently bool EmitShader(AZ::IO::GenericStream& outputStream, const AZStd::string& extraCompilerParams) const; //! compile with --ia independently and populate document @output diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslData.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslData.h index 4ea7a51fb1..303e2f355f 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslData.h +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslData.h @@ -83,23 +83,41 @@ namespace AZ AZStd::string m_azslFileName; //!< Name for the source .azsl file }; - struct AzslCodeTopData + + //! DEPRECATED [ATOM-15472] + //! This class is used to collect all the json files produced by the compilation + //! of an AZSL file as objects. + struct AzslData { + AzslData(const AZStd::shared_ptr& a_sources) : m_sources(a_sources) { } + + AZStd::shared_ptr m_sources; + AZStd::string m_preprocessedFullPath; // Full path to a preprocessed version of the original AZSL file + AZStd::string m_shaderCodePrefix; // AssetProcessor generated shader code which is added to the + // AZSLc emitted code prior to invoking the native shader compiler + SrgDataContainer m_srgData; - AzslFunctions m_functions; - StructContainer m_structs; + AzslFunctions m_functions; + StructContainer m_structs; RootConstantData m_rootConstantData; }; - struct AzslData + //! This class is used to collect all the json files produced by the compilation + //! of an AZSL file as objects. + struct AzslData2 { - AzslData(const AZStd::shared_ptr& a_sources) : m_sources(a_sources) { } + AzslData2(const AZStd::shared_ptr& a_sources) + : m_sources(a_sources) + { + } AZStd::shared_ptr m_sources; - AZStd::string m_preprocessedFullPath; // Full path to a preprocessed version of the original AZSL file - AZStd::string m_shaderCodePrefix; // AssetProcessor generated shader code which is added to the - // AZSLc emitted code prior to invoking the native shader compiler - AzslCodeTopData m_topData; + AZStd::string m_preprocessedFullPath; // Full path to a preprocessed version of the original AZSL file + + SrgDataContainer m_srgData; + AzslFunctions m_functions; + StructContainer m_structs; + RootConstantData m_rootConstantData; }; } // ShaderBuilder } // AZ diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp index a0f9f7db22..fb4d370621 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp @@ -86,7 +86,7 @@ namespace AZ // Register AZSL's compilation products Builder AssetBuilderSDK::AssetBuilderDesc azslBuilderDescriptor; azslBuilderDescriptor.m_name = "AZSL Builder"; - azslBuilderDescriptor.m_version = 7; // LKG Merge + azslBuilderDescriptor.m_version = 8; // ATOM-15276 // register all extensions thay may carry azsl code. header. main shader. or SRG azslBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); azslBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsl", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); @@ -102,7 +102,7 @@ namespace AZ // Register Shader Resource Group Layout Builder AssetBuilderSDK::AssetBuilderDesc srgLayoutBuilderDescriptor; srgLayoutBuilderDescriptor.m_name = "Shader Resource Group Layout Builder"; - srgLayoutBuilderDescriptor.m_version = 54; // Enable Null Rhi for AutomatedTesting + srgLayoutBuilderDescriptor.m_version = 55; // ATOM-15276 srgLayoutBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsl", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); srgLayoutBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsli", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); @@ -118,7 +118,7 @@ namespace AZ // Register Shader Asset Builder AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor; shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder"; - shaderAssetBuilderDescriptor.m_version = 98; // Enable Null Rhi for AutomatedTesting + shaderAssetBuilderDescriptor.m_version = 99; // ATOM-15276 // .shader file changes trigger rebuilds shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); shaderAssetBuilderDescriptor.m_busId = azrtti_typeid(); @@ -133,7 +133,7 @@ namespace AZ shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder"; // Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update // ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder". - shaderVariantAssetBuilderDescriptor.m_version = 19; // Enable Null Rhi for AutomatedTesting + shaderVariantAssetBuilderDescriptor.m_version = 20; // ATOM-15276 shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid(); shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); @@ -145,7 +145,7 @@ namespace AZ // Register Precompiled Shader Builder AssetBuilderSDK::AssetBuilderDesc precompiledShaderBuilderDescriptor; precompiledShaderBuilderDescriptor.m_name = "Precompiled Shader Builder"; - precompiledShaderBuilderDescriptor.m_version = 7; // ATOM-14780 + precompiledShaderBuilderDescriptor.m_version = 8; // ATOM-15276 precompiledShaderBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", AZ::PrecompiledShaderBuilder::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); precompiledShaderBuilderDescriptor.m_busId = azrtti_typeid(); precompiledShaderBuilderDescriptor.m_createJobFunction = AZStd::bind(&PrecompiledShaderBuilder::CreateJobs, &m_precompiledShaderBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); @@ -153,6 +153,43 @@ namespace AZ m_precompiledShaderBuilder.BusConnect(precompiledShaderBuilderDescriptor.m_busId); AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, precompiledShaderBuilderDescriptor); + + // Register Shader Asset Builder 2 + AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilder2Descriptor; + shaderAssetBuilder2Descriptor.m_name = "Shader Asset Builder 2"; + shaderAssetBuilder2Descriptor.m_version = 1; // ATOM-15276 + // .shader2 file changes trigger rebuilds + shaderAssetBuilder2Descriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( + AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension2), + AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); + shaderAssetBuilder2Descriptor.m_busId = azrtti_typeid(); + shaderAssetBuilder2Descriptor.m_createJobFunction = + AZStd::bind(&ShaderAssetBuilder2::CreateJobs, &m_shaderAssetBuilder2, AZStd::placeholders::_1, AZStd::placeholders::_2); + shaderAssetBuilder2Descriptor.m_processJobFunction = + AZStd::bind(&ShaderAssetBuilder2::ProcessJob, &m_shaderAssetBuilder2, AZStd::placeholders::_1, AZStd::placeholders::_2); + + m_shaderAssetBuilder2.BusConnect(shaderAssetBuilder2Descriptor.m_busId); + AssetBuilderSDK::AssetBuilderBus::Broadcast( + &AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderAssetBuilder2Descriptor); + + // Register Shader Variant Asset Builder 2 + AssetBuilderSDK::AssetBuilderDesc shaderVariantAssetBuilder2Descriptor; + shaderVariantAssetBuilder2Descriptor.m_name = "Shader Variant Asset Builder 2"; + // Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update + // ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder". + shaderVariantAssetBuilder2Descriptor.m_version = 1; // ATOM-15276 + shaderVariantAssetBuilder2Descriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( + AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension2), + AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); + shaderVariantAssetBuilder2Descriptor.m_busId = azrtti_typeid(); + shaderVariantAssetBuilder2Descriptor.m_createJobFunction = AZStd::bind( + &ShaderVariantAssetBuilder2::CreateJobs, &m_shaderVariantAssetBuilder2, AZStd::placeholders::_1, AZStd::placeholders::_2); + shaderVariantAssetBuilder2Descriptor.m_processJobFunction = AZStd::bind( + &ShaderVariantAssetBuilder2::ProcessJob, &m_shaderVariantAssetBuilder2, AZStd::placeholders::_1, AZStd::placeholders::_2); + + m_shaderVariantAssetBuilder2.BusConnect(shaderVariantAssetBuilder2Descriptor.m_busId); + AssetBuilderSDK::AssetBuilderBus::Broadcast( + &AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderVariantAssetBuilder2Descriptor); } void AzslShaderBuilderSystemComponent::Deactivate() @@ -161,6 +198,8 @@ namespace AZ m_srgLayoutBuilder.BusDisconnect(); m_shaderVariantAssetBuilder.BusDisconnect(); m_precompiledShaderBuilder.BusDisconnect(); + m_shaderAssetBuilder2.BusDisconnect(); + m_shaderVariantAssetBuilder2.BusDisconnect(); RHI::ShaderPlatformInterfaceRegisterBus::Handler::BusDisconnect(); ShaderPlatformInterfaceRequestBus::Handler::BusDisconnect(); diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.h index 9d685c0d12..2f881f45d2 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.h +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.h @@ -18,12 +18,14 @@ #include -#include -#include -#include -#include -#include -#include +#include "AzslBuilder.h" +#include "SrgLayoutBuilder.h" +#include "ShaderAssetBuilder.h" +#include "ShaderVariantAssetBuilder.h" +#include "PrecompiledShaderBuilder.h" +#include "ShaderPlatformInterfaceRequest.h" +#include "ShaderAssetBuilder2.h" +#include "ShaderVariantAssetBuilder2.h" namespace AZ { @@ -71,6 +73,8 @@ namespace AZ ShaderAssetBuilder m_shaderAssetBuilder; ShaderVariantAssetBuilder m_shaderVariantAssetBuilder; PrecompiledShaderBuilder m_precompiledShaderBuilder; + ShaderAssetBuilder2 m_shaderAssetBuilder2; + ShaderVariantAssetBuilder2 m_shaderVariantAssetBuilder2; /// Contains the ShaderPlatformInterface for all registered RHIs AZStd::unordered_map m_shaderPlatformInterfaces; diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/GlobalBuildOptions.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/GlobalBuildOptions.cpp index c9d23aecd1..5f2acf251c 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/GlobalBuildOptions.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/GlobalBuildOptions.cpp @@ -62,7 +62,7 @@ namespace AZ } } - GlobalBuildOptions ReadBuildOptions(const char* builderName) + GlobalBuildOptions ReadBuildOptions(const char* builderName, const char* optionalIncludeFolder) { GlobalBuildOptions output; // try to parse some config file for eventual additional options @@ -79,7 +79,7 @@ namespace AZ { AZ_TracePrintf(builderName, "config file [%s] not found.", globalBuildOption.c_str()); } - InitializePreprocessorOptions(output.m_preprocessorSettings, builderName); + InitializePreprocessorOptions(output.m_preprocessorSettings, builderName, optionalIncludeFolder); return output; } } diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/GlobalBuildOptions.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/GlobalBuildOptions.h index f55d395609..9e85f5bcdd 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/GlobalBuildOptions.h +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/GlobalBuildOptions.h @@ -33,6 +33,9 @@ namespace AZ RHI::ShaderCompilerArguments m_compilerArguments; }; - GlobalBuildOptions ReadBuildOptions(const char* builderName); + //! Reads the global options used when compiling shaders. The options are defined in /Config/shader_global_build_options.json + //! @param builderName: A string with the name of the builder calling this API. Used for trace debugging. + //! @param optionalIncludeFolder: An additional directory to add to the list of include folders for the C-preprocessor. + GlobalBuildOptions ReadBuildOptions(const char* builderName, const char* optionalIncludeFolder = nullptr); } } diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/Preprocessor.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/Preprocessor.cpp index 84a95b2c54..1e471f8644 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/Preprocessor.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/Preprocessor.cpp @@ -58,6 +58,37 @@ namespace AZ } } + void PreprocessorOptions::RemovePredefinedMacros(const AZStd::vector& macroNames) + { + m_predefinedMacros.erase( + AZStd::remove_if( + m_predefinedMacros.begin(), m_predefinedMacros.end(), + [&](const AZStd::string& predefinedMacro) + { + for (const auto& macroName : macroNames) + { + // Haystack, needle, bCaseSensitive + if (!AzFramework::StringFunc::StartsWith(predefinedMacro, macroName, true)) + { + return false; + } + // If found, let's make sure it is not just a substring. + if (predefinedMacro.size() == macroName.size()) + { + return true; + } + // The predefinedMacro can be a string like "macro=value". If we find '=' it is a match. + if (predefinedMacro.c_str()[macroName.size()] == '=') + { + return true; + } + return false; + } + return false; + }), + m_predefinedMacros.end()); + } + //! Binder helper to Matsui C-Pre-Processor library class McppBinder { @@ -286,7 +317,8 @@ namespace AZ } // populate options with scan folders and contents of parsing shader_global_build_options.json - void InitializePreprocessorOptions(PreprocessorOptions& options, [[maybe_unused]] const char* builderName) + void InitializePreprocessorOptions( + PreprocessorOptions& options, [[maybe_unused]] const char* builderName, const char* optionalIncludeFolder) { AZ_TraceContext("Init include-paths lookup options", "preprocessor"); @@ -303,6 +335,10 @@ namespace AZ // Add the project path to list of include paths AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath(); scanFoldersSet.emplace(projectPath.c_str(), projectPath.size()); + if (optionalIncludeFolder) + { + scanFoldersSet.emplace(optionalIncludeFolder, strnlen(optionalIncludeFolder, AZ::IO::MaxPathLength)); + } // but while we transfer to the set, we're going to keep only folders where +/ShaderLib exists for (AZStd::string folder : scanFoldersVector) { diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/Preprocessor.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/Preprocessor.h index 7718dba6c4..b5fbf438a3 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/Preprocessor.h +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/Preprocessor.h @@ -47,9 +47,13 @@ namespace AZ //! folders are relative to the dev folder of the project AZStd::vector m_projectIncludePaths; - //! passed as -D macro1[=value1] -D macro2 ... + //! Each string is of the type "name[=value]" + //! passed as -Dmacro1[=value1] -Dmacro2 ... to MCPP. AZStd::vector m_predefinedMacros; + //! Removes all macros from @m_predefinedMacros that appear in @macroNames + void RemovePredefinedMacros(const AZStd::vector& macroNames); + //! if needed, we may add configurations like //! "keep comments" or "don't predefine non-standard macros" //! or "output diagnostics to std.err" or "enable digraphs/trigraphs"... @@ -59,7 +63,10 @@ namespace AZ //! It will populate your option with a default base of include folders given by the Asset Processor scan folders. //! This is going to look for a Config/shader_global_build_options.json in one of the scan folders //! (that file can specify additional include files and preprocessor macros). - void InitializePreprocessorOptions(PreprocessorOptions& options, const char* builderName); + //! @param options: Outout parameter, will contain the preprocessor options. + //! @param builderName: Used for debugging. + //! @param optionalIncludeFolder: If not null, will be added to the list of include folders for the c-preprocessor in @options. + void InitializePreprocessorOptions(PreprocessorOptions& options, const char* builderName, const char* optionalIncludeFolder = nullptr); /** * Runs the preprocessor on the given source file path, and stores results in outputData. diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp index d6197ff279..8d491d5d4d 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp @@ -166,17 +166,6 @@ namespace AZ response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success; } - static uint32_t GetRootVariantAssetSubId(const RHI::ShaderPlatformInterface& shaderPlatformInterface) - { - //The 2 Most significant bits encode the the RHI::API unique index. - const uint32_t apiUniqueIndex = shaderPlatformInterface.GetAPIUniqueIndex(); - AZ_Assert(apiUniqueIndex <= RHI::Limits::APIType::PerPlatformApiUniqueIndexMax, - "Invalid api unique index [%u] from ShaderPlatformInterface [%s]", apiUniqueIndex, shaderPlatformInterface.GetAPIName().GetCStr()); - const uint32_t rhiApiSubId = apiUniqueIndex << 30; - const uint32_t productSubID = rhiApiSubId | static_cast(RPI::ShaderAssetSubId::RootShaderVariantAsset); - return productSubID; - } - static AssetBuilderSDK::ProcessJobResultCode CompileForAPI( const ShaderBuilderUtility::AzslSubProducts::Paths& pathOfProductFiles, RPI::ShaderAssetCreator& shaderAssetCreator, @@ -201,7 +190,7 @@ namespace AZ if (shaderSourceDataDescriptor.m_programSettings.m_entryPoints.empty()) { AZ_TracePrintf(ShaderAssetBuilderName, "ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n"); - ShaderVariantAssetBuilder::GetDefaultEntryPointsFromAzslData(azslData, shaderEntryPoints); + ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslData.m_functions, shaderEntryPoints); } else { @@ -249,7 +238,9 @@ namespace AZ // so the root ShaderVariantAsset is found when the ShaderAsset is deserialized. AZStd::string fullSourcePath; AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), fullSourcePath, true); - const uint32_t productSubID = GetRootVariantAssetSubId(*shaderPlatformInterface); + const uint32_t productSubID = RPI::ShaderAsset::MakeAssetProductSubId( + shaderPlatformInterface->GetAPIUniqueIndex(), + aznumeric_cast(RPI::ShaderAssetSubId::RootShaderVariantAsset)); auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(fullSourcePath, productSubID); AZ_Assert(assetIdOutcome.IsSuccess(), "Failed to get AssetId from shader %s", fullSourcePath.c_str()); const Data::AssetId variantAssetId = assetIdOutcome.TakeValue(); @@ -288,12 +279,13 @@ namespace AZ // add byproducts as job output products: if (variantCreationContext.m_outputByproducts) { + uint32_t subProductType = aznumeric_cast(RPI::ShaderAssetSubId::GeneratedHlslSource) + 1; for (const AZStd::string& byproduct : variantCreationContext.m_outputByproducts->m_intermediatePaths) { AssetBuilderSDK::JobProduct jobProduct; jobProduct.m_productFileName = byproduct; jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt"); - jobProduct.m_productSubID = ShaderBuilderUtility::MakeDebugByproductSubId(shaderPlatformInterface->GetAPIType(), byproduct); + jobProduct.m_productSubID = RPI::ShaderAsset::MakeAssetProductSubId(shaderPlatformInterface->GetAPIUniqueIndex(), subProductType++); response.m_outputProducts.push_back(AZStd::move(jobProduct)); } } @@ -305,12 +297,12 @@ namespace AZ attributeMaps.resize(RHI::ShaderStageCount); for (const auto& shaderEntry : shaderSourceDataDescriptor.m_programSettings.m_entryPoints) { - auto findId = AZStd::find_if(AZ_BEGIN_END(azslData.m_topData.m_functions), [&shaderEntry](const auto& func) + auto findId = AZStd::find_if(AZ_BEGIN_END(azslData.m_functions), [&shaderEntry](const auto& func) { return func.m_name == shaderEntry.m_name; }); - if (findId == azslData.m_topData.m_functions.end()) + if (findId == azslData.m_functions.end()) { // shaderData.m_functions only contains Vertex, Fragment and Compute entries for now // Tessellation shaders will need to be handled too diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder2.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder2.cpp new file mode 100644 index 0000000000..1668b57866 --- /dev/null +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder2.cpp @@ -0,0 +1,684 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include "ShaderAssetBuilder2.h" + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AzslBuilder.h" +#include "ShaderVariantAssetBuilder2.h" +#include "ShaderBuilderUtility.h" +#include "ShaderPlatformInterfaceRequest.h" +#include "AtomShaderConfig.h" + +#include +#include +namespace AZ +{ + namespace ShaderBuilder + { + static constexpr char ShaderAssetBuilder2Name[] = "ShaderAssetBuilder2"; + static constexpr uint32_t ShaderAssetBuildTimestampParam = 0; + + void ShaderAssetBuilder2::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const + { + AZStd::string fullPath; + AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullPath, true); + + AZ_TracePrintf(ShaderAssetBuilder2Name, "CreateJobs for Shader \"%s\"\n", fullPath.data()); + + // Used to synchronize versions of the ShaderAsset and ShaderVariantTreeAsset, especially during hot-reload. + // Note it's probably important for this to be set once outside the platform loop so every platform's ShaderAsset + // has the same value, because later the ShaderVariantTreeAsset job will fetch this value from the local ShaderAsset + // which could cross platforms (i.e. building an android ShaderVariantTreeAsset on PC would fetch the tiemstamp from + // the PC's ShaderAsset). + AZStd::sys_time_t shaderAssetBuildTimestamp = AZStd::GetTimeNowMicroSecond(); + + // Need to get the name of the azsl file from the .shader source asset, to be able to declare a dependency to SRG Layout Job. + // and the macro options to preprocess. + auto descriptorParseOutcome = ShaderBuilderUtility::LoadShaderDataJson(fullPath); + if (!descriptorParseOutcome.IsSuccess()) + { + AZ_Error( + ShaderAssetBuilder2Name, false, "Failed to parse Shader Descriptor JSON: %s", + descriptorParseOutcome.GetError().c_str()); + return; + } + + RPI::ShaderSourceData shaderSourceData = descriptorParseOutcome.TakeValue(); + + AZStd::string azslFullPath; + ShaderBuilderUtility::GetAbsolutePathToAzslFile(fullPath, shaderSourceData.m_source, azslFullPath); + if (!IO::FileIOBase::GetInstance()->Exists(azslFullPath.c_str())) + { + AZ_Error( + ShaderAssetBuilder2Name, false, "Shader program listed as the source entry does not exist: %s.", azslFullPath.c_str()); + response.m_result = AssetBuilderSDK::CreateJobsResultCode::Failed; + return; + } + + + GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderAssetBuilder2Name); + + // [GFX TODO] [ATOM-14966] In principle, based on macro definitions, included files can change per supervariant. + // So, the list of source asset dependencies must be collected by running MCPP on each supervariant. + // For now, we will run MCPP only once because CreateJobs() should be as light as possible. + // + // Regardless of the PlatformInfo and enabled ShaderPlatformInterfaces, the azsl file will be preprocessed + // with the sole purpose of extracting all included files. For each included file a SourceDependency will be declared. + PreprocessorData output; + buildOptions.m_compilerArguments.Merge(shaderSourceData.m_compiler); + PreprocessFile(azslFullPath, output, buildOptions.m_preprocessorSettings, true, true); + for (auto includePath : output.includedPaths) + { + // m_sourceFileDependencyList does not support paths with "." or ".." for relative lookup, but the preprocessor + // may produce path strings like "C:/a/b/c/../../d/file.azsli" so we have to normalize + AzFramework::StringFunc::Path::Normalize(includePath); + + AssetBuilderSDK::SourceFileDependency includeFileDependency; + includeFileDependency.m_sourceFileDependencyPath = includePath; + response.m_sourceFileDependencyList.emplace_back(includeFileDependency); + } + + { + // Add the AZSL as source dependency + AssetBuilderSDK::SourceFileDependency azslFileDependency; + azslFileDependency.m_sourceFileDependencyPath = azslFullPath; + response.m_sourceFileDependencyList.emplace_back(azslFileDependency); + } + + for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms) + { + AZ_TraceContext("For platform", platformInfo.m_identifier.data()); + + // Get the platform interfaces to be able to access the prepend file + AZStd::vector platformInterfaces = ShaderBuilderUtility::DiscoverValidShaderPlatformInterfaces(platformInfo); + if (platformInterfaces.empty()) + { + continue; + } + + AssetBuilderSDK::JobDescriptor jobDescriptor; + jobDescriptor.m_priority = 2; + // [GFX TODO][ATOM-2830] Set 'm_critical' back to 'false' once proper fix for Atom startup issues are in + jobDescriptor.m_critical = true; + jobDescriptor.m_jobKey = ShaderAssetBuilder2JobKey; + jobDescriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str()); + jobDescriptor.m_jobParameters.emplace(ShaderAssetBuildTimestampParam, AZStd::to_string(shaderAssetBuildTimestamp)); + + response.m_createJobOutputs.push_back(jobDescriptor); + } // for all request.m_enabledPlatforms + + response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success; + } + + static bool SerializeOutShaderAsset(Data::Asset shaderAsset, + const AZStd::string& tempDirPath, + AssetBuilderSDK::ProcessJobResponse& response) + { + AZStd::string shaderAssetFileName = AZStd::string::format("%s.%s", shaderAsset->GetName().GetCStr(), RPI::ShaderAsset2::Extension); + AZStd::string shaderAssetOutputPath; + AzFramework::StringFunc::Path::ConstructFull(tempDirPath.data(), shaderAssetFileName.data(), shaderAssetOutputPath, true); + + if (!Utils::SaveObjectToFile(shaderAssetOutputPath, DataStream::ST_BINARY, shaderAsset.Get())) + { + AZ_Error(ShaderAssetBuilder2Name, false, "Failed to output Shader Descriptor"); + return false; + } + + AssetBuilderSDK::JobProduct shaderJobProduct; + if (!AssetBuilderSDK::OutputObject(shaderAsset.Get(), shaderAssetOutputPath, azrtti_typeid(), + aznumeric_cast(RPI::ShaderAsset2ProductSubId::ShaderAsset2), shaderJobProduct)) + { + AZ_Error(ShaderAssetBuilder2Name, false, "Failed to output product dependencies."); + return false; + } + response.m_outputProducts.push_back(AZStd::move(shaderJobProduct)); + + return true; + } + + static AZ::Outcome BuildAttributesMap( + const RHI::ShaderPlatformInterface* shaderPlatformInterface, + const AzslData& azslData, + const MapOfStringToStageType& shaderEntryPoints, + bool& hasRasterProgram) + { + hasRasterProgram = false; + bool hasComputeProgram = false; + bool hasRayTracingProgram = false; + RHI::ShaderStageAttributeMapList attributeMaps; + attributeMaps.resize(RHI::ShaderStageCount); + for (const auto& shaderEntryPoint : shaderEntryPoints) + { + auto shaderEntryName = shaderEntryPoint.first; + auto shaderStageType = shaderEntryPoint.second; + auto assetBuilderShaderType = ShaderBuilderUtility::ToAssetBuilderShaderType(shaderStageType); + hasRasterProgram |= shaderPlatformInterface->IsShaderStageForRaster(assetBuilderShaderType); + hasComputeProgram |= shaderPlatformInterface->IsShaderStageForCompute(assetBuilderShaderType); + hasRayTracingProgram |= shaderPlatformInterface->IsShaderStageForRayTracing(assetBuilderShaderType); + + auto findId = AZStd::find_if(AZ_BEGIN_END(azslData.m_functions), [&shaderEntryPoint](const auto& func) { + return func.m_name == shaderEntryPoint.first; + }); + + if (findId == azslData.m_functions.end()) + { + // shaderData.m_functions only contains Vertex, Fragment and Compute entries for now + // Tessellation shaders will need to be handled too + continue; + } + + const auto shaderStage = ToRHIShaderStage(assetBuilderShaderType); + for (const auto& attr : findId->attributesList) + { + // Some stages like RHI::ShaderStage::Tessellation are compound and consist of two or more shader entries + const Name& attributeName = attr.first; + const RHI::ShaderStageAttributeArguments& args = attr.second; + const auto stageIndex = static_cast(shaderStage); + AZ_Assert(stageIndex < RHI::ShaderStageCount, "Invalid shader stage specified!"); + attributeMaps[stageIndex][attributeName] = args; + } + } + + if (hasRasterProgram && hasComputeProgram) + { + return AZ::Failure(AZStd::string(" Shader asset descriptor defines both a raster entry point and a compute entry point.")); + } + + if (!hasRasterProgram && !hasComputeProgram && !hasRayTracingProgram) + { + AZStd::string entryPointNames = ShaderBuilderUtility::GetAcceptableDefaultEntryPointNames(azslData); + return AZ::Failure( + AZStd::string::format( "Shader asset descriptor has a program variant that does not define any entry points. Either declare entry " + "points in the .shader file, or use one of the available default names (not case-sensitive): [%s]", + entryPointNames.c_str())); + } + + return AZ::Success(attributeMaps); + } + + void ShaderAssetBuilder2::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const + { + const AZStd::sys_time_t startTime = AZStd::GetTimeNowTicks(); + AZStd::string shaderFullPath; + AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), shaderFullPath, true); + // Save .shader file name (no extension and no parent directory path) + AZStd::string shaderFileName; + AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.c_str(), shaderFileName); + + // No error checking because the same calls were already executed during CreateJobs() + auto descriptorParseOutcome = ShaderBuilderUtility::LoadShaderDataJson(shaderFullPath); + RPI::ShaderSourceData shaderSourceData = descriptorParseOutcome.TakeValue(); + AZStd::string azslFullPath; + ShaderBuilderUtility::GetAbsolutePathToAzslFile(shaderFullPath, shaderSourceData.m_source, azslFullPath); + AZ_TracePrintf(ShaderAssetBuilder2Name, "Original AZSL File: %s \n", azslFullPath.c_str()); + + // The directory where the Azsl file was found must be added to the list of include paths + AZStd::string azslFolderPath; + AzFramework::StringFunc::Path::GetFolderPath(azslFullPath.c_str(), azslFolderPath); + GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderAssetBuilder2Name, azslFolderPath.c_str()); + + // Request the list of valid shader platform interfaces for the target platform. + AZStd::vector platformInterfaces = ShaderBuilderUtility::DiscoverEnabledShaderPlatformInterfaces( + request.m_platformInfo, shaderSourceData); + if (platformInterfaces.empty()) + { + //No work to do. Exit gracefully. + AZ_TracePrintf(ShaderAssetBuilder2Name, + "No azshader is produced on behalf of %s because all valid RHI backends were disabled for this shader.\n", + shaderFullPath.c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; + return; + } + + // Get the time stamp string as sys_time_t, and also convert back to string to make sure it was converted correctly. + AZStd::sys_time_t shaderAssetBuildTimestamp = 0; + auto shaderAssetBuildTimestampIterator = request.m_jobDescription.m_jobParameters.find(ShaderAssetBuildTimestampParam); + if (shaderAssetBuildTimestampIterator != request.m_jobDescription.m_jobParameters.end()) + { + shaderAssetBuildTimestamp = AZStd::stoull(shaderAssetBuildTimestampIterator->second); + + if (AZStd::to_string(shaderAssetBuildTimestamp) != shaderAssetBuildTimestampIterator->second) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + AZ_Assert(false, "Incorrect conversion of ShaderAssetBuildTimestampParam"); + return; + } + } + + auto supervariantList = ShaderBuilderUtility::GetSupervariantListFromShaderSourceData(shaderSourceData); + + RPI::ShaderAssetCreator2 shaderAssetCreator; + shaderAssetCreator.Begin(Uuid::CreateRandom()); + + shaderAssetCreator.SetName(AZ::Name{shaderFileName.c_str()}); + shaderAssetCreator.SetDrawListName(Name(shaderSourceData.m_drawListName)); + shaderAssetCreator.SetShaderAssetBuildTimestamp(shaderAssetBuildTimestamp); + + // The ShaderOptionGroupLayout must be the same across all supervariants because + // there can be only a single ShaderVariantTreeAsset per ShaderAsset. + // We will store here the one that results when the *.azslin file is + // compiled for the default, nameless, supervariant. + // For all other supervariants we just make sure the hashes are the same + // as this one. + RPI::Ptr finalShaderOptionGroupLayout = nullptr; + + + // Time to describe the big picture. + // 1- Preprocess an AZSL file with MCPP (a C-Preprocessor), and generate a flat AZSL file without #include lines and any macros in it. + // Let's call it the Flat-AZSL file. There are two levels of macro definition that need to be merged before we can invoke MCPP: + // 1.1- From /Config/shader_global_build_options.json, which we have stored in the local variable @buildOptions. + // 1.2- From the "Supervariant" definition key, which can be different for each supervariant. + // 2- There will be one Flat-AZSL per supervariant. Each Flat-AZSL will be transpiled to HLSL with AZSLc. This means there will be one HLSL file + // per supervariant. + // 3- The generated HLSL (one HLSL per supervariant) file may contain C-Preprocessor Macros inserted by AZSLc. And that file will be given to DXC. + // DXC has a preprocessor embedded in it. DXC will be executed once for each entry function listed in the .shader file. + // There will be one DXIL compiled binary for each entry function. All the DXIL compiled binaries for each supervariant will be combined + // in the ROOT ShaderVariantAsset. + + // Remark: In general, the work done by the ShaderVariantAssetBuilder is similar, but it will start from the HLSL file created; in step 2, mentioned above; by this builder, + // for each supervariant. + + // At this moment We have global build options that should be merged with the build options that are common + // to all the supervariants of this shader. + buildOptions.m_compilerArguments.Merge(shaderSourceData.m_compiler); + + for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces) + { + AZStd::string apiName(shaderPlatformInterface->GetAPIName().GetCStr()); + AZ_TraceContext("Platform API", apiName); + // Signal the begin of shader data for an RHI API. + shaderAssetCreator.BeginAPI(shaderPlatformInterface->GetAPIType()); + + // Each shaderPlatformInterface has its own azsli header that needs to be prepended to the AZSL file before + // preprocessing. We will create a new temporary file that contains the combined data. + RHI::PrependArguments args; + args.m_sourceFile = azslFullPath.c_str(); + args.m_prependFile = shaderPlatformInterface->GetAzslHeader(request.m_platformInfo); + args.m_addSuffixToFileName = apiName.c_str(); + args.m_destinationFolder = request.m_tempDirPath.c_str(); + + AZStd::string prependedAzslFilePath = RHI::PrependFile(args); + if (prependedAzslFilePath == azslFullPath) + { + // For some reason the combined azsl file was not created in the temporary + // directory assigned to this job. + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + // Cache common AZSLC invokation arguments related with the current RHI Backend. + // Each supervariant can, optionally, remove or add more arguments for AZSLc. + AZStd::string commonAzslcCompilerParameters = + shaderPlatformInterface->GetAzslCompilerParameters(buildOptions.m_compilerArguments); + commonAzslcCompilerParameters += " "; + commonAzslcCompilerParameters += + shaderPlatformInterface->GetAzslCompilerWarningParameters(buildOptions.m_compilerArguments); + AtomShaderConfig::AddParametersFromConfigFile(commonAzslcCompilerParameters, request.m_platformInfo); + + // The register number only makes sense if the platform uses "spaces", + // since the register Id of the resource will not change even if the pipeline layout changes. + // We can pass in a default ShaderCompilerArguments because all we care about is whether the shaderPlatformInterface + // appends the "--use-spaces" flag. + const bool platformUsesRegisterSpaces = + (AzFramework::StringFunc::Find(commonAzslcCompilerParameters, "--use-spaces") != AZStd::string::npos); + + uint32_t supervariantIndex = 0; + for (const auto& supervariantInfo : supervariantList) + { + AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId); + if (jobCancelListener.IsCancelled()) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled; + return; + } + + shaderAssetCreator.BeginSupervariant(supervariantInfo.m_name); + + // Let's combine the global macro definitions, with the macro definitions particular to this + // supervariant. Two steps: + // 1- Supervariants can specify which macros to remove from the global definitions. + AZStd::vector macroDefinitionNamesToRemove = supervariantInfo.GetCombinedListOfMacroDefinitionNamesToRemove(); + PreprocessorOptions preprocessorOptions = buildOptions.m_preprocessorSettings; + preprocessorOptions.RemovePredefinedMacros(macroDefinitionNamesToRemove); + // 2- Supervariants can specify which macros to add. + AZStd::vector macroDefinitionsToAdd = supervariantInfo.GetMacroDefinitionsToAdd(); + preprocessorOptions.m_predefinedMacros.insert( + preprocessorOptions.m_predefinedMacros.end(), macroDefinitionsToAdd.begin(), macroDefinitionsToAdd.end()); + // Run the preprocessor. + PreprocessorData output; + PreprocessFile(prependedAzslFilePath, output, preprocessorOptions, true, true); + RHI::ReportErrorMessages(ShaderAssetBuilder2Name, output.diagnostics); + // Dump the preprocessed string as a flat AZSL file with extension .azslin, which will be given to AZSLc to generate the HLSL file. + AZStd::string superVariantAzslinStemName = shaderFileName; + if (!supervariantInfo.m_name.IsEmpty()) + { + superVariantAzslinStemName += AZStd::string::format("-%s", supervariantInfo.m_name.GetCStr()); + } + AZStd::string azslinFullPath = ShaderBuilderUtility::DumpPreprocessedCode( + ShaderAssetBuilder2Name, output.code, request.m_tempDirPath, superVariantAzslinStemName, + apiName, true /*add2*/); + if (azslinFullPath.empty()) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + AZ_TracePrintf(ShaderAssetBuilder2Name, "Preprocessed AZSL File: %s \n", prependedAzslFilePath.c_str()); + + // Before transpiling the flat-AZSL(.azslin) file into HLSL it is necessary + // to setup the AZSLc arguments as required by the current supervariant. + AZStd::string azslcCompilerParameters = supervariantInfo.GetCustomizedArgumentsForAzslc(commonAzslcCompilerParameters); + + // Ready to transpile the azslin file into HLSL. + ShaderBuilder::AzslCompiler azslc(azslinFullPath); + AZStd::string hlslFullPath = AZStd::string::format("%s_%s.hlsl2", superVariantAzslinStemName.c_str(), apiName.c_str()); + AzFramework::StringFunc::Path::Join(request.m_tempDirPath.c_str(), hlslFullPath.c_str(), hlslFullPath, true); + auto emitFullOutcome = azslc.EmitFullData(azslcCompilerParameters, hlslFullPath, "2"); + if (!emitFullOutcome.IsSuccess()) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + ShaderBuilderUtility::AzslSubProducts::Paths subProductsPaths = emitFullOutcome.TakeValue(); + + // In addition to the hlsl file, there are other json files that were generated. + // Each output file will become a product. + for (int i = 0; i < subProductsPaths.size(); ++i) + { + AssetBuilderSDK::JobProduct jobProduct; + jobProduct.m_productFileName = subProductsPaths[i]; + static const AZ::Uuid AzslOutcomeType = "{6977AEB1-17AD-4992-957B-23BB2E85B18B}"; + jobProduct.m_productAssetType = AzslOutcomeType; + // uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, uint32_t subProductType + jobProduct.m_productSubID = RPI::ShaderAsset2::MakeProductAssetSubId( + shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex, + aznumeric_cast(ShaderBuilderUtility::AzslSubProducts::SubList[i])); + jobProduct.m_dependenciesHandled = true; + // Note that the output products are not traditional product assets that will be used by the game project. + // They are artifacts that are produced once, cached, and used later by other AssetBuilders as a way to centralize + // build organization. + response.m_outputProducts.push_back(AZStd::move(jobProduct)); + } + + AZStd::shared_ptr files(new ShaderFiles); + AzslData azslData(files); + azslData.m_preprocessedFullPath = azslinFullPath; + RPI::ShaderResourceGroupLayoutList srgLayoutList; + RPI::Ptr shaderOptionGroupLayout = RPI::ShaderOptionGroupLayout::Create(); + BindingDependencies bindingDependencies; + RootConstantData rootConstantData; + AssetBuilderSDK::ProcessJobResultCode azslJsonReadResult = ShaderBuilderUtility::PopulateAzslDataFromJsonFiles( + ShaderAssetBuilder2Name, subProductsPaths, platformUsesRegisterSpaces, azslData, srgLayoutList, shaderOptionGroupLayout, + bindingDependencies, rootConstantData); + if (azslJsonReadResult != AssetBuilderSDK::ProcessJobResult_Success) + + { + response.m_resultCode = azslJsonReadResult; + return; + } + + shaderAssetCreator.SetSrgLayoutList(srgLayoutList); + + if (!finalShaderOptionGroupLayout) + { + finalShaderOptionGroupLayout = shaderOptionGroupLayout; + shaderAssetCreator.SetShaderOptionGroupLayout(finalShaderOptionGroupLayout); + const uint32_t usedShaderOptionBits = shaderOptionGroupLayout->GetBitSize(); + AZ_TracePrintf( + ShaderAssetBuilder2Name, "Note: This shader uses %u of %u available shader variant key bits. \n", + usedShaderOptionBits, RPI::ShaderVariantKeyBitCount); + } + else + { + if (finalShaderOptionGroupLayout->GetHash() != shaderOptionGroupLayout->GetHash()) + { + AZ_Error( + ShaderAssetBuilder2Name, false, "Supervariant %s has a different ShaderOptionGroupLayout", + supervariantInfo.m_name.GetCStr()) + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + } + + // Discover entry points & type of programs. + MapOfStringToStageType shaderEntryPoints; + if (shaderSourceData.m_programSettings.m_entryPoints.empty()) + { + AZ_TracePrintf( + ShaderAssetBuilder2Name, + "ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n"); + ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslData.m_functions, shaderEntryPoints); + } + else + { + for (const auto& entryPoint : shaderSourceData.m_programSettings.m_entryPoints) + { + shaderEntryPoints[entryPoint.m_name] = entryPoint.m_type; + } + } + + bool hasRasterProgram = false; + auto attributeMapsOutcome = BuildAttributesMap(shaderPlatformInterface, azslData, shaderEntryPoints, hasRasterProgram); + if (!attributeMapsOutcome.IsSuccess()) + { + AZ_Error(ShaderAssetBuilder2Name, false, "%s\n", attributeMapsOutcome.GetError().c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + shaderAssetCreator.SetShaderStageAttributeMapList(attributeMapsOutcome.TakeValue()); + + // Check if we were canceled before we do any heavy processing of + // the shader data (compiling the shader kernels, processing SRG + // and pipeline layout data, etc.). + if (jobCancelListener.IsCancelled()) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled; + return; + } + + RHI::Ptr pipelineLayoutDescriptor = + ShaderBuilderUtility::BuildPipelineLayoutDescriptorForApi( + ShaderAssetBuilder2Name, srgLayoutList, shaderEntryPoints, buildOptions.m_compilerArguments, rootConstantData, + shaderPlatformInterface, bindingDependencies); + if (!pipelineLayoutDescriptor) + { + AZ_Error( + ShaderAssetBuilder2Name, false, "Failed to build pipeline layout descriptor for api=[%s]", + shaderPlatformInterface->GetAPIName().GetCStr()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + shaderAssetCreator.SetPipelineLayout(pipelineLayoutDescriptor); + + + RPI::ShaderInputContract shaderInputContract; + RPI::ShaderOutputContract shaderOutputContract; + size_t colorAttachmentCount = 0; + ShaderBuilderUtility::CreateShaderInputAndOutputContracts( + azslData, shaderEntryPoints, *shaderOptionGroupLayout.get(), + subProductsPaths[ShaderBuilderUtility::AzslSubProducts::om], + subProductsPaths[ShaderBuilderUtility::AzslSubProducts::ia], + shaderInputContract, shaderOutputContract, colorAttachmentCount); + shaderAssetCreator.SetInputContract(shaderInputContract); + shaderAssetCreator.SetOutputContract(shaderOutputContract); + + if (hasRasterProgram) + { + // Set the various states to what is in the descriptor. + const RHI::TargetBlendState& targetBlendState = shaderSourceData.m_blendState; + RHI::RenderStates renderStates; + renderStates.m_rasterState = shaderSourceData.m_rasterState; + renderStates.m_depthStencilState = shaderSourceData.m_depthStencilState; + // [GFX TODO][ATOM-930] We should support unique blend states per RT + for (size_t i = 0; i < colorAttachmentCount; ++i) + { + renderStates.m_blendState.m_targets[i] = targetBlendState; + } + + shaderAssetCreator.SetRenderStates(renderStates); + } + + Outcome hlslSourceCodeOutcome = Utils::ReadFile(hlslFullPath); + if (!hlslSourceCodeOutcome.IsSuccess()) + { + AZ_Error( + ShaderAssetBuilder2Name, false, "Failed to obtain shader source from %s. [%s]", hlslFullPath.c_str(), + hlslSourceCodeOutcome.GetError().c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + AZStd::string hlslSourceCode = hlslSourceCodeOutcome.TakeValue(); + + // The root ShaderVariantAsset needs to be created with the known uuid of the source .shader asset because + // the ShaderAsset owns a Data::Asset<> reference that gets serialized. It must have the correct uuid + // so the root ShaderVariantAsset is found when the ShaderAsset is deserialized. + uint32_t rootVariantProductSubId = RPI::ShaderAsset2::MakeProductAssetSubId( + shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex, + aznumeric_cast(RPI::ShaderAsset2ProductSubId::RootShaderVariantAsset)); + auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(shaderFullPath, rootVariantProductSubId); + AZ_Assert(assetIdOutcome.IsSuccess(), "Failed to get AssetId from shader %s", shaderFullPath.c_str()); + const Data::AssetId variantAssetId = assetIdOutcome.TakeValue(); + + RPI::ShaderVariantListSourceData::VariantInfo rootVariantInfo; + ShaderVariantCreationContext2 shaderVariantCreationContext = { + *shaderPlatformInterface, + request.m_platformInfo, + buildOptions.m_compilerArguments, + request.m_tempDirPath, + startTime, + shaderSourceData, + *shaderOptionGroupLayout.get(), + shaderEntryPoints, + variantAssetId, + superVariantAzslinStemName, + hlslFullPath, + hlslSourceCode}; + + + AZStd::optional outputByproducts; + auto rootShaderVariantAssetOutcome = ShaderVariantAssetBuilder2::CreateShaderVariantAsset(rootVariantInfo, shaderVariantCreationContext, outputByproducts); + if (!rootShaderVariantAssetOutcome.IsSuccess()) + { + AZ_Error(ShaderAssetBuilder2Name, false, "%s\n", rootShaderVariantAssetOutcome.GetError().c_str()) + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + Data::Asset rootShaderVariantAsset = rootShaderVariantAssetOutcome.TakeValue(); + + shaderAssetCreator.SetRootShaderVariantAsset(rootShaderVariantAsset); + + if (!shaderAssetCreator.EndSupervariant()) + { + AZ_Error( + ShaderAssetBuilder2Name, false, "Failed to create shader asset for supervariant [%s]", supervariantInfo.m_name.GetCStr()) + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + // Time to save the root variant related assets in the cache. + AssetBuilderSDK::JobProduct assetProduct; + if (!ShaderVariantAssetBuilder2::SerializeOutShaderVariantAsset( + rootShaderVariantAsset, superVariantAzslinStemName, request.m_tempDirPath, *shaderPlatformInterface, + rootVariantProductSubId, + assetProduct)) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + response.m_outputProducts.push_back(assetProduct); + + if (outputByproducts) + { + // add byproducts as job output products: + uint32_t subProductType = aznumeric_cast(RPI::ShaderAsset2ProductSubId::FirstByProduct); + for (const AZStd::string& byproduct : outputByproducts.value().m_intermediatePaths) + { + AssetBuilderSDK::JobProduct jobProduct; + jobProduct.m_productFileName = byproduct; + jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt"); + jobProduct.m_productSubID = RPI::ShaderAsset2::MakeProductAssetSubId( + shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex, + subProductType++); + response.m_outputProducts.push_back(AZStd::move(jobProduct)); + } + } + + + supervariantIndex++; + + } // end for the supervariant + + shaderAssetCreator.EndAPI(); + + } // end for all ShaderPlatformInterfaces + + Data::Asset shaderAsset; + if (!shaderAssetCreator.End(shaderAsset)) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + if (!SerializeOutShaderAsset(shaderAsset, request.m_tempDirPath, response)) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; + + const AZStd::sys_time_t endTime = AZStd::GetTimeNowTicks(); + const AZStd::sys_time_t deltaTime = endTime - startTime; + const float elapsedTimeSeconds = (float)(deltaTime) / (float)AZStd::GetTimeTicksPerSecond(); + + AZ_TracePrintf(ShaderAssetBuilder2Name, "Finished processing %s in %.2f seconds\n", request.m_sourceFile.c_str(), elapsedTimeSeconds); + + ShaderBuilderUtility::LogProfilingData(ShaderAssetBuilder2Name, shaderFileName); + } + + } // ShaderBuilder +} // AZ diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder2.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder2.h new file mode 100644 index 0000000000..915d4e53d0 --- /dev/null +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder2.h @@ -0,0 +1,60 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include + +#include + +namespace AZ +{ + namespace Data + { + class AssetHandler; + } + + namespace RHI + { + class ShaderPlatformInterface; + } + + namespace ShaderBuilder + { + struct AzslData; + + class ShaderAssetBuilder2 + : public AssetBuilderSDK::AssetBuilderCommandBus::Handler + { + public: + AZ_TYPE_INFO(ShaderAssetBuilder2, "{C94DA151-82BC-4475-86FA-E6C92A0BD6F8}"); + + static constexpr const char* ShaderAssetBuilder2JobKey = "Shader Asset 2"; + + ShaderAssetBuilder2() = default; + ~ShaderAssetBuilder2() = default; + + // Asset Builder Callback Functions ... + void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const; + void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const; + + // AssetBuilderSDK::AssetBuilderCommandBus interface overrides ... + void ShutDown() override { }; + + private: + AZ_DISABLE_COPY_MOVE(ShaderAssetBuilder2); + }; + + } // ShaderBuilder +} // AZ diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.cpp index 19b2f328c1..a20b9c869b 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.cpp @@ -29,7 +29,8 @@ #include #include -#include +#include // DEPRECATED - [ATOM-15472] +#include #include #include @@ -41,13 +42,15 @@ #include "ShaderPlatformInterfaceRequest.h" #include "AtomShaderConfig.h" +#include "SrgLayoutUtility.h" + namespace AZ { namespace ShaderBuilder { namespace ShaderBuilderUtility { - static const char* ShaderBuilderUtilityName = "ShaderBuilderUtility"; + static constexpr char ShaderBuilderUtilityName[] = "ShaderBuilderUtility"; Outcome LoadShaderDataJson(const AZStd::string& fullPathToJsonFile) { @@ -84,22 +87,8 @@ namespace AZ AzFramework::StringFunc::Path::ReplaceExtension(absoluteAzslPath, "azsl"); } - uint32_t MakeDebugByproductSubId(RHI::APIType apiType, const AZStd::string& productFileName) - { - // bits: ----- 24 -----|- 4 -|- 4 - - // fn hash | id + api | 0xF - uint32_t subId = 0xF; // to avoid collisions with subid of other source outputs using RPI::ShaderAssetSubId::GeneratedSource + api - uint32_t id_api = static_cast(RPI::ShaderAssetSubId::DebugByProduct); - id_api += apiType; - id_api <<= 4; - subId |= id_api; - size_t fnHash = AZStd::hash()(productFileName); - subId |= static_cast(fnHash) & 0xFFFFFF00; - return subId; - } - static bool LoadShaderResourceGroupAssets( - [[maybe_unused]] const char* BuilderName, + [[maybe_unused]] const char* builderName, const SrgDataContainer& resourceGroups, ShaderResourceGroupAssets& srgAssets) { @@ -121,7 +110,7 @@ namespace AZ if (!assetFound) { - AZ_Error(BuilderName, false, "Could not find asset identified by path '%s'", srgFilePath.c_str()); + AZ_Error(builderName, false, "Could not find asset identified by path '%s'", srgFilePath.c_str()); readSRGsSuccessfuly = false; continue; } @@ -139,7 +128,7 @@ namespace AZ : asset.GetStatus() == Status::ReadyPreNotify ? "ready-pre-notify" : asset.GetStatus() == Status::Error ? "error" : "not-loaded/ready/unknown"; - AZ_Error(BuilderName, false, "Searching SRG [%s]: Could not load SRG asset. (asset status [%s]) AssetId='%s' Path='%s'", + AZ_Error(builderName, false, "Searching SRG [%s]: Could not load SRG asset. (asset status [%s]) AssetId='%s' Path='%s'", srgData.m_name.c_str(), statusString.c_str(), assetId.ToString().c_str(), srgFilePath.c_str()); @@ -148,7 +137,7 @@ namespace AZ } else if (!asset->IsValid()) { - AZ_Error(BuilderName, false, "SRG asset has no layout information. AssetId='%s' Path='%s'", + AZ_Error(builderName, false, "SRG asset has no layout information. AssetId='%s' Path='%s'", assetId.ToString().c_str(), srgFilePath.c_str()); readSRGsSuccessfuly = false; continue; @@ -182,8 +171,10 @@ namespace AZ return files; } + + //! [GFX TODO] [ATOM-15472] Deprecated, remove when this ticket is addressed. AssetBuilderSDK::ProcessJobResultCode PopulateAzslDataFromJsonFiles( - const char* BuilderName, + const char* builderName, const AzslSubProducts::Paths& pathOfJsonFiles, AzslData& azslData, ShaderResourceGroupAssets& srgAssets, @@ -204,7 +195,7 @@ namespace AZ outcomes[i] = JsonSerializationUtils::ReadJsonFile(pathOfJsonFiles[i]); if (!outcomes[i].IsSuccess()) { - AZ_Error(BuilderName, false, "%s", outcomes[i].GetError().c_str()); + AZ_Error(builderName, false, "%s", outcomes[i].GetError().c_str()); allReadSuccess = false; } } @@ -215,22 +206,22 @@ namespace AZ // Get full list of functions eligible for vertex shader entry points // along with metadata for constructing the InputAssembly for each of them - if (!azslc.ParseIaPopulateFunctionData(outcomes[AzslSubProducts::ia].GetValue(), azslData.m_topData.m_functions)) + if (!azslc.ParseIaPopulateFunctionData(outcomes[AzslSubProducts::ia].GetValue(), azslData.m_functions)) { return AssetBuilderSDK::ProcessJobResult_Failed; } // Each SRG is built as a separate asset in the SrgLayoutBuilder, here we just // build the list and load the data from multiple dependency assets. - if (!azslc.ParseSrgPopulateSrgData(outcomes[AzslSubProducts::srg].GetValue(), azslData.m_topData.m_srgData)) + if (!azslc.ParseSrgPopulateSrgData(outcomes[AzslSubProducts::srg].GetValue(), azslData.m_srgData)) { return AssetBuilderSDK::ProcessJobResult_Failed; } // Add all Shader Resource Group Assets that were defined in the shader code to the shader asset - if (!LoadShaderResourceGroupAssets(BuilderName, azslData.m_topData.m_srgData, srgAssets)) + if (!LoadShaderResourceGroupAssets(builderName, azslData.m_srgData, srgAssets)) { - AZ_Error(BuilderName, false, "Failed to obtain shader resource group assets"); + AZ_Error(builderName, false, "Failed to obtain shader resource group assets"); return AssetBuilderSDK::ProcessJobResult_Failed; } @@ -238,7 +229,7 @@ namespace AZ // for each option and what is its default value. if (!azslc.ParseOptionsPopulateOptionGroupLayout(outcomes[AzslSubProducts::options].GetValue(), shaderOptionGroupLayout)) { - AZ_Error(BuilderName, false, "Failed to find a valid list of shader options!"); + AZ_Error(builderName, false, "Failed to find a valid list of shader options!"); return AssetBuilderSDK::ProcessJobResult_Failed; } @@ -246,14 +237,100 @@ namespace AZ // and informs us on register indexes and shader stages using these resources if (!azslc.ParseBindingdepPopulateBindingDependencies(outcomes[AzslSubProducts::bindingdep].GetValue(), bindingDependencies)) // consuming data from binding-dep { - AZ_Error(BuilderName, false, "Failed to obtain shader resource binding reflection"); + AZ_Error(builderName, false, "Failed to obtain shader resource binding reflection"); return AssetBuilderSDK::ProcessJobResult_Failed; } // access the root constants reflection if (!azslc.ParseSrgPopulateRootConstantData(outcomes[AzslSubProducts::srg].GetValue(), rootConstantData)) // consuming data from --srg ("InlineConstantBuffer" subjson section) { - AZ_Error(BuilderName, false, "Failed to obtain root constant data reflection"); + AZ_Error(builderName, false, "Failed to obtain root constant data reflection"); + return AssetBuilderSDK::ProcessJobResult_Failed; + } + + return AssetBuilderSDK::ProcessJobResult_Success; + } + + + AssetBuilderSDK::ProcessJobResultCode PopulateAzslDataFromJsonFiles( + const char* builderName, + const AzslSubProducts::Paths& pathOfJsonFiles, + const bool platformUsesRegisterSpaces, + AzslData& azslData, + RPI::ShaderResourceGroupLayoutList& srgLayoutList, + RPI::Ptr shaderOptionGroupLayout, + BindingDependencies& bindingDependencies, + RootConstantData& rootConstantData) + { + AzslCompiler azslc( + azslData + .m_preprocessedFullPath); // set the input file for eventual error messages, but the compiler won't be called on it. + bool allReadSuccess = true; + // read: input assembly reflection + // shader resource group reflection + // options reflection + // binding dependencies reflection + int indicesOfInterest[] = { + AzslSubProducts::ia, AzslSubProducts::srg, AzslSubProducts::options, AzslSubProducts::bindingdep}; + AZStd::unordered_map> outcomes; + for (int i : indicesOfInterest) + { + outcomes[i] = JsonSerializationUtils::ReadJsonFile(pathOfJsonFiles[i]); + if (!outcomes[i].IsSuccess()) + { + AZ_Error(builderName, false, "%s", outcomes[i].GetError().c_str()); + allReadSuccess = false; + } + } + if (!allReadSuccess) + { + return AssetBuilderSDK::ProcessJobResult_Failed; + } + + // Get full list of functions eligible for vertex shader entry points + // along with metadata for constructing the InputAssembly for each of them + if (!azslc.ParseIaPopulateFunctionData(outcomes[AzslSubProducts::ia].GetValue(), azslData.m_functions)) + { + return AssetBuilderSDK::ProcessJobResult_Failed; + } + + // Each SRG is built as a separate asset in the SrgLayoutBuilder, here we just + // build the list and load the data from multiple dependency assets. + if (!azslc.ParseSrgPopulateSrgData(outcomes[AzslSubProducts::srg].GetValue(), azslData.m_srgData)) + { + return AssetBuilderSDK::ProcessJobResult_Failed; + } + + // Add all Shader Resource Group Assets that were defined in the shader code to the shader asset + if (!SrgLayoutUtility::LoadShaderResourceGroupLayouts(builderName, azslData.m_srgData, platformUsesRegisterSpaces, srgLayoutList)) + { + AZ_Error(builderName, false, "Failed to obtain shader resource group assets"); + return AssetBuilderSDK::ProcessJobResult_Failed; + } + + // The shader options define what options are available, what are the allowed values/range + // for each option and what is its default value. + if (!azslc.ParseOptionsPopulateOptionGroupLayout(outcomes[AzslSubProducts::options].GetValue(), shaderOptionGroupLayout)) + { + AZ_Error(builderName, false, "Failed to find a valid list of shader options!"); + return AssetBuilderSDK::ProcessJobResult_Failed; + } + + // It analyzes the shader external bindings (all SRG contents) + // and informs us on register indexes and shader stages using these resources + if (!azslc.ParseBindingdepPopulateBindingDependencies( + outcomes[AzslSubProducts::bindingdep].GetValue(), bindingDependencies)) // consuming data from binding-dep + { + AZ_Error(builderName, false, "Failed to obtain shader resource binding reflection"); + return AssetBuilderSDK::ProcessJobResult_Failed; + } + + // access the root constants reflection + if (!azslc.ParseSrgPopulateRootConstantData( + outcomes[AzslSubProducts::srg].GetValue(), + rootConstantData)) // consuming data from --srg ("InlineConstantBuffer" subjson section) + { + AZ_Error(builderName, false, "Failed to obtain root constant data reflection"); return AssetBuilderSDK::ProcessJobResult_Failed; } @@ -312,7 +389,7 @@ namespace AZ } RHI::Ptr BuildPipelineLayoutDescriptorForApi( - [[maybe_unused]] const char* BuilderName, + [[maybe_unused]] const char* builderName, RHI::ShaderPlatformInterface* shaderPlatformInterface, BindingDependencies& bindingDependencies /*inout*/, const ShaderResourceGroupAssets& srgAssets, @@ -356,7 +433,7 @@ namespace AZ const BindingDependencies::SrgResources* srgResources = bindingDependencies.GetSrg(srgName); if (!srgResources) { - AZ_Error(BuilderName, false, "SRG %s not found in the dependency dataset", srgName.data()); + AZ_Error(builderName, false, "SRG %s not found in the dependency dataset", srgName.data()); return nullptr; } @@ -385,23 +462,21 @@ namespace AZ for (const auto& constantData : rootConstantData->m_constants) { RHI::ShaderInputConstantDescriptor rootConstantDesc( - constantData.m_nameId, - constantData.m_constantByteOffset, - constantData.m_constantByteSize, + constantData.m_nameId, constantData.m_constantByteOffset, constantData.m_constantByteSize, rootConstantData->m_bindingInfo.m_registerId); - + rootConstantsLayout->AddShaderInput(rootConstantDesc); } } - + if (!rootConstantsLayout->Finalize()) { - AZ_Error(BuilderName, false, "Failed to finalize root constants layout"); + AZ_Error(builderName, false, "Failed to finalize root constants layout"); return nullptr; } pipelineLayoutDescriptor->SetRootConstantsLayout(*rootConstantsLayout); - + RHI::ShaderPlatformInterface::RootConstantsInfo rootConstantInfo; if (rootConstantData) { @@ -415,14 +490,15 @@ namespace AZ rootConstantInfo.m_registerId = dummyRootConstantData.m_bindingInfo.m_registerId; } rootConstantInfo.m_totalSizeInBytes = rootConstantsLayout->GetDataSize(); - + // Build platform-specific PipelineLayoutDescriptor data, and finalize - if (!shaderPlatformInterface->BuildPipelineLayoutDescriptor(pipelineLayoutDescriptor, srgInfos, rootConstantInfo, shaderCompilerArguments)) + if (!shaderPlatformInterface->BuildPipelineLayoutDescriptor( + pipelineLayoutDescriptor, srgInfos, rootConstantInfo, shaderCompilerArguments)) { - AZ_Error(BuilderName, false, "Failed to build pipeline layout descriptor"); + AZ_Error(builderName, false, "Failed to build pipeline layout descriptor"); return nullptr; } - + return pipelineLayoutDescriptor; } @@ -442,7 +518,7 @@ namespace AZ } else { - formatted = AZStd::string::format("%s.%s.%s", stemName.c_str(), apiTypeString.c_str(), extension.c_str()); + formatted = AZStd::string::format("%s_%s.%s", stemName.c_str(), apiTypeString.c_str(), extension.c_str()); } AzFramework::StringFunc::Path::Join(dumpDirectory.c_str(), formatted.c_str(), finalFilePath, true, true); AZ::IO::FileIOStream outFileStream(finalFilePath.data(), AZ::IO::OpenMode::ModeWrite); @@ -463,14 +539,20 @@ namespace AZ return finalFilePath; } - AZStd::string DumpPreprocessedCode(const char* builderName, const AZStd::string& preprocessedCode, const AZStd::string& tempDirPath, const AZStd::string& stemName, const AZStd::string& apiTypeString) + // [GFX TODO] Remove 'add2' when [ATOM-15472] + AZStd::string DumpPreprocessedCode(const char* builderName, const AZStd::string& preprocessedCode, const AZStd::string& tempDirPath, const AZStd::string& stemName, const AZStd::string& apiTypeString, bool add2) { + if (add2) + { + return DumpCode(builderName, preprocessedCode, tempDirPath, stemName, apiTypeString, "azslin2"); + } + return DumpCode(builderName, preprocessedCode, tempDirPath, stemName, apiTypeString, "azslin"); } AZStd::string DumpAzslPrependedCode(const char* builderName, const AZStd::string& nonPreprocessedYetAzslSource, const AZStd::string& tempDirPath, const AZStd::string& stemName, const AZStd::string& apiTypeString) { - return DumpCode(builderName, nonPreprocessedYetAzslSource, tempDirPath, stemName, apiTypeString, "azsl.prepend"); + return DumpCode(builderName, nonPreprocessedYetAzslSource, tempDirPath, stemName, apiTypeString, "azslprepend"); } AZStd::string ExtractStemName(const char* path) @@ -489,6 +571,83 @@ namespace AZ return platformInterfaces; } + + AZStd::vector DiscoverEnabledShaderPlatformInterfaces(const AssetBuilderSDK::PlatformInfo& info, const RPI::ShaderSourceData& shaderSourceData) + { + // Request the list of valid shader platform interfaces for the target platform. + AZStd::vector platformInterfaces; + ShaderPlatformInterfaceRequestBus::BroadcastResult( + platformInterfaces, &ShaderPlatformInterfaceRequest::GetShaderPlatformInterface, info); + + // Let's remove the unwanted RHI interfaces from the list. + platformInterfaces.erase( + AZStd::remove_if(AZ_BEGIN_END(platformInterfaces), + [&](const RHI::ShaderPlatformInterface* shaderPlatformInterface) { + return !shaderPlatformInterface || + shaderSourceData.IsRhiBackendDisabled(shaderPlatformInterface->GetAPIName()) || + (shaderPlatformInterface->GetAPIUniqueIndex() == static_cast(AZ::RHI::APIIndex::Null)); + }), + platformInterfaces.end()); + return platformInterfaces; + } + + static bool IsValidSupervariantName(const AZStd::string& supervariantName) + { + return AZStd::all_of(AZ_BEGIN_END(supervariantName), + [](AZStd::string::value_type ch) + { + return AZStd::is_alnum(ch); // allow alpha numeric only + } + ); + } + + AZStd::vector GetSupervariantListFromShaderSourceData( + const RPI::ShaderSourceData& shaderSourceData) + { + AZStd::vector supervariants; + supervariants.reserve(shaderSourceData.m_supervariants.size() + 1); + + // Add the supervariants, always making sure that: + // 1- The default, nameless, supervariant goes to the front. + // 2- Each supervariant has a unique name + AZStd::unordered_set uniqueSuperVariants; // This set helps duplicate detection. + // Although it is not common, it is possible to declare a nameless supervariant. + bool addedNamelessSupervariant = false; + for (const auto& supervariantInfo : shaderSourceData.m_supervariants) + { + if (!IsValidSupervariantName(supervariantInfo.m_name.GetStringView())) + { + AZ_Error( + ShaderBuilderUtilityName, false, "The supervariant name: [%s] contains invalid characters. Only [a-zA-Z0-9] are supported", + supervariantInfo.m_name.GetCStr()); + return {}; // Return an empty vector. + } + if (uniqueSuperVariants.count(supervariantInfo.m_name)) + { + AZ_Error( + ShaderBuilderUtilityName, false, "It is invalid to specify more than one supervariant with the same name: [%s]", + supervariantInfo.m_name.GetCStr()); + return {}; // Return an empty vector. + } + uniqueSuperVariants.emplace(supervariantInfo.m_name); + supervariants.push_back(supervariantInfo); + if (supervariantInfo.m_name.IsEmpty()) + { + addedNamelessSupervariant = true; + // Always move the default, nameless, variant to the begining of the list. + AZStd::swap(supervariants.front(), supervariants.back()); + } + } + if (!addedNamelessSupervariant) + { + supervariants.push_back({}); + // Always move the default, nameless, variant to the begining of the list. + AZStd::swap(supervariants.front(), supervariants.back()); + } + + return supervariants; + } + static void ReadShaderCompilerProfiling([[maybe_unused]] const char* builderName, RHI::ShaderCompilerProfiling& shaderCompilerProfiling, AZStd::string_view shaderPath) { AZStd::string folderPath; @@ -561,12 +720,64 @@ namespace AZ uint32_t MakeAzslBuildProductSubId(RPI::ShaderAssetSubId subId, RHI::APIType apiType) { - auto subIdMaxEnumerator = RPI::ShaderAssetSubId::GeneratedSource; + auto subIdMaxEnumerator = RPI::ShaderAssetSubId::GeneratedHlslSource; // separate bit space between subid enum, and api-type: int shiftLeft = static_cast(log2(static_cast(subIdMaxEnumerator))) + 1; return static_cast(subId) + (apiType << shiftLeft); } + Outcome ObtainBuildArtifactPathFromShaderAssetBuilder2( + const uint32_t rhiUniqueIndex, const AZStd::string& platformIdentifier, const AZStd::string& shaderJsonPath, + const uint32_t supervariantIndex, RPI::ShaderAssetSubId shaderAssetSubId) + { + // platform id from identifier + AzFramework::PlatformId platformId = AzFramework::PlatformId::PC; + if (platformIdentifier == "pc") + { + platformId = AzFramework::PlatformId::PC; + } + else if (platformIdentifier == "osx_gl") + { + platformId = AzFramework::PlatformId::OSX; + } + else if (platformIdentifier == "es3") + { + platformId = AzFramework::PlatformId::ES3; + } + else if (platformIdentifier == "ios") + { + platformId = AzFramework::PlatformId::IOS; + } + + uint32_t assetSubId = RPI::ShaderAsset2::MakeProductAssetSubId(rhiUniqueIndex, supervariantIndex, aznumeric_cast(shaderAssetSubId)); + auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(shaderJsonPath, assetSubId); + if (!assetIdOutcome.IsSuccess()) + { + return Failure(AZStd::string::format( + "Missing ShaderAssetBuilder2 product %s, for sub %d", shaderJsonPath.c_str(), (uint32_t)shaderAssetSubId)); + } + + Data::AssetId assetId = assetIdOutcome.TakeValue(); + // get the relative path: + AZStd::string assetPath; + Data::AssetCatalogRequestBus::BroadcastResult(assetPath, &Data::AssetCatalogRequests::GetAssetPathById, assetId); + + // get the root: + AZStd::string assetRoot = AzToolsFramework::PlatformAddressedAssetCatalog::GetAssetRootForPlatform(platformId); + // join + AZStd::string assetFullPath; + AzFramework::StringFunc::Path::Join(assetRoot.c_str(), assetPath.c_str(), assetFullPath); + bool fileExists = IO::FileIOBase::GetInstance()->Exists(assetFullPath.c_str()) && + !IO::FileIOBase::GetInstance()->IsDirectory(assetFullPath.c_str()); + if (!fileExists) + { + return Failure(AZStd::string::format( + "asset [%s] from shader source %s and subId %d doesn't exist", assetFullPath.c_str(), shaderJsonPath.c_str(), + (uint32_t)shaderAssetSubId)); + } + return AZ::Success(assetFullPath); + } + Outcome ObtainBuildArtifactsFromAzslBuilder([[maybe_unused]] const char* builderName, const AZStd::string& sourceFullPath, RHI::APIType apiType, const AZStd::string& platform) { AzslSubProducts::Paths products; @@ -619,6 +830,7 @@ namespace AZ return AZ::Success(products); } + // DEPRECATED [ATOM-15472] // See header for info. // REMARK: The approach to string searching and matching done in this function is kind of naive // because the strings can match text within a comment block, etc. So it is not 100% fool proof. @@ -672,6 +884,399 @@ namespace AZ return SrgSkipFileResult::ContinueProcess; } + + RHI::Ptr BuildPipelineLayoutDescriptorForApi( + const char* builderName, const RPI::ShaderResourceGroupLayoutList& srgLayoutList, const MapOfStringToStageType& shaderEntryPoints, + const RHI::ShaderCompilerArguments& shaderCompilerArguments, const RootConstantData& rootConstantData, + RHI::ShaderPlatformInterface* shaderPlatformInterface, BindingDependencies& bindingDependencies /*inout*/) + { + PruneNonEntryFunctions(bindingDependencies, shaderEntryPoints); + + // Translates from a list of function names that use a resource to a shader stage mask. + auto getRHIShaderStageMask = [&shaderEntryPoints](const BindingDependencies::FunctionsNameVector& functions) { + RHI::ShaderStageMask mask = RHI::ShaderStageMask::None; + // Iterate through all the functions that are using the resource. + for (const auto& functionName : functions) + { + // Search the function name into the list of valid entry points into the shader. + auto findId = + AZStd::find_if(shaderEntryPoints.begin(), shaderEntryPoints.end(), [&functionName, &mask](const auto& item) { + return item.first == functionName; + }); + + if (findId != shaderEntryPoints.end()) + { + // Use the entry point shader stage type to calculate the mask. + RHI::ShaderHardwareStage hardwareStage = ToAssetBuilderShaderType(findId->second); + mask |= static_cast(AZ_BIT(static_cast(RHI::ToRHIShaderStage(hardwareStage)))); + } + } + + return mask; + }; + + // Build general PipelineLayoutDescriptor data that is provided for all platforms + RHI::Ptr pipelineLayoutDescriptor = + shaderPlatformInterface->CreatePipelineLayoutDescriptor(); + RHI::ShaderPlatformInterface::ShaderResourceGroupInfoList srgInfos; + for (const auto& srgLayout : srgLayoutList) + { + // Search the binding info for a Shader Resource Group. + AZStd::string_view srgName = srgLayout->GetName().GetStringView(); + const BindingDependencies::SrgResources* srgResources = bindingDependencies.GetSrg(srgName); + if (!srgResources) + { + AZ_Error(builderName, false, "SRG %s not found in the dependency dataset", srgName.data()); + return nullptr; + } + + RHI::ShaderResourceGroupBindingInfo srgBindingInfo; + srgBindingInfo.m_spaceId = srgResources->m_registerSpace; + const RHI::ShaderResourceGroupLayout* layout = srgLayout.get(); + // Calculate the binding in for the constant data. All constant data share the same binding info. + srgBindingInfo.m_constantDataBindingInfo = { + getRHIShaderStageMask(srgResources->m_srgConstantsDependencies.m_binding.m_dependentFunctions), + srgResources->m_srgConstantsDependencies.m_binding.m_registerId}; + // Calculate the binding info for each resource of the Shader Resource Group. + for (auto const& resource : srgResources->m_resources) + { + auto const& resourceInfo = resource.second; + srgBindingInfo.m_resourcesRegisterMap.insert( + {AZ::Name(resourceInfo.m_selfName), + RHI::ResourceBindingInfo( + getRHIShaderStageMask(resourceInfo.m_dependentFunctions), resourceInfo.m_registerId)}); + } + pipelineLayoutDescriptor->AddShaderResourceGroupLayoutInfo(*layout, srgBindingInfo); + srgInfos.push_back(RHI::ShaderPlatformInterface::ShaderResourceGroupInfo{layout, srgBindingInfo}); + } + + RHI::Ptr rootConstantsLayout = RHI::ConstantsLayout::Create(); + for (const auto& constantData : rootConstantData.m_constants) + { + RHI::ShaderInputConstantDescriptor rootConstantDesc( + constantData.m_nameId, constantData.m_constantByteOffset, constantData.m_constantByteSize, + rootConstantData.m_bindingInfo.m_registerId); + + rootConstantsLayout->AddShaderInput(rootConstantDesc); + } + + + if (!rootConstantsLayout->Finalize()) + { + AZ_Error(builderName, false, "Failed to finalize root constants layout"); + return nullptr; + } + + pipelineLayoutDescriptor->SetRootConstantsLayout(*rootConstantsLayout); + + RHI::ShaderPlatformInterface::RootConstantsInfo rootConstantInfo; + rootConstantInfo.m_spaceId = rootConstantData.m_bindingInfo.m_space; + rootConstantInfo.m_registerId = rootConstantData.m_bindingInfo.m_registerId; + rootConstantInfo.m_totalSizeInBytes = rootConstantsLayout->GetDataSize(); + + // Build platform-specific PipelineLayoutDescriptor data, and finalize + if (!shaderPlatformInterface->BuildPipelineLayoutDescriptor( + pipelineLayoutDescriptor, srgInfos, rootConstantInfo, shaderCompilerArguments)) + { + AZ_Error(builderName, false, "Failed to build pipeline layout descriptor"); + return nullptr; + } + + return pipelineLayoutDescriptor; + } + + static bool IsSystemValueSemantic(const AZStd::string_view semantic) + { + // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics#system-value-semantics + return AzFramework::StringFunc::StartsWith(semantic, "sv_", false); + } + + static bool CreateShaderInputContract( + const AzslData& azslData, + const AZStd::string& vertexShaderName, + const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout, + const AZStd::string& pathToIaJson, + RPI::ShaderInputContract& contract) + { + StructData inputStruct; + inputStruct.m_id = ""; + + auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToIaJson); + if (!jsonOutcome.IsSuccess()) + { + AZ_Error(ShaderBuilderUtilityName, false, "%s", jsonOutcome.GetError().c_str()); + return AssetBuilderSDK::ProcessJobResult_Failed; + } + + AzslCompiler azslc(azslData.m_preprocessedFullPath); + if (!azslc.ParseIaPopulateStructData(jsonOutcome.GetValue(), vertexShaderName, inputStruct)) + { + AZ_Error(ShaderBuilderUtilityName, false, "Failed to parse input layout\n"); + return false; + } + + if (inputStruct.m_id.empty()) + { + AZ_Error( + ShaderBuilderUtilityName, false, "Failed to find the input struct for vertex shader %s.", + vertexShaderName.c_str()); + return false; + } + + for (const auto& member : inputStruct.m_members) + { + RHI::ShaderSemantic streamChannelSemantic{Name{member.m_semanticText}, static_cast(member.m_semanticIndex)}; + + // Semantics that represent a system-generated value do not map to an input stream + if (IsSystemValueSemantic(streamChannelSemantic.m_name.GetStringView())) + { + continue; + } + + contract.m_streamChannels.push_back(); + contract.m_streamChannels.back().m_semantic = streamChannelSemantic; + + if (member.m_variable.m_typeModifier == MatrixMajor::ColumnMajor) + { + contract.m_streamChannels.back().m_componentCount = member.m_variable.m_cols; + } + else + { + contract.m_streamChannels.back().m_componentCount = member.m_variable.m_rows; + } + + // [GFX_TODO][ATOM-14475]: Come up with a more elegant way to mark optional channels and their corresponding shader + // option + static const char OptionalInputStreamPrefix[] = "m_optional_"; + if (AzFramework::StringFunc::StartsWith(member.m_variable.m_name, OptionalInputStreamPrefix, true)) + { + AZStd::string expectedOptionName = AZStd::string::format( + "o_%s_isBound", member.m_variable.m_name.substr(strlen(OptionalInputStreamPrefix)).c_str()); + + RPI::ShaderOptionIndex shaderOptionIndex = shaderOptionGroupLayout.FindShaderOptionIndex(Name{expectedOptionName}); + if (!shaderOptionIndex.IsValid()) + { + AZ_Error( + ShaderBuilderUtilityName, false, "Shader option '%s' not found for optional input stream '%s'", + expectedOptionName.c_str(), member.m_variable.m_name.c_str()); + return false; + } + + const RPI::ShaderOptionDescriptor& option = shaderOptionGroupLayout.GetShaderOption(shaderOptionIndex); + if (option.GetType() != RPI::ShaderOptionType::Boolean) + { + AZ_Error(ShaderBuilderUtilityName, false, "Shader option '%s' must be a bool.", expectedOptionName.c_str()); + return false; + } + + if (option.GetDefaultValue().GetStringView() != "false") + { + AZ_Error( + ShaderBuilderUtilityName, false, "Shader option '%s' must default to false.", + expectedOptionName.c_str()); + return false; + } + + contract.m_streamChannels.back().m_isOptional = true; + contract.m_streamChannels.back().m_streamBoundIndicatorIndex = shaderOptionIndex; + } + } + + return true; + } + + static bool CreateShaderOutputContract( + const AzslData& azslData, + const AZStd::string& fragmentShaderName, + const AZStd::string& pathToOmJson, + RPI::ShaderOutputContract& contract) + { + StructData outputStruct; + outputStruct.m_id = ""; + + auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToOmJson); + if (!jsonOutcome.IsSuccess()) + { + AZ_Error(ShaderBuilderUtilityName, false, "%s", jsonOutcome.GetError().c_str()); + return AssetBuilderSDK::ProcessJobResult_Failed; + } + + AzslCompiler azslc(azslData.m_preprocessedFullPath); + if (!azslc.ParseOmPopulateStructData(jsonOutcome.GetValue(), fragmentShaderName, outputStruct)) + { + AZ_Error(ShaderBuilderUtilityName, false, "Failed to parse output layout\n"); + return false; + } + + for (const auto& member : outputStruct.m_members) + { + RHI::ShaderSemantic semantic = RHI::ShaderSemantic::Parse(member.m_semanticText); + + bool depthFound = false; + + if (semantic.m_name.GetStringView() == "SV_Target") + { + contract.m_requiredColorAttachments.push_back(); + // Render targets only support 1-D vector types and those are always column-major (per DXC) + contract.m_requiredColorAttachments.back().m_componentCount = member.m_variable.m_cols; + } + else if ( + semantic.m_name.GetStringView() == "SV_Depth" || semantic.m_name.GetStringView() == "SV_DepthGreaterEqual" || + semantic.m_name.GetStringView() == "SV_DepthLessEqual") + { + if (depthFound) + { + AZ_Error( + ShaderBuilderUtilityName, false, + "SV_Depth specified more than once in the fragment shader output structure"); + return false; + } + depthFound = true; + } + else + { + AZ_Error( + ShaderBuilderUtilityName, false, "Unsupported shader output semantic '%s'.", semantic.m_name.GetCStr()); + return false; + } + } + + return true; + } + + bool CreateShaderInputAndOutputContracts( + const AzslData& azslData, + const MapOfStringToStageType& shaderEntryPoints, + const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout, + const AZStd::string& pathToOmJson, + const AZStd::string& pathToIaJson, + RPI::ShaderInputContract& shaderInputContract, + RPI::ShaderOutputContract& shaderOutputContract, + size_t& colorAttachmentCount) + { + bool success = true; + for (const auto& shaderEntryPoint : shaderEntryPoints) + { + auto shaderEntryName = shaderEntryPoint.first; + auto shaderStageType = shaderEntryPoint.second; + + if (shaderStageType == RPI::ShaderStageType::Vertex) + { + const bool layoutCreated = CreateShaderInputContract(azslData, shaderEntryName, shaderOptionGroupLayout, pathToIaJson, shaderInputContract); + if (!layoutCreated) + { + success = false; + AZ_Error( + ShaderBuilderUtilityName, false, "Could not create the input contract for the vertex function %s", + shaderEntryName.c_str()); + continue; // Using continue to report all the errors found + } + } + + if (shaderStageType == RPI::ShaderStageType::Fragment) + { + const bool layoutCreated = + CreateShaderOutputContract(azslData, shaderEntryName, pathToOmJson, shaderOutputContract); + if (!layoutCreated) + { + success = false; + AZ_Error( + ShaderBuilderUtilityName, false, "Could not create the output contract for the fragment function %s", + shaderEntryName.c_str()); + continue; // Using continue to report all the errors found + } + + colorAttachmentCount = shaderOutputContract.m_requiredColorAttachments.size(); + } + } + return success; + } + + + //! Returns a list of acceptable default entry point names + static void GetAcceptableDefaultEntryPoints( + const AZStd::vector& azslFunctionDataList, + AZStd::unordered_map& defaultEntryPoints) + { + for (const auto& func : azslFunctionDataList) + { + if (!func.m_hasShaderStageVaryings) + { + // Not declaring any semantics for a shader entry is valid, but unusual. + // A shader entry with no semantics must be explicitly listed and won't be selected by default. + continue; + } + + if (func.m_name.starts_with("VS") || func.m_name.ends_with("VS")) + { + defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Vertex; + AZ_TracePrintf( + ShaderBuilderUtilityName, "Assuming \"%s\" is a valid Vertex shader entry point.\n", func.m_name.c_str()); + } + else if (func.m_name.starts_with("PS") || func.m_name.ends_with("PS")) + { + defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Fragment; + AZ_TracePrintf( + ShaderBuilderUtilityName, "Assuming \"%s\" is a valid Fragment shader entry point.\n", + func.m_name.c_str()); + } + else if (func.m_name.starts_with("CS") || func.m_name.ends_with("CS")) + { + defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Compute; + AZ_TracePrintf( + ShaderBuilderUtilityName, "Assuming \"%s\" is a valid Compute shader entry point.\n", func.m_name.c_str()); + } + } + } + + + // DEPRECATED [ATOM-15472 + //! Returns a list of acceptable default entry point names + //! This function + static void GetAcceptableDefaultEntryPoints( + const AzslData& azslData, AZStd::unordered_map& defaultEntryPoints) + { + return GetAcceptableDefaultEntryPoints(azslData.m_functions, defaultEntryPoints); + } + + + void GetDefaultEntryPointsFromFunctionDataList( + const AZStd::vector azslFunctionDataList, + AZStd::unordered_map& shaderEntryPoints) + { + AZStd::unordered_map defaultEntryPoints; + GetAcceptableDefaultEntryPoints(azslFunctionDataList, defaultEntryPoints); + + for (const auto& functionData : azslFunctionDataList) + { + for (const auto& defaultEntryPoint : defaultEntryPoints) + { + // Equal defaults to case insensitive compares... + if (AzFramework::StringFunc::Equal(defaultEntryPoint.first.c_str(), functionData.m_name.c_str())) + { + shaderEntryPoints[defaultEntryPoint.first] = defaultEntryPoint.second; + break; // stop looping default entry points and go to the next shader function + } + } + } + } + + AZStd::string GetAcceptableDefaultEntryPointNames(const AzslData& azslData) + { + AZStd::unordered_map defaultEntryPointList; + GetAcceptableDefaultEntryPoints(azslData, defaultEntryPointList); + + AZStd::vector defaultEntryPointNamesList; + for (const auto& shaderEntryPoint : defaultEntryPointList) + { + defaultEntryPointNamesList.push_back(shaderEntryPoint.first); + } + AZStd::string shaderEntryPoints; + AzFramework::StringFunc::Join( + shaderEntryPoints, defaultEntryPointNamesList.begin(), defaultEntryPointNamesList.end(), ", "); + return AZStd::move(shaderEntryPoints); + } + } // namespace ShaderBuilderUtility } // namespace ShaderBuilder } // AZ diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.h index d6926d0086..e31c6c70a1 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.h +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.h @@ -18,8 +18,10 @@ #include #include +#include #include +#include "AzslData.h" namespace AZ { @@ -27,7 +29,6 @@ namespace AZ { class AzslCompiler; struct ShaderFiles; - struct AzslData; struct BindingDependencies; struct RootConstantData; @@ -40,8 +41,6 @@ namespace AZ void GetAbsolutePathToAzslFile(const AZStd::string& shaderTemplatePathAndFile, AZStd::string specifiedShaderPathAndName, AZStd::string& absoluteShaderPath); - uint32_t MakeDebugByproductSubId(RHI::APIType apiType, const AZStd::string& productFileName); - //! Opens and read the .shader, returns expanded file paths AZStd::shared_ptr PrepareSourceInput( const char* builderName, @@ -54,13 +53,20 @@ namespace AZ using SubId = RPI::ShaderAssetSubId; // product sub id enumerators: - static constexpr SubId SubList[] = { SubId::PostPreprocessingPureAzsl, SubId::IaJson, SubId::OmJson, SubId::SrgJson, SubId::OptionsJson, SubId::BindingdepJson, SubId::GeneratedSource }; + static constexpr SubId SubList[] = {SubId::PostPreprocessingPureAzsl, + SubId::IaJson, + SubId::OmJson, + SubId::SrgJson, + SubId::OptionsJson, + SubId::BindingdepJson, + SubId::GeneratedHlslSource}; // in the same order, their file name suffix (they replicate what's in AzslcMain.cpp. and hlsl corresponds to what's in AzslBuilder.cpp) // a type to declare variables holding the full paths of their files using Paths = AZStd::fixed_vector; }; + //! [GFX TODO] [ATOM-15472] Deprecated, remove when this ticket is addressed. //! Collects and generates the necessary data for compiling a shader. //! @azslData must have paths correctly set. //! shaderOptionGroupLayout, azslData, srgAssets get the output data. @@ -74,6 +80,16 @@ namespace AZ RootConstantData& rootConstantData ); + //! Collects all the JSON files generated during AZSL compilation and loads the data as objects. + //! @azslData must have paths correctly set. + //! @azslData, @srgLayoutList, @shaderOptionGroupLayout, @bindingDependencies and @rootConstantData get the output data. + AssetBuilderSDK::ProcessJobResultCode PopulateAzslDataFromJsonFiles( + const char* builderName, const AzslSubProducts::Paths& pathOfJsonFiles, + const bool platformUsesRegisterSpaces, AzslData& azslData, + RPI::ShaderResourceGroupLayoutList& srgLayoutList, RPI::Ptr shaderOptionGroupLayout, + BindingDependencies& bindingDependencies, RootConstantData& rootConstantData); + + RHI::ShaderHardwareStage ToAssetBuilderShaderType(RPI::ShaderStageType stageType); //! Must be called before shaderPlatformInterface->CompilePlatformInternal() @@ -82,7 +98,7 @@ namespace AZ //! The pipeline layout descriptor is returned, but the same data will also be set into the @shaderPlatformInterface //! object, which is why it is important to call this method before calling shaderPlatformInterface->CompilePlatformInternal(). RHI::Ptr BuildPipelineLayoutDescriptorForApi( - const char* BuilderName, + const char* builderName, RHI::ShaderPlatformInterface* shaderPlatformInterface, BindingDependencies& bindingDependencies /*inout*/, const ShaderResourceGroupAssets& srgAssets, @@ -91,6 +107,33 @@ namespace AZ const RootConstantData* rootConstantData = nullptr ); + + //! Must be called before shaderPlatformInterface->CompilePlatformInternal() + //! This function will prune non entry functions from BindingDependencies and use the + //! rest of input data to create a pipeline layout descriptor. + //! The pipeline layout descriptor is returned, but the same data will also be set into the @shaderPlatformInterface + //! object, which is why it is important to call this method before calling shaderPlatformInterface->CompilePlatformInternal(). + RHI::Ptr BuildPipelineLayoutDescriptorForApi( + const char* builderName, + const RPI::ShaderResourceGroupLayoutList& srgLayoutList, + const MapOfStringToStageType& shaderEntryPoints, + const RHI::ShaderCompilerArguments& shaderCompilerArguments, + const RootConstantData& rootConstantData, + RHI::ShaderPlatformInterface* shaderPlatformInterface, + BindingDependencies& bindingDependencies /*inout*/); + + + bool CreateShaderInputAndOutputContracts( + const AzslData& azslData, const MapOfStringToStageType& shaderEntryPoints, + const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout, const AZStd::string& pathToOmJson, + const AZStd::string& pathToIaJson, RPI::ShaderInputContract& shaderInputContract, + RPI::ShaderOutputContract& shaderOutputContract, size_t& colorAttachmentCount); + + + //! Returns a list of acceptable default entry point names as a single string for debug messages. + AZStd::string GetAcceptableDefaultEntryPointNames(const AzslData& shaderData); + + //! Create a file from a string's content. //! That file will be named filename.api.azslin //! This is meant to be used at this stage: @@ -102,7 +145,8 @@ namespace AZ const AZStd::string& preprocessedCode, const AZStd::string& tempDirPath, const AZStd::string& preprocessedFileName, - const AZStd::string& apiTypeString = ""); + const AZStd::string& apiTypeString = "", + bool add2 = false); // [GFX TODO] Remove add2 when [ATOM-15472] //! Create a file from a string's content. //! That file will be named filename.api.azsl.prepend @@ -121,12 +165,30 @@ namespace AZ AZStd::string ExtractStemName(const char* path); AZStd::vector DiscoverValidShaderPlatformInterfaces(const AssetBuilderSDK::PlatformInfo& info); + AZStd::vector DiscoverEnabledShaderPlatformInterfaces( + const AssetBuilderSDK::PlatformInfo& info, const RPI::ShaderSourceData& shaderSourceData); + + // The idea is that the "Supervariants" json property is optional in .shader files, + // For cases when it is not specified, this function will return a vector with one item, the default, nameless, supervariant. + // If "Supervariants" is not empty, then this function will make sure the first supervariant in the list + // is the default, nameless, supervariant. + AZStd::vector GetSupervariantListFromShaderSourceData( + const RPI::ShaderSourceData& shaderSourceData); + + void GetDefaultEntryPointsFromFunctionDataList( + const AZStd::vector azslFunctionDataList, + AZStd::unordered_map& shaderEntryPoints); void LogProfilingData(const char* builderName, AZStd::string_view shaderPath); //! Job products sub id generation helper for AzslBuilder uint32_t MakeAzslBuildProductSubId(RPI::ShaderAssetSubId subId, RHI::APIType apiType); + //! Returns the asset path of a product artifact produced by ShaderAssetBuilder2. + Outcome ObtainBuildArtifactPathFromShaderAssetBuilder2( + const uint32_t rhiUniqueIndex, const AZStd::string& platformIdentifier, const AZStd::string& shaderJsonPath, + const uint32_t supervariantIndex, RPI::ShaderAssetSubId shaderAssetSubId); + //! Reconstructs the expected output product paths of the AzslBuilder (from the 2 arguments @azslSourceFullPath and @apiType) Outcome ObtainBuildArtifactsFromAzslBuilder(const char* builderName, const AZStd::string& azslSourceFullPath, RHI::APIType apiType, const AZStd::string& platform); diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp index 02cb8bd242..7114b50906 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp @@ -276,12 +276,6 @@ namespace AZ return LoadResult{LoadResult::Code::DeferredError, AZStd::string::format("ShaderSourceData file does not exist: %s.", shaderSourceFileFullPath.c_str())}; } - // Let's open the shader source, because We need the source code of its AZSL file - auto outcomeShaderData = ShaderBuilderUtility::LoadShaderDataJson(shaderSourceFileFullPath); - if (!outcomeShaderData.IsSuccess()) - { - return LoadResult{LoadResult::Code::DeferredError, AZStd::string::format("Failed to parse Shader Descriptor JSON: %s", outcomeShaderData.GetError().c_str())}; - } return LoadResult{LoadResult::Code::Success}; } // LoadShaderVariantListAndAzslSource @@ -420,15 +414,6 @@ namespace AZ return; } - if (jobParameters.find(ShouldExitEarlyFromProcessJobParam) != jobParameters.end()) - { - AZ_TracePrintf( - ShaderVariantAssetBuilderName, "Doing nothing on behalf of [%s] because it's been overriden by game project.", - jobParameters.at(ShaderVariantLoadErrorParam).c_str()); - response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; - return; - } - AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId); if (jobCancelListener.IsCancelled()) { @@ -589,7 +574,7 @@ namespace AZ if (shaderSourceDataDescriptor.m_programSettings.m_entryPoints.empty()) { AZ_TracePrintf(ShaderVariantAssetBuilderName, "ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n"); - ShaderVariantAssetBuilder::GetDefaultEntryPointsFromAzslData(azslData, shaderEntryPoints); + ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslData.m_functions, shaderEntryPoints); } else { @@ -778,7 +763,7 @@ namespace AZ } // Time to save the asset in the cache tmp folder. - const uint32_t productSubID = RPI::ShaderVariantAsset::GetAssetSubId(shaderPlatformInterface->GetAPIUniqueIndex(), shaderVariantAsset->GetStableId()); + const uint32_t productSubID = RPI::ShaderVariantAsset::MakeAssetProductSubId(shaderPlatformInterface->GetAPIUniqueIndex(), shaderVariantAsset->GetStableId()); AssetBuilderSDK::JobProduct assetProduct; if (!SerializeOutShaderVariantAsset(shaderVariantAsset, shaderSourceFileFullPath, request.m_tempDirPath, *shaderPlatformInterface, productSubID, assetProduct)) { @@ -788,12 +773,14 @@ namespace AZ response.m_outputProducts.push_back(assetProduct); // add byproducts as job output products: + uint32_t subProductType = aznumeric_cast(RPI::ShaderAssetSubId::GeneratedHlslSource) + 1; for (const AZStd::string& byproduct : byproducts.m_intermediatePaths) { AssetBuilderSDK::JobProduct jobProduct; jobProduct.m_productFileName = byproduct; jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt"); - jobProduct.m_productSubID = ShaderBuilderUtility::MakeDebugByproductSubId(shaderPlatformInterface->GetAPIType(), byproduct); + jobProduct.m_productSubID = RPI::ShaderVariantAsset::MakeAssetProductSubId( + shaderPlatformInterface->GetAPIType(), shaderVariantAsset->GetStableId(), subProductType++); response.m_outputProducts.push_back(AZStd::move(jobProduct)); } } @@ -801,53 +788,6 @@ namespace AZ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; } - - /// Returns a list of acceptable default entry point names - static void GetAcceptableDefaultEntryPoints(const AzslData& shaderData, AZStd::unordered_map& defaultEntryPoints) - { - for (const auto& func : shaderData.m_topData.m_functions) - { - if (!func.m_hasShaderStageVaryings) - { - // Not declaring any semantics for a shader entry is valid, but unusual. - // A shader entry with no semantics must be explicitly listed and won't be selected by default. - continue; - } - - if (func.m_name.starts_with("VS") || func.m_name.ends_with("VS")) - { - defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Vertex; - AZ_TracePrintf(ShaderVariantAssetBuilderName, "Assuming \"%s\" is a valid Vertex shader entry point.\n", func.m_name.c_str()); - } - else if (func.m_name.starts_with("PS") || func.m_name.ends_with("PS")) - { - defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Fragment; - AZ_TracePrintf(ShaderVariantAssetBuilderName, "Assuming \"%s\" is a valid Fragment shader entry point.\n", func.m_name.c_str()); - } - else if (func.m_name.starts_with("CS") || func.m_name.ends_with("CS")) - { - defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Compute; - AZ_TracePrintf(ShaderVariantAssetBuilderName, "Assuming \"%s\" is a valid Compute shader entry point.\n", func.m_name.c_str()); - } - } - } - - /// Returns a list of acceptable default entry point names as a single string for messages - static AZStd::string GetAcceptableDefaultEntryPointNames(const AzslData& shaderData) - { - AZStd::unordered_map defaultEntryPointList; - GetAcceptableDefaultEntryPoints(shaderData, defaultEntryPointList); - - AZStd::vector defaultEntryPointNamesList; - for (const auto& shaderEntryPoint : defaultEntryPointList) - { - defaultEntryPointNamesList.push_back(shaderEntryPoint.first); - } - AZStd::string shaderEntryPoints; - AzFramework::StringFunc::Join(shaderEntryPoints, defaultEntryPointNamesList.begin(), defaultEntryPointNamesList.end(), ", "); - return AZStd::move(shaderEntryPoints); - } - static bool CreateShaderVariant( ShaderVariantCreationContext& variantCreationContext, const AzslData& azslData, @@ -945,7 +885,7 @@ namespace AZ if (!hasRasterProgram && !hasComputeProgram && !hasRayTracingProgram) { - AZStd::string entryPointNames = GetAcceptableDefaultEntryPointNames(azslData); + AZStd::string entryPointNames = ShaderBuilderUtility::GetAcceptableDefaultEntryPointNames(azslData); AZ_Error(ShaderVariantAssetBuilderName, false, "Shader asset descriptor has a program variant that does not define any entry points. Either declare entry points in the .shader file, or use one of the available default names (not case-sensitive): [%s]", entryPointNames.data()); @@ -990,198 +930,6 @@ namespace AZ return isVariantValid; } - static bool IsSystemValueSemantic(const AZStd::string_view semantic) - { - // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics#system-value-semantics - return AzFramework::StringFunc::StartsWith(semantic, "sv_", false); - } - - static bool CreateShaderInputContract( - const AzslData& azslData, - const AZStd::string& vertexShaderName, - const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout, - RPI::ShaderInputContract& contract, - const AZStd::string& pathToIaJson) - { - StructData inputStruct; - inputStruct.m_id = ""; - - auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToIaJson); - if (!jsonOutcome.IsSuccess()) - { - AZ_Error(ShaderVariantAssetBuilderName, false, "%s", jsonOutcome.GetError().c_str()); - return AssetBuilderSDK::ProcessJobResult_Failed; - } - - AzslCompiler azslc(azslData.m_preprocessedFullPath); - if (!azslc.ParseIaPopulateStructData(jsonOutcome.GetValue(), vertexShaderName, inputStruct)) - { - AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to parse input layout\n"); - return false; - } - - if (inputStruct.m_id.empty()) - { - AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to find the input struct for vertex shader %s.", vertexShaderName.c_str()); - return false; - } - - for (const auto& member : inputStruct.m_members) - { - RHI::ShaderSemantic streamChannelSemantic{ - Name{ member.m_semanticText }, - static_cast(member.m_semanticIndex) }; - - // Semantics that represent a system-generated value do not map to an input stream - if (IsSystemValueSemantic(streamChannelSemantic.m_name.GetStringView())) - { - continue; - } - - contract.m_streamChannels.push_back(); - contract.m_streamChannels.back().m_semantic = streamChannelSemantic; - - if (member.m_variable.m_typeModifier == MatrixMajor::ColumnMajor) - { - contract.m_streamChannels.back().m_componentCount = member.m_variable.m_cols; - } - else - { - contract.m_streamChannels.back().m_componentCount = member.m_variable.m_rows; - } - - // [GFX_TODO][ATOM-14475]: Come up with a more elegant way to mark optional channels and their corresponding shader option - static const char OptionalInputStreamPrefix[] = "m_optional_"; - if (AzFramework::StringFunc::StartsWith(member.m_variable.m_name, OptionalInputStreamPrefix, true)) - { - AZStd::string expectedOptionName = AZStd::string::format("o_%s_isBound", member.m_variable.m_name.substr(strlen(OptionalInputStreamPrefix)).c_str()); - - RPI::ShaderOptionIndex shaderOptionIndex = shaderOptionGroupLayout.FindShaderOptionIndex(Name{expectedOptionName}); - if (!shaderOptionIndex.IsValid()) - { - AZ_Error(ShaderVariantAssetBuilderName, false, "Shader option '%s' not found for optional input stream '%s'", expectedOptionName.c_str(), member.m_variable.m_name.c_str()); - return false; - } - - const RPI::ShaderOptionDescriptor& option = shaderOptionGroupLayout.GetShaderOption(shaderOptionIndex); - if (option.GetType() != RPI::ShaderOptionType::Boolean) - { - AZ_Error(ShaderVariantAssetBuilderName, false, "Shader option '%s' must be a bool.", expectedOptionName.c_str()); - return false; - } - - if (option.GetDefaultValue().GetStringView() != "false") - { - AZ_Error(ShaderVariantAssetBuilderName, false, "Shader option '%s' must default to false.", expectedOptionName.c_str()); - return false; - } - - contract.m_streamChannels.back().m_isOptional = true; - contract.m_streamChannels.back().m_streamBoundIndicatorIndex = shaderOptionIndex; - } - } - - return true; - } - - static bool CreateShaderOutputContract( - const AzslData& azslData, - const AZStd::string& fragmentShaderName, - RPI::ShaderOutputContract& contract, - const AZStd::string& pathToOmJson) - { - StructData outputStruct; - outputStruct.m_id = ""; - - auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToOmJson); - if (!jsonOutcome.IsSuccess()) - { - AZ_Error(ShaderVariantAssetBuilderName, false, "%s", jsonOutcome.GetError().c_str()); - return AssetBuilderSDK::ProcessJobResult_Failed; - } - - AzslCompiler azslc(azslData.m_preprocessedFullPath); - if (!azslc.ParseOmPopulateStructData(jsonOutcome.GetValue(), fragmentShaderName, outputStruct)) - { - AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to parse output layout\n"); - return false; - } - - for (const auto& member : outputStruct.m_members) - { - RHI::ShaderSemantic semantic = RHI::ShaderSemantic::Parse(member.m_semanticText); - - bool depthFound = false; - - if (semantic.m_name.GetStringView() == "SV_Target") - { - contract.m_requiredColorAttachments.push_back(); - // Render targets only support 1-D vector types and those are always column-major (per DXC) - contract.m_requiredColorAttachments.back().m_componentCount = member.m_variable.m_cols; - } - else if (semantic.m_name.GetStringView() == "SV_Depth" || - semantic.m_name.GetStringView() == "SV_DepthGreaterEqual" || - semantic.m_name.GetStringView() == "SV_DepthLessEqual") - { - if (depthFound) - { - AZ_Error(ShaderVariantAssetBuilderName, false, "SV_Depth specified more than once in the fragment shader output structure"); - return false; - } - depthFound = true; - } - else - { - AZ_Error(ShaderVariantAssetBuilderName, false, "Unsupported shader output semantic '%s'.", semantic.m_name.GetCStr()); - return false; - } - } - - return true; - } - - static bool CreateShaderInputAndOutputContracts( - const AzslData& azslData, - const MapOfStringToStageType& shaderEntryPoints, - const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout, - RPI::ShaderInputContract& shaderInputContract, - RPI::ShaderOutputContract& shaderOutputContract, - size_t& colorAttachmentCount, - const AZStd::string& pathToOmJson, - const AZStd::string& pathToIaJson) - { - bool success = true; - for (const auto& shaderEntryPoint : shaderEntryPoints) - { - auto shaderEntryName = shaderEntryPoint.first; - auto shaderStageType = shaderEntryPoint.second; - - if (shaderStageType == RPI::ShaderStageType::Vertex) - { - const bool layoutCreated = CreateShaderInputContract(azslData, shaderEntryName, shaderOptionGroupLayout, shaderInputContract, pathToIaJson); - if (!layoutCreated) - { - success = false; - AZ_Error(ShaderVariantAssetBuilderName, false, "Could not create the input contract for the vertex function %s", shaderEntryName.c_str()); - continue; // Using continue to report all the errors found - } - } - - if (shaderStageType == RPI::ShaderStageType::Fragment) - { - const bool layoutCreated = CreateShaderOutputContract(azslData, shaderEntryName, shaderOutputContract, pathToOmJson); - if (!layoutCreated) - { - success = false; - AZ_Error(ShaderVariantAssetBuilderName, false, "Could not create the output contract for the fragment function %s", shaderEntryName.c_str()); - continue; // Using continue to report all the errors found - } - - colorAttachmentCount = shaderOutputContract.m_requiredColorAttachments.size(); - } - } - return success; - } AZ::Outcome, AZStd::string> ShaderVariantAssetBuilder::CreateShaderVariantAssetForAPI( const RPI::ShaderVariantListSourceData::VariantInfo& variantInfo, @@ -1195,8 +943,8 @@ namespace AZ RPI::ShaderInputContract shaderInputContract; RPI::ShaderOutputContract shaderOutputContract; size_t colorAttachmentCount = 0; - CreateShaderInputAndOutputContracts(azslData, variantCreationContext.m_shaderEntryPoints, variantCreationContext.m_shaderOptionGroupLayout, - shaderInputContract, shaderOutputContract, colorAttachmentCount, pathToOmJson, pathToIaJson); + ShaderBuilderUtility::CreateShaderInputAndOutputContracts(azslData, variantCreationContext.m_shaderEntryPoints, variantCreationContext.m_shaderOptionGroupLayout, pathToOmJson, + pathToIaJson, shaderInputContract, shaderOutputContract, colorAttachmentCount); const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout = variantCreationContext.m_shaderOptionGroupLayout; // Temporary structure used for sorting and caching intermediate results @@ -1284,25 +1032,6 @@ namespace AZ return AZ::Success(AZStd::move(shaderVariantAsset)); } - void ShaderVariantAssetBuilder::GetDefaultEntryPointsFromAzslData(const AzslData& shaderData, AZStd::unordered_map& shaderEntryPoints) - { - AZStd::unordered_map defaultEntryPoints; - GetAcceptableDefaultEntryPoints(shaderData, defaultEntryPoints); - - for (const auto& functionData : shaderData.m_topData.m_functions) - { - for (const auto& defaultEntryPoint : defaultEntryPoints) - { - // Equal defaults to case insensitive compares... - if (AzFramework::StringFunc::Equal(defaultEntryPoint.first.c_str(), functionData.m_name.c_str())) - { - shaderEntryPoints[defaultEntryPoint.first] = defaultEntryPoint.second; - break; // stop looping default entry points and go to the next shader function - } - } - } - } - bool ShaderVariantAssetBuilder::SerializeOutShaderVariantAsset(const Data::Asset shaderVariantAsset, const AZStd::string& shaderSourceFileFullPath, const AZStd::string& tempDirPath, const RHI::ShaderPlatformInterface& shaderPlatformInterface, const uint32_t productSubID, AssetBuilderSDK::JobProduct& assetProduct) { diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.h index 7dd76f7ef1..84ab4fbc70 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.h +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.h @@ -73,8 +73,6 @@ namespace AZ static bool SerializeOutShaderVariantAsset(const Data::Asset shaderVariantAsset, const AZStd::string& shaderFullPath, const AZStd::string& tempDirPath, const RHI::ShaderPlatformInterface& shaderPlatformInterface, const uint32_t productSubID, AssetBuilderSDK::JobProduct& assetProduct); - static void GetDefaultEntryPointsFromAzslData(const AzslData& shaderData, AZStd::unordered_map& shaderEntryPoints); - // AssetBuilderSDK::AssetBuilderCommandBus interface overrides ... void ShutDown() override { }; diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder2.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder2.cpp new file mode 100644 index 0000000000..961e54c7a8 --- /dev/null +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder2.cpp @@ -0,0 +1,978 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ShaderAssetBuilder2.h" +#include "ShaderBuilderUtility.h" +#include "AzslData.h" +#include "AzslCompiler.h" +#include "AzslBuilder.h" +#include +#include +#include +#include "AtomShaderConfig.h" + +namespace AZ +{ + namespace ShaderBuilder + { + static constexpr char ShaderVariantAssetBuilder2Name[] = "ShaderVariantAssetBuilder2"; + + static void AddShaderAssetJobDependency2( + AssetBuilderSDK::JobDescriptor& jobDescriptor, const AssetBuilderSDK::PlatformInfo& platformInfo, + const AZStd::string& shaderVariantListFilePath, const AZStd::string& shaderFilePath) + { + AZStd::vector possibleDependencies = + AZ::RPI::AssetUtils::GetPossibleDepenencyPaths(shaderVariantListFilePath, shaderFilePath); + for (auto& file : possibleDependencies) + { + AssetBuilderSDK::JobDependency jobDependency; + jobDependency.m_jobKey = ShaderAssetBuilder2::ShaderAssetBuilder2JobKey; + jobDependency.m_platformIdentifier = platformInfo.m_identifier; + jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order; + jobDependency.m_sourceFile.m_sourceFileDependencyPath = file; + jobDescriptor.m_jobDependencyList.push_back(jobDependency); + } + } + + //! Returns true if @sourceFileFullPath starts with a valid asset processor scan folder, false otherwise. + //! In case of true, it splits @sourceFileFullPath into @scanFolderFullPath and @filePathFromScanFolder. + //! @sourceFileFullPath The full path to a source asset file. + //! @scanFolderFullPath [out] Gets the full path of the scan folder where the source file is located. + //! @filePathFromScanFolder [out] Get the file path relative to @scanFolderFullPath. + static bool SplitSourceAssetPathIntoScanFolderFullPathAndRelativeFilePath2(const AZStd::string& sourceFileFullPath, AZStd::string& scanFolderFullPath, AZStd::string& filePathFromScanFolder) + { + AZStd::vector scanFolders; + bool success = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(success, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetAssetSafeFolders, scanFolders); + if (!success) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Couldn't get the scan folders"); + return false; + } + + for (AZStd::string scanFolder : scanFolders) + { + AzFramework::StringFunc::Path::Normalize(scanFolder); + if (!AZ::StringFunc::StartsWith(sourceFileFullPath, scanFolder)) + { + continue; + } + const size_t scanFolderSize = scanFolder.size(); + const size_t sourcePathSize = sourceFileFullPath.size(); + scanFolderFullPath = scanFolder; + filePathFromScanFolder = sourceFileFullPath.substr(scanFolderSize + 1, sourcePathSize - scanFolderSize - 1); + return true; + } + + return false; + } + + //! Validates if a given .shadervariantlist file is located at the correct path for a given .shader full path. + //! There are two valid paths: + //! 1- Lower Precedence: The same folder where the .shader file is located. + //! 2- Higher Precedence: //ShaderVariants/. + //! The "Higher Precedence" path gives the option to game projects to override what variants to generate. If this + //! file exists then the "Lower Precedence" path is disregarded. + //! A .shader full path is located under an AP scan folder. + //! Example: "/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.shader" + //! - In this example the Scan Folder is "/Gems/Atom/Feature/Common/Assets", while the subfolder is "Materials/Types". + //! The "Higher Precedence" expected valid location for the .shadervariantlist would be: + //! - //ShaderVariants/Materials/Types/StandardPBR_ForwardPass.shadervariantlist. + //! The "Lower Precedence" valid location would be: + //! - /Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.shadervariantlist. + //! @shouldExitEarlyFromProcessJob [out] Set to true if ProcessJob should do no work but return successfully. + //! Set to false if ProcessJob should do work and create assets. + //! When @shaderVariantListFileFullPath is provided by a Gem/Feature instead of the Game Project + //! We check if the game project already defined the shader variant list, and if it did it means + //! ProcessJob should do no work, but return successfully nonetheless. + static bool ValidateShaderVariantListLocation2(const AZStd::string& shaderVariantListFileFullPath, + const AZStd::string& shaderFileFullPath, bool& shouldExitEarlyFromProcessJob) + { + AZStd::string scanFolderFullPath; + AZStd::string shaderProductFileRelativePath; + if (!SplitSourceAssetPathIntoScanFolderFullPathAndRelativeFilePath2(shaderFileFullPath, scanFolderFullPath, shaderProductFileRelativePath)) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Couldn't get the scan folder for shader [%s]", shaderFileFullPath.c_str()); + return false; + } + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "For shader [%s], Scan folder full path [%s], relative file path [%s]", shaderFileFullPath.c_str(), scanFolderFullPath.c_str(), shaderProductFileRelativePath.c_str()); + + AZStd::string shaderVariantListFileRelativePath = shaderProductFileRelativePath; + AzFramework::StringFunc::Path::ReplaceExtension(shaderVariantListFileRelativePath, RPI::ShaderVariantListSourceData::Extension); + + const char * gameProjectPath = nullptr; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(gameProjectPath, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetAbsoluteDevGameFolderPath); + + AZStd::string expectedHigherPrecedenceFileFullPath; + AzFramework::StringFunc::Path::Join(gameProjectPath, RPI::ShaderVariantTreeAsset::CommonSubFolder, expectedHigherPrecedenceFileFullPath, false /* handle directory overlap? */, false /* be case insensitive? */); + AzFramework::StringFunc::Path::Join(expectedHigherPrecedenceFileFullPath.c_str(), shaderProductFileRelativePath.c_str(), expectedHigherPrecedenceFileFullPath, false /* handle directory overlap? */, false /* be case insensitive? */); + AzFramework::StringFunc::Path::ReplaceExtension(expectedHigherPrecedenceFileFullPath, AZ::RPI::ShaderVariantListSourceData::Extension); + AzFramework::StringFunc::Path::Normalize(expectedHigherPrecedenceFileFullPath); + + AZStd::string normalizedShaderVariantListFileFullPath = shaderVariantListFileFullPath; + AzFramework::StringFunc::Path::Normalize(normalizedShaderVariantListFileFullPath); + + if (expectedHigherPrecedenceFileFullPath == normalizedShaderVariantListFileFullPath) + { + // Whenever the Game Project declares a *.shadervariantlist file we always do work. + shouldExitEarlyFromProcessJob = false; + return true; + } + + AZ::Data::AssetInfo assetInfo; + AZStd::string watchFolder; + bool foundHigherPrecedenceAsset = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(foundHigherPrecedenceAsset + , &AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourcePath + , expectedHigherPrecedenceFileFullPath.c_str(), assetInfo, watchFolder); + if (foundHigherPrecedenceAsset) + { + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "The shadervariantlist [%s] has been overriden by the game project with [%s]", + normalizedShaderVariantListFileFullPath.c_str(), expectedHigherPrecedenceFileFullPath.c_str()); + shouldExitEarlyFromProcessJob = true; + return true; + } + + // Check the "Lower Precedence" case, .shader path == .shadervariantlist path. + AZStd::string normalizedShaderFileFullPath = shaderFileFullPath; + AzFramework::StringFunc::Path::Normalize(normalizedShaderFileFullPath); + + AZStd::string normalizedShaderFileFullPathWithoutExtension = normalizedShaderFileFullPath; + AzFramework::StringFunc::Path::StripExtension(normalizedShaderFileFullPathWithoutExtension); + + AZStd::string normalizedShaderVariantListFileFullPathWithoutExtension = normalizedShaderVariantListFileFullPath; + AzFramework::StringFunc::Path::StripExtension(normalizedShaderVariantListFileFullPathWithoutExtension); + +#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS + //In certain circumstances, the capitalization of the drive letter may not match + const bool caseSensitive = false; +#else + //On the other platforms there's no drive letter, so it should be a non-issue. + const bool caseSensitive = true; +#endif + if (!StringFunc::Equal(normalizedShaderFileFullPathWithoutExtension.c_str(), normalizedShaderVariantListFileFullPathWithoutExtension.c_str(), caseSensitive)) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "For shader file at path [%s], the shader variant list [%s] is expected to be located at [%s.%s] or [%s]" + , normalizedShaderFileFullPath.c_str(), normalizedShaderVariantListFileFullPath.c_str(), + normalizedShaderFileFullPathWithoutExtension.c_str(), RPI::ShaderVariantListSourceData::Extension, + expectedHigherPrecedenceFileFullPath.c_str()); + return false; + } + + shouldExitEarlyFromProcessJob = false; + return true; + } + + // We treat some issues as warnings and return "Success" from CreateJobs allows us to report the dependency. + // If/when a valid dependency file appears, that will trigger the ShaderVariantAssetBuilder2 to run again. + // Since CreateJobs will pass, we forward this message to ProcessJob which will report it as an error. + struct LoadResult2 + { + enum class Code + { + Error, + DeferredError, + Success + }; + + Code m_code; + AZStd::string m_deferredMessage; // Only used when m_code == DeferredError + }; + + static LoadResult2 LoadShaderVariantList2(const AZStd::string& variantListFullPath, RPI::ShaderVariantListSourceData& shaderVariantList, AZStd::string& shaderSourceFileFullPath, + bool& shouldExitEarlyFromProcessJob) + { + // Need to get the name of the shader file from the template so that we can preprocess the shader data and setup + // source file dependencies. + if (!RPI::JsonUtils::LoadObjectFromFile(variantListFullPath, shaderVariantList)) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to parse Shader Variant List Descriptor JSON from [%s]", variantListFullPath.c_str()); + return LoadResult2{LoadResult2::Code::Error}; + } + + const AZStd::string resolvedShaderPath = AZ::RPI::AssetUtils::ResolvePathReference(variantListFullPath, shaderVariantList.m_shaderFilePath); + if (!AZ::IO::LocalFileIO::GetInstance()->Exists(resolvedShaderPath.c_str())) + { + return LoadResult2{LoadResult2::Code::DeferredError, AZStd::string::format("The shader path [%s] was not found.", resolvedShaderPath.c_str())}; + } + + shaderSourceFileFullPath = resolvedShaderPath; + + if (!ValidateShaderVariantListLocation2(variantListFullPath, shaderSourceFileFullPath, shouldExitEarlyFromProcessJob)) + { + return LoadResult2{LoadResult2::Code::Error}; + } + + if (shouldExitEarlyFromProcessJob) + { + return LoadResult2{LoadResult2::Code::Success}; + } + + auto resultOutcome = RPI::ShaderVariantTreeAssetCreator::ValidateStableIdsAreUnique(shaderVariantList.m_shaderVariants); + if (!resultOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Variant info validation error: %s", resultOutcome.GetError().c_str()); + return LoadResult2{LoadResult2::Code::Error}; + } + + if (!IO::FileIOBase::GetInstance()->Exists(shaderSourceFileFullPath.c_str())) + { + return LoadResult2{LoadResult2::Code::DeferredError, AZStd::string::format("ShaderSourceData file does not exist: %s.", shaderSourceFileFullPath.c_str())}; + } + + return LoadResult2{LoadResult2::Code::Success}; + } // LoadShaderVariantListAndAzslSource + + void ShaderVariantAssetBuilder2::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const + { + AZStd::string variantListFullPath; + AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), variantListFullPath, true); + + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "CreateJobs for Shader Variant List \"%s\"\n", variantListFullPath.data()); + + RPI::ShaderVariantListSourceData shaderVariantList; + AZStd::string shaderSourceFileFullPath; + bool shouldExitEarlyFromProcessJob = false; + const LoadResult2 loadResult = LoadShaderVariantList2(variantListFullPath, shaderVariantList, shaderSourceFileFullPath, shouldExitEarlyFromProcessJob); + + if (loadResult.m_code == LoadResult2::Code::Error) + { + response.m_result = AssetBuilderSDK::CreateJobsResultCode::Failed; + return; + } + + if (loadResult.m_code == LoadResult2::Code::DeferredError || shouldExitEarlyFromProcessJob) + { + for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms) + { + // Let's create fake jobs that will fail ProcessJob, but are useful to establish dependency on the shader file. + AssetBuilderSDK::JobDescriptor jobDescriptor; + + jobDescriptor.m_priority = -5000; + jobDescriptor.m_critical = false; + jobDescriptor.m_jobKey = ShaderVariantAssetBuilder2JobKey; + jobDescriptor.SetPlatformIdentifier(info.m_identifier.data()); + + AddShaderAssetJobDependency2(jobDescriptor, info, variantListFullPath, shaderVariantList.m_shaderFilePath); + + if (loadResult.m_code == LoadResult2::Code::DeferredError) + { + jobDescriptor.m_jobParameters.emplace(ShaderVariantLoadErrorParam, loadResult.m_deferredMessage); + } + + if (shouldExitEarlyFromProcessJob) + { + // The value doesn't matter, what matters is the presence of the key which will + // signal that no assets should be produced on behalf of this shadervariantlist because + // the game project overrode it. + jobDescriptor.m_jobParameters.emplace(ShouldExitEarlyFromProcessJobParam, variantListFullPath); + } + + response.m_createJobOutputs.push_back(jobDescriptor); + } + response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success; + return; + } + + for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms) + { + AZ_TraceContext("For platform", info.m_identifier.data()); + + // First job is for the ShaderVariantTreeAsset. + { + AssetBuilderSDK::JobDescriptor jobDescriptor; + + // The ShaderVariantTreeAsset is high priority, but must be generated after the ShaderAsset + jobDescriptor.m_priority = 1; + jobDescriptor.m_critical = false; + + jobDescriptor.m_jobKey = GetShaderVariantTreeAssetJobKey(); + jobDescriptor.SetPlatformIdentifier(info.m_identifier.data()); + + AddShaderAssetJobDependency2(jobDescriptor, info, variantListFullPath, shaderVariantList.m_shaderFilePath); + + jobDescriptor.m_jobParameters.emplace(ShaderSourceFilePathJobParam, shaderSourceFileFullPath); + + response.m_createJobOutputs.push_back(jobDescriptor); + } + + // One job for each variant. Each job will produce one ".azshadervariant" per RHI per supervariant. + for (const AZ::RPI::ShaderVariantListSourceData::VariantInfo& variantInfo : shaderVariantList.m_shaderVariants) + { + AZStd::string variantInfoAsJsonString; + const bool convertSuccess = AZ::RPI::JsonUtils::SaveObjectToJsonString(variantInfo, variantInfoAsJsonString); + AZ_Assert(convertSuccess, "Failed to convert VariantInfo to json string"); + + AssetBuilderSDK::JobDescriptor jobDescriptor; + + // There can be tens/hundreds of thousands of shader variants. By default each shader will get + // a root variant that can be used at runtime. In order to prevent the AssetProcessor from + // being overtaken by shader variant compilation We mark all non-root shader variant generation + // as non critical and very low priority. + jobDescriptor.m_priority = -5000; + jobDescriptor.m_critical = false; + + jobDescriptor.m_jobKey = GetShaderVariantAssetJobKey(RPI::ShaderVariantStableId{variantInfo.m_stableId}); + jobDescriptor.SetPlatformIdentifier(info.m_identifier.data()); + + // The ShaderVariantAssets are job dependent on the ShaderVariantTreeAsset. + AssetBuilderSDK::SourceFileDependency fileDependency; + fileDependency.m_sourceFileDependencyPath = variantListFullPath; + AssetBuilderSDK::JobDependency variantTreeJobDependency; + variantTreeJobDependency.m_jobKey = GetShaderVariantTreeAssetJobKey(); + variantTreeJobDependency.m_platformIdentifier = info.m_identifier; + variantTreeJobDependency.m_sourceFile = fileDependency; + variantTreeJobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order; + jobDescriptor.m_jobDependencyList.emplace_back(variantTreeJobDependency); + + jobDescriptor.m_jobParameters.emplace(ShaderVariantJobVariantParam, variantInfoAsJsonString); + jobDescriptor.m_jobParameters.emplace(ShaderSourceFilePathJobParam, shaderSourceFileFullPath); + + response.m_createJobOutputs.push_back(jobDescriptor); + } + + } + response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success; + } // CreateJobs + + void ShaderVariantAssetBuilder2::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const + { + const auto& jobParameters = request.m_jobDescription.m_jobParameters; + + if (jobParameters.find(ShaderVariantLoadErrorParam) != jobParameters.end()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Error during CreateJobs: %s", jobParameters.at(ShaderVariantLoadErrorParam).c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + if (jobParameters.find(ShouldExitEarlyFromProcessJobParam) != jobParameters.end()) + { + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Doing nothing on behalf of [%s] because it's been overridden by game project.", jobParameters.at(ShaderVariantLoadErrorParam).c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; + return; + } + + AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId); + if (jobCancelListener.IsCancelled()) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled; + return; + } + + if (request.m_jobDescription.m_jobKey == GetShaderVariantTreeAssetJobKey()) + { + ProcessShaderVariantTreeJob(request, response); + } + else + { + ProcessShaderVariantJob(request, response); + } + } + + + static RPI::Ptr LoadShaderOptionsGroupLayoutFromShaderAssetBuilder2( + const RHI::ShaderPlatformInterface* shaderPlatformInterface, + const AssetBuilderSDK::PlatformInfo& platformInfo, + const AzslCompiler& azslCompiler, + const AZStd::string& shaderSourceFileFullPath, + const RPI::SupervariantIndex supervariantIndex) + { + auto optionsGroupPathOutcome = ShaderBuilderUtility::ObtainBuildArtifactPathFromShaderAssetBuilder2( + shaderPlatformInterface->GetAPIUniqueIndex(), platformInfo.m_identifier, shaderSourceFileFullPath, supervariantIndex.GetIndex(), + AZ::RPI::ShaderAssetSubId::OptionsJson); + if (!optionsGroupPathOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", optionsGroupPathOutcome.GetError().c_str()); + return nullptr; + } + auto optionsGroupJsonPath = optionsGroupPathOutcome.TakeValue(); + RPI::Ptr shaderOptionGroupLayout = RPI::ShaderOptionGroupLayout::Create(); + // The shader options define what options are available, what are the allowed values/range + // for each option and what is its default value. + auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(optionsGroupJsonPath); + if (!jsonOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", jsonOutcome.GetError().c_str()); + return nullptr; + } + if (!azslCompiler.ParseOptionsPopulateOptionGroupLayout(jsonOutcome.GetValue(), shaderOptionGroupLayout)) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to find a valid list of shader options!"); + return nullptr; + } + + return shaderOptionGroupLayout; + } + + static void LoadShaderFunctionsFromShaderAssetBuilder2( + const RHI::ShaderPlatformInterface* shaderPlatformInterface, const AssetBuilderSDK::PlatformInfo& platformInfo, + const AzslCompiler& azslCompiler, const AZStd::string& shaderSourceFileFullPath, + const RPI::SupervariantIndex supervariantIndex, + AzslFunctions& functions) + { + auto functionsJsonPathOutcome = ShaderBuilderUtility::ObtainBuildArtifactPathFromShaderAssetBuilder2( + shaderPlatformInterface->GetAPIUniqueIndex(), platformInfo.m_identifier, shaderSourceFileFullPath, supervariantIndex.GetIndex(), + AZ::RPI::ShaderAssetSubId::IaJson); + if (!functionsJsonPathOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", functionsJsonPathOutcome.GetError().c_str()); + return; + } + + auto functionsJsonPath = functionsJsonPathOutcome.TakeValue(); + auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(functionsJsonPath); + if (!jsonOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", jsonOutcome.GetError().c_str()); + return; + } + if (!azslCompiler.ParseIaPopulateFunctionData(jsonOutcome.GetValue(), functions)) + { + functions.clear(); + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to find shader functions."); + return; + } + } + + + // Returns the content of the hlsl file for the given supervariant as produced by ShaderAsssetBuilder2. + // In addition to the content it also returns the full path of the hlsl file in @hlslSourcePath. + static AZStd::string LoadHlslFileFromShaderAssetBuilder2( + const RHI::ShaderPlatformInterface* shaderPlatformInterface, const AssetBuilderSDK::PlatformInfo& platformInfo, + const AZStd::string& shaderSourceFileFullPath, const RPI::SupervariantIndex supervariantIndex, AZStd::string& hlslSourcePath) + { + auto hlslSourcePathOutcome = ShaderBuilderUtility::ObtainBuildArtifactPathFromShaderAssetBuilder2( + shaderPlatformInterface->GetAPIUniqueIndex(), platformInfo.m_identifier, shaderSourceFileFullPath, supervariantIndex.GetIndex(), + AZ::RPI::ShaderAssetSubId::GeneratedHlslSource); + if (!hlslSourcePathOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", hlslSourcePathOutcome.GetError().c_str()); + return ""; + } + + hlslSourcePath = hlslSourcePathOutcome.TakeValue(); + Outcome hlslSourceOutcome = Utils::ReadFile(hlslSourcePath); + if (!hlslSourceOutcome.IsSuccess()) + { + AZ_Error( + ShaderVariantAssetBuilder2Name, false, "Failed to obtain shader source from %s. [%s]", hlslSourcePath.c_str(), + hlslSourceOutcome.TakeError().c_str()); + return ""; + } + return hlslSourceOutcome.TakeValue(); + } + + void ShaderVariantAssetBuilder2::ProcessShaderVariantTreeJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const + { + AZStd::string variantListFullPath; + AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), variantListFullPath, true); + + RPI::ShaderVariantListSourceData shaderVariantListDescriptor; + if (!RPI::JsonUtils::LoadObjectFromFile(variantListFullPath, shaderVariantListDescriptor)) + { + AZ_Assert(false, "Failed to parse Shader Variant List Descriptor JSON [%s]", variantListFullPath.c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + const AZStd::string& shaderSourceFileFullPath = request.m_jobDescription.m_jobParameters.at(ShaderSourceFilePathJobParam); + + //For debugging purposes will create a dummy azshadervarianttree file. + AZStd::string shaderName; + AzFramework::StringFunc::Path::GetFileName(shaderSourceFileFullPath.c_str(), shaderName); + + // No error checking because the same calls were already executed during CreateJobs() + auto descriptorParseOutcome = ShaderBuilderUtility::LoadShaderDataJson(shaderSourceFileFullPath); + RPI::ShaderSourceData shaderSourceDescriptor = descriptorParseOutcome.TakeValue(); + RPI::Ptr shaderOptionGroupLayout; + + // Request the list of valid shader platform interfaces for the target platform. + AZStd::vector platformInterfaces = + ShaderBuilderUtility::DiscoverEnabledShaderPlatformInterfaces(request.m_platformInfo, shaderSourceDescriptor); + if (platformInterfaces.empty()) + { + // No work to do. Exit gracefully. + AZ_TracePrintf( + ShaderVariantAssetBuilder2Name, + "No azshadervarianttree is produced on behalf of %s because all valid RHI backends were disabled for this shader.\n", + shaderSourceFileFullPath.c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; + return; + } + + + // set the input file for eventual error messages, but the compiler won't be called on it. + AZStd::string azslFullPath; + ShaderBuilderUtility::GetAbsolutePathToAzslFile(shaderSourceFileFullPath, shaderSourceDescriptor.m_source, azslFullPath); + AzslCompiler azslc(azslFullPath); + + AZStd::string previousLoopApiName; + for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces) + { + auto thisLoopApiName = shaderPlatformInterface->GetAPIName().GetStringView(); + RPI::Ptr loopLocal_ShaderOptionGroupLayout = + LoadShaderOptionsGroupLayoutFromShaderAssetBuilder2( + shaderPlatformInterface, request.m_platformInfo, azslc, shaderSourceFileFullPath, RPI::DefaultSupervariantIndex); + if (!loopLocal_ShaderOptionGroupLayout) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + if (shaderOptionGroupLayout && shaderOptionGroupLayout->GetHash() != loopLocal_ShaderOptionGroupLayout->GetHash()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "There was a discrepancy in shader options between %s and %s", previousLoopApiName.c_str(), thisLoopApiName.data()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + shaderOptionGroupLayout = loopLocal_ShaderOptionGroupLayout; + previousLoopApiName = thisLoopApiName; + } + + RPI::ShaderVariantTreeAssetCreator shaderVariantTreeAssetCreator; + shaderVariantTreeAssetCreator.Begin(Uuid::CreateRandom()); + shaderVariantTreeAssetCreator.SetShaderOptionGroupLayout(*shaderOptionGroupLayout); + shaderVariantTreeAssetCreator.SetVariantInfos(shaderVariantListDescriptor.m_shaderVariants); + Data::Asset shaderVariantTreeAsset; + if (!shaderVariantTreeAssetCreator.End(shaderVariantTreeAsset)) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to build Shader Variant Tree Asset"); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + AZStd::string filename = AZStd::string::format("%s.%s", shaderName.c_str(), RPI::ShaderVariantTreeAsset::Extension); + AZStd::string assetPath; + AzFramework::StringFunc::Path::ConstructFull(request.m_tempDirPath.c_str(), filename.c_str(), assetPath, true); + if (!AZ::Utils::SaveObjectToFile(assetPath, AZ::DataStream::ST_BINARY, shaderVariantTreeAsset.Get())) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to save Shader Variant Tree Asset to \"%s\"", assetPath.c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + AssetBuilderSDK::JobProduct assetProduct; + assetProduct.m_productSubID = RPI::ShaderVariantTreeAsset::ProductSubID; + assetProduct.m_productFileName = assetPath; + assetProduct.m_productAssetType = azrtti_typeid(); + assetProduct.m_dependenciesHandled = true; // This builder has no dependencies to output + response.m_outputProducts.push_back(assetProduct); + + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Shader Variant Tree Asset [%s] compiled successfully.\n", assetPath.c_str()); + + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; + } + + void ShaderVariantAssetBuilder2::ProcessShaderVariantJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const + { + const AZStd::sys_time_t startTime = AZStd::GetTimeNowTicks(); + AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId); + + AZStd::string fullPath; + AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullPath, true); + + const auto& jobParameters = request.m_jobDescription.m_jobParameters; + const AZStd::string& shaderSourceFileFullPath = jobParameters.at(ShaderSourceFilePathJobParam); + AZStd::string shaderFileName; + AzFramework::StringFunc::Path::GetFileName(shaderSourceFileFullPath.c_str(), shaderFileName); + + const AZStd::string& variantJsonString = jobParameters.at(ShaderVariantJobVariantParam); + RPI::ShaderVariantListSourceData::VariantInfo variantInfo; + const bool fromJsonStringSuccess = AZ::RPI::JsonUtils::LoadObjectFromJsonString(variantJsonString, variantInfo); + AZ_Assert(fromJsonStringSuccess, "Failed to convert json string to VariantInfo"); + + RPI::ShaderSourceData shaderSourceDescriptor; + AZStd::shared_ptr sources = ShaderBuilderUtility::PrepareSourceInput(ShaderVariantAssetBuilder2Name, shaderSourceFileFullPath, shaderSourceDescriptor); + + // set the input file for eventual error messages, but the compiler won't be called on it. + AzslCompiler azslc(sources->m_azslSourceFullPath); + + // Request the list of valid shader platform interfaces for the target platform. + AZStd::vector platformInterfaces = + ShaderBuilderUtility::DiscoverEnabledShaderPlatformInterfaces(request.m_platformInfo, shaderSourceDescriptor); + if (platformInterfaces.empty()) + { + // No work to do. Exit gracefully. + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, + "No azshader is produced on behalf of %s because all valid RHI backends were disabled for this shader.\n", + shaderSourceFileFullPath.c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; + return; + } + + auto supervariantList = ShaderBuilderUtility::GetSupervariantListFromShaderSourceData(shaderSourceDescriptor); + + GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderVariantAssetBuilder2Name); + // At this moment We have global build options that should be merged with the build options that are common + // to all the supervariants of this shader. + buildOptions.m_compilerArguments.Merge(shaderSourceDescriptor.m_compiler); + + //! The ShaderOptionGroupLayout is common across all RHIs & Supervariants + RPI::Ptr shaderOptionGroupLayout = nullptr; + + // Generate shaders for each of those ShaderPlatformInterfaces. + for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces) + { + AZ_TraceContext("ShaderPlatformInterface", shaderPlatformInterface->GetAPIName().GetCStr()); + + // Loop through all the Supervariants. + uint32_t supervariantIndexCounter = 0; + for (const auto& supervariantInfo : supervariantList) + { + RPI::SupervariantIndex supervariantIndex(supervariantIndexCounter); + + // Check if we were canceled before we do any heavy processing of + // the shader variant data. + if (jobCancelListener.IsCancelled()) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled; + return; + } + + AZStd::string shaderStemNamePrefix = shaderFileName; + if (supervariantIndex.GetIndex() > 0) + { + shaderStemNamePrefix += supervariantInfo.m_name.GetStringView(); + } + + // We need these additional pieces of information To build a shader variant asset: + // 1- ShaderOptionsGroupLayout (Need to load it once, because it's the same acrosss all supervariants + RHIs) + // 2- entryFunctions + // 3- hlsl code. + + // 1- ShaderOptionsGroupLayout + if (!shaderOptionGroupLayout) + { + shaderOptionGroupLayout = + LoadShaderOptionsGroupLayoutFromShaderAssetBuilder2( + shaderPlatformInterface, request.m_platformInfo, azslc, shaderSourceFileFullPath, supervariantIndex); + if (!shaderOptionGroupLayout) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + } + + // 2- entryFunctions. + AzslFunctions azslFunctions; + LoadShaderFunctionsFromShaderAssetBuilder2( + shaderPlatformInterface, request.m_platformInfo, azslc, shaderSourceFileFullPath, supervariantIndex, azslFunctions); + if (azslFunctions.empty()) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + MapOfStringToStageType shaderEntryPoints; + if (shaderSourceDescriptor.m_programSettings.m_entryPoints.empty()) + { + AZ_TracePrintf( + ShaderVariantAssetBuilder2Name, + "ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n"); + ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslFunctions, shaderEntryPoints); + } + else + { + for (const auto& entryPoint : shaderSourceDescriptor.m_programSettings.m_entryPoints) + { + shaderEntryPoints[entryPoint.m_name] = entryPoint.m_type; + } + } + + // 3- hlslCode + AZStd::string hlslSourcePath; + AZStd::string hlslCode = LoadHlslFileFromShaderAssetBuilder2( + shaderPlatformInterface, request.m_platformInfo, shaderSourceFileFullPath, supervariantIndex, hlslSourcePath); + if (hlslCode.empty() || hlslSourcePath.empty()) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + // Setup the shader variant creation context: + ShaderVariantCreationContext2 shaderVariantCreationContext = + { + *shaderPlatformInterface, request.m_platformInfo, buildOptions.m_compilerArguments, request.m_tempDirPath, + startTime, + shaderSourceDescriptor, + *shaderOptionGroupLayout.get(), + shaderEntryPoints, + Uuid::CreateRandom(), + shaderStemNamePrefix, + hlslSourcePath, hlslCode + }; + + AZStd::optional outputByproducts; + auto shaderVariantAssetOutcome = CreateShaderVariantAsset(variantInfo, shaderVariantCreationContext, outputByproducts); + if (!shaderVariantAssetOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s\n", shaderVariantAssetOutcome.GetError().c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + Data::Asset shaderVariantAsset = shaderVariantAssetOutcome.TakeValue(); + + + // Time to save the asset in the tmp folder so it ends up in the Cache folder. + const uint32_t productSubID = RPI::ShaderVariantAsset2::MakeAssetProductSubId( + shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex.GetIndex(), + shaderVariantAsset->GetStableId()); + AssetBuilderSDK::JobProduct assetProduct; + if (!SerializeOutShaderVariantAsset(shaderVariantAsset, shaderStemNamePrefix, + request.m_tempDirPath, *shaderPlatformInterface, productSubID, + assetProduct)) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + response.m_outputProducts.push_back(assetProduct); + + if (outputByproducts) + { + // add byproducts as job output products: + uint32_t subProductType = RPI::ShaderVariantAsset2::ShaderVariantAsset2SubProductType; + for (const AZStd::string& byproduct : outputByproducts.value().m_intermediatePaths) + { + AssetBuilderSDK::JobProduct jobProduct; + jobProduct.m_productFileName = byproduct; + jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt"); + jobProduct.m_productSubID = RPI::ShaderVariantAsset2::MakeAssetProductSubId( + shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex.GetIndex(), shaderVariantAsset->GetStableId(), + subProductType++); + response.m_outputProducts.push_back(AZStd::move(jobProduct)); + } + } + supervariantIndexCounter++; + } // End of supervariant for block + + } + + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; + } + + bool ShaderVariantAssetBuilder2::SerializeOutShaderVariantAsset( + const Data::Asset shaderVariantAsset, const AZStd::string& shaderStemNamePrefix, + const AZStd::string& tempDirPath, + const RHI::ShaderPlatformInterface& shaderPlatformInterface, const uint32_t productSubID, AssetBuilderSDK::JobProduct& assetProduct) + { + AZStd::string filename = AZStd::string::format( + "%s_%s_%u.%s", shaderStemNamePrefix.c_str(), shaderPlatformInterface.GetAPIName().GetCStr(), + shaderVariantAsset->GetStableId().GetIndex(), RPI::ShaderVariantAsset2::Extension); + + AZStd::string assetPath; + AzFramework::StringFunc::Path::ConstructFull(tempDirPath.c_str(), filename.c_str(), assetPath, true); + + if (!AZ::Utils::SaveObjectToFile(assetPath, AZ::DataStream::ST_BINARY, shaderVariantAsset.Get())) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to save Shader Variant Asset to \"%s\"", assetPath.c_str()); + return false; + } + + assetProduct.m_productSubID = productSubID; + assetProduct.m_productFileName = assetPath; + assetProduct.m_productAssetType = azrtti_typeid(); + assetProduct.m_dependenciesHandled = true; // This builder has no dependencies to output + + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Shader Variant Asset [%s] compiled successfully.\n", assetPath.c_str()); + return true; + } + + + AZ::Outcome, AZStd::string> ShaderVariantAssetBuilder2::CreateShaderVariantAsset( + const RPI::ShaderVariantListSourceData::VariantInfo& shaderVariantInfo, + ShaderVariantCreationContext2& creationContext, + AZStd::optional& outputByproducts) + { + // Temporary structure used for sorting and caching intermediate results + struct OptionCache + { + AZ::Name m_optionName; + AZ::Name m_valueName; + RPI::ShaderOptionIndex m_optionIndex; // Cached m_optionName + RPI::ShaderOptionValue m_value; // Cached m_valueName + }; + AZStd::vector optionList; + // We can not have more options than the number of options in the layout: + optionList.reserve(creationContext.m_shaderOptionGroupLayout.GetShaderOptionCount()); + + // This loop will validate and cache the indices for each option value: + for (const auto& shaderOption : shaderVariantInfo.m_options) + { + Name optionName{shaderOption.first}; + Name optionValue{shaderOption.second}; + + RPI::ShaderOptionIndex optionIndex = creationContext.m_shaderOptionGroupLayout.FindShaderOptionIndex(optionName); + if (optionIndex.IsNull()) + { + return AZ::Failure(AZStd::string::format("Invalid shader option: %s", optionName.GetCStr())); + } + + const RPI::ShaderOptionDescriptor& option = creationContext.m_shaderOptionGroupLayout.GetShaderOption(optionIndex); + RPI::ShaderOptionValue value = option.FindValue(optionValue); + if (value.IsNull()) + { + return AZ::Failure( + AZStd::string::format("Invalid value (%s) for shader option: %s", optionValue.GetCStr(), optionName.GetCStr())); + } + + optionList.push_back(OptionCache{optionName, optionValue, optionIndex, value}); + } + + // Create one instance of the shader variant + RPI::ShaderOptionGroup optionGroup(&creationContext.m_shaderOptionGroupLayout); + + //! Contains the series of #define macro values that define a variant. Can be empty (root variant). + //! If this string is NOT empty, a new temporary hlsl file will be created that will be the combination + //! of this string + @m_hlslSourceContent. + AZStd::string hlslCodeToPrependForVariant; + + // We want to go over all options listed in the variant and set their respective values + // This loop will populate the optionGroup and m_shaderCodePrefix in order of the option priority + for (const auto& optionCache : optionList) + { + const RPI::ShaderOptionDescriptor& option = creationContext.m_shaderOptionGroupLayout.GetShaderOption(optionCache.m_optionIndex); + + // Assign the option value specified in the variant: + option.Set(optionGroup, optionCache.m_value); + + // Populate all shader option defines. We have already confirmed they're valid. + hlslCodeToPrependForVariant += AZStd::string::format( + "#define %s_OPTION_DEF %s\n", optionCache.m_optionName.GetCStr(), optionCache.m_valueName.GetCStr()); + } + + AZStd::string variantShaderSourcePath; + // Check if we need to prepend any code prefix + if (!hlslCodeToPrependForVariant.empty()) + { + // Prepend any shader code prefix that we should apply to this variant + // and save it back to a file. + AZStd::string variantShaderSourceString(hlslCodeToPrependForVariant); + variantShaderSourceString += creationContext.m_hlslSourceContent; + + AZStd::string shaderAssetName = AZStd::string::format( + "%s_%s_%u.hlsl", creationContext.m_shaderStemNamePrefix.c_str(), + creationContext.m_shaderPlatformInterface.GetAPIName().GetCStr(), shaderVariantInfo.m_stableId); + AzFramework::StringFunc::Path::Join( + creationContext.m_tempDirPath.c_str(), shaderAssetName.c_str(), variantShaderSourcePath, true, true); + + auto outcome = Utils::WriteFile(variantShaderSourceString, variantShaderSourcePath); + if (!outcome.IsSuccess()) + { + return AZ::Failure(AZStd::string::format("Failed to create file %s", variantShaderSourcePath.c_str())); + } + } + else + { + variantShaderSourcePath = creationContext.m_hlslSourcePath; + } + + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Variant StableId: %u", shaderVariantInfo.m_stableId); + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Variant Shader Options: %s", optionGroup.ToString().c_str()); + + const RPI::ShaderVariantStableId shaderVariantStableId{shaderVariantInfo.m_stableId}; + + // By this time the optionGroup was populated with all option values for the variant and + // the m_shaderCodePrefix contains all option related preprocessing macros + // Let's add the requested variant: + RPI::ShaderVariantAssetCreator2 variantCreator; + RPI::ShaderOptionGroup shaderOptions{&creationContext.m_shaderOptionGroupLayout, optionGroup.GetShaderVariantId()}; + variantCreator.Begin( + creationContext.m_shaderVariantAssetId, optionGroup.GetShaderVariantId(), shaderVariantStableId, + shaderOptions.IsFullySpecified()); + + const AZStd::unordered_map& shaderEntryPoints = creationContext.m_shaderEntryPoints; + for (const auto& shaderEntryPoint : shaderEntryPoints) + { + auto shaderEntryName = shaderEntryPoint.first; + auto shaderStageType = shaderEntryPoint.second; + + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Entry Point: %s", shaderEntryName.c_str()); + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Begin compiling shader function \"%s\"", shaderEntryName.c_str()); + + auto assetBuilderShaderType = ShaderBuilderUtility::ToAssetBuilderShaderType(shaderStageType); + + // Compile HLSL to the platform specific shader. + RHI::ShaderPlatformInterface::StageDescriptor descriptor; + bool shaderWasCompiled = creationContext.m_shaderPlatformInterface.CompilePlatformInternal( + creationContext.m_platformInfo, variantShaderSourcePath, shaderEntryName, assetBuilderShaderType, + creationContext.m_tempDirPath, descriptor, creationContext.m_shaderCompilerArguments); + + if (!shaderWasCompiled) + { + return AZ::Failure(AZStd::string::format("Could not compile the shader function %s", shaderEntryName.c_str())); + } + // bubble up the byproducts to the caller by moving them to the context. + outputByproducts.emplace(AZStd::move(descriptor.m_byProducts)); + + RHI::Ptr shaderStageFunction = creationContext.m_shaderPlatformInterface.CreateShaderStageFunction(descriptor); + variantCreator.SetShaderFunction(ToRHIShaderStage(assetBuilderShaderType), shaderStageFunction); + + if (descriptor.m_byProducts.m_dynamicBranchCount != AZ::RHI::ShaderPlatformInterface::ByProducts::UnknownDynamicBranchCount) + { + AZ_TracePrintf( + ShaderVariantAssetBuilder2Name, "Finished compiling shader function. Number of dynamic branches: %u", + descriptor.m_byProducts.m_dynamicBranchCount); + } + else + { + AZ_TracePrintf( + ShaderVariantAssetBuilder2Name, "Finished compiling shader function. Number of dynamic branches: unknown"); + } + } + + Data::Asset shaderVariantAsset; + variantCreator.End(shaderVariantAsset); + return AZ::Success(AZStd::move(shaderVariantAsset)); + } + + } // ShaderBuilder +} // AZ diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder2.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder2.h new file mode 100644 index 0000000000..c0b632d9bd --- /dev/null +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder2.h @@ -0,0 +1,107 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +#include "ShaderBuilderUtility.h" + +namespace AZ +{ + namespace ShaderBuilder + { + struct AzslData; + + //! This is nothing more than a class to help consolidate all + //! the data needed to generate a shader variant and prevent + //! all the functions involved in the process to have too many + //! arguments. + struct ShaderVariantCreationContext2 + { + RHI::ShaderPlatformInterface& m_shaderPlatformInterface; + const AssetBuilderSDK::PlatformInfo& m_platformInfo; + const RHI::ShaderCompilerArguments& m_shaderCompilerArguments; + //! Used to write temporary files during shader compilation, like *.hlsl, or *.air, or *.metallib, etc. + const AZStd::string& m_tempDirPath; + //! Used to synchronize versions of the ShaderAsset and ShaderVariantAsset, + //! especially during hot-reload. A (ShaderVariantAsset.timestamp) >= (ShaderAsset.timestamp). + const AZStd::sys_time_t m_assetBuildTimestamp; + const RPI::ShaderSourceData& m_shaderSourceDataDescriptor; + const RPI::ShaderOptionGroupLayout& m_shaderOptionGroupLayout; + const MapOfStringToStageType& m_shaderEntryPoints; + const Data::AssetId m_shaderVariantAssetId; + const AZStd::string& m_shaderStemNamePrefix; //- + const AZStd::string& m_hlslSourcePath; + const AZStd::string& m_hlslSourceContent; + }; + + class ShaderVariantAssetBuilder2 + : public AssetBuilderSDK::AssetBuilderCommandBus::Handler + { + public: + AZ_TYPE_INFO(ShaderVariantAssetBuilder2, "{C959AEC2-2083-4488-AD88-F61B1144535B}"); + + static constexpr char ShaderVariantAssetBuilder2JobKey[] = "Shader Variant Asset 2"; + + ShaderVariantAssetBuilder2() = default; + ~ShaderVariantAssetBuilder2() = default; + + // Asset Builder Callback Functions ... + void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const; + void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const; + + //! The ShaderVariantAsset returned by this function won't be written to the filesystem. + //! You should call SerializeOutShaderVariantAsset to write it to the temp folder assigned + //! by the asset processor. + static AZ::Outcome, AZStd::string> CreateShaderVariantAsset( + const RPI::ShaderVariantListSourceData::VariantInfo& shaderVariantInfo, + ShaderVariantCreationContext2& creationContext, + AZStd::optional& outputByproducts); + + static bool SerializeOutShaderVariantAsset( + const Data::Asset shaderVariantAsset, + const AZStd::string& shaderStemNamePrefix, const AZStd::string& tempDirPath, + const RHI::ShaderPlatformInterface& shaderPlatformInterface, const uint32_t productSubID, AssetBuilderSDK::JobProduct& assetProduct); + + // AssetBuilderSDK::AssetBuilderCommandBus interface overrides ... + void ShutDown() override { }; + + private: + AZ_DISABLE_COPY_MOVE(ShaderVariantAssetBuilder2); + + static constexpr uint32_t ShaderVariantLoadErrorParam = 0; + static constexpr uint32_t ShaderSourceFilePathJobParam = 2; + static constexpr uint32_t ShaderVariantJobVariantParam = 3; + static constexpr uint32_t ShouldExitEarlyFromProcessJobParam = 4; + + //! Called from ProcessJob when the job is supposed to create a ShaderVariantTreeAsset. + void ProcessShaderVariantTreeJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const; + + //! Called from ProcessJob when the job is supposed to create ShaderVariantAssets. One ShaderVariantAsset will be produced per RHI::APIType + //! supported by the platform. + void ProcessShaderVariantJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const; + + static AZStd::string GetShaderVariantTreeAssetJobKey() { return AZStd::string::format("%s_varianttree", ShaderVariantAssetBuilder2JobKey); } + static AZStd::string GetShaderVariantAssetJobKey(RPI::ShaderVariantStableId variantStableId) { return AZStd::string::format("%s_variant_%u", ShaderVariantAssetBuilder2JobKey, variantStableId.GetIndex()); } + + }; + + } // ShaderBuilder +} // AZ diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutBuilder.cpp index 0b9b813815..a7721c84a2 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutBuilder.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutBuilder.cpp @@ -345,17 +345,21 @@ namespace AZ for(const SrgDataEntry& srgDataEntry : entry.second) { RHI::ShaderPlatformInterface* shaderPlatformInterface = srgDataEntry.first; - const SrgData& srgData = srgDataEntry.second; - - srgAssetCreator.BeginAPI(shaderPlatformInterface->GetAPIType()); - srgAssetCreator.SetBindingSlot(srgData.m_bindingSlot.m_index); // The register number only makes sense if the platform uses "spaces", // since the register Id of the resource will not change even if the pipeline layout changes. - // We can pass in a default ShaderCompilerArguments because all we care about is whether the shaderPlatformInterface appends the "--use-spaces" flag. - AZStd::string azslCompilerParameters = shaderPlatformInterface->GetAzslCompilerParameters(RHI::ShaderCompilerArguments{}); + // We can pass in a default ShaderCompilerArguments because all we care about is whether the shaderPlatformInterface + // appends the + // "--use-spaces" flag. + AZStd::string azslCompilerParameters = + shaderPlatformInterface->GetAzslCompilerParameters(RHI::ShaderCompilerArguments{}); bool useRegisterId = (AzFramework::StringFunc::Find(azslCompilerParameters, "--use-spaces") != AZStd::string::npos); + const SrgData& srgData = srgDataEntry.second; + + srgAssetCreator.BeginAPI(shaderPlatformInterface->GetAPIType()); + srgAssetCreator.SetBindingSlot(srgData.m_bindingSlot.m_index); + // Samplers for (const SamplerSrgData& samplerData : srgData.m_samplers) { diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutUtility.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutUtility.cpp new file mode 100644 index 0000000000..fc3fbfc32c --- /dev/null +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutUtility.cpp @@ -0,0 +1,231 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#include "SrgLayoutUtility.h" + +#include + +namespace AZ +{ + namespace ShaderBuilder + { + namespace SrgLayoutUtility + { + static constexpr char SrgLayoutUtilityName[] = "SrgLayoutUtility"; + + RHI::ShaderInputImageType ToShaderInputImageType(TextureType textureType) + { + switch (textureType) + { + case TextureType::Texture1D: + return RHI::ShaderInputImageType::Image1D; + case TextureType::Texture1DArray: + return RHI::ShaderInputImageType::Image1DArray; + case TextureType::Texture2D: + return RHI::ShaderInputImageType::Image2D; + case TextureType::Texture2DArray: + return RHI::ShaderInputImageType::Image2DArray; + case TextureType::Texture2DMS: + return RHI::ShaderInputImageType::Image2DMultisample; + case TextureType::Texture2DMSArray: + return RHI::ShaderInputImageType::Image2DMultisampleArray; + case TextureType::Texture3D: + return RHI::ShaderInputImageType::Image3D; + case TextureType::TextureCube: + return RHI::ShaderInputImageType::ImageCube; + case TextureType::RwTexture1D: + return RHI::ShaderInputImageType::Image1D; + case TextureType::RwTexture1DArray: + return RHI::ShaderInputImageType::Image1DArray; + case TextureType::RwTexture2D: + return RHI::ShaderInputImageType::Image2D; + case TextureType::RwTexture2DArray: + return RHI::ShaderInputImageType::Image2DArray; + case TextureType::RwTexture3D: + return RHI::ShaderInputImageType::Image3D; + case TextureType::RasterizerOrderedTexture1D: + return RHI::ShaderInputImageType::Image1D; + case TextureType::RasterizerOrderedTexture1DArray: + return RHI::ShaderInputImageType::Image1DArray; + case TextureType::RasterizerOrderedTexture2D: + return RHI::ShaderInputImageType::Image2D; + case TextureType::RasterizerOrderedTexture2DArray: + return RHI::ShaderInputImageType::Image2DArray; + case TextureType::RasterizerOrderedTexture3D: + return RHI::ShaderInputImageType::Image3D; + case TextureType::SubpassInput: + return RHI::ShaderInputImageType::SubpassInput; + default: + AZ_Assert(false, "Unhandled TextureType"); + return RHI::ShaderInputImageType::Unknown; + } + } + + RHI::ShaderInputBufferType ToShaderInputBufferType(BufferType bufferType) + { + switch (bufferType) + { + case BufferType::Buffer: + case BufferType::RwBuffer: + case BufferType::RasterizerOrderedBuffer: + return RHI::ShaderInputBufferType::Typed; + case BufferType::AppendStructuredBuffer: + case BufferType::ConsumeStructuredBuffer: + case BufferType::RasterizerOrderedStructuredBuffer: + case BufferType::RwStructuredBuffer: + case BufferType::StructuredBuffer: + return RHI::ShaderInputBufferType::Structured; + case BufferType::RasterizerOrderedByteAddressBuffer: + case BufferType::ByteAddressBuffer: + case BufferType::RwByteAddressBuffer: + return RHI::ShaderInputBufferType::Raw; + case BufferType::RaytracingAccelerationStructure: + return RHI::ShaderInputBufferType::AccelerationStructure; + default: + AZ_Assert(false, "Unhandled BufferType"); + return RHI::ShaderInputBufferType::Unknown; + } + } + + bool LoadShaderResourceGroupLayouts( + [[maybe_unused]] const char* builderName, const SrgDataContainer& resourceGroups, + const bool platformUsesRegisterSpaces, RPI::ShaderResourceGroupLayoutList& srgLayoutList) + { + // The register number only makes sense if the platform uses "spaces", + // since the register Id of the resource will not change even if the pipeline layout changes. + // All we care about is whether the shaderPlatformInterface appends the "--use-spaces" flag. + bool useRegisterId = platformUsesRegisterSpaces; + + // Load all SRGs included in source file + for (const SrgData& srgData : resourceGroups) + { + RHI::Ptr newSrgLayout = RHI::ShaderResourceGroupLayout::Create(); + newSrgLayout->SetName(AZ::Name{srgData.m_name.c_str()}); + newSrgLayout->SetBindingSlot(srgData.m_bindingSlot.m_index); + + // Samplers + for (const SamplerSrgData& samplerData : srgData.m_samplers) + { + if (samplerData.m_isDynamic) + { + newSrgLayout->AddShaderInput( + {samplerData.m_nameId, samplerData.m_count, + useRegisterId ? samplerData.m_registerId : RHI::UndefinedRegisterSlot}); + } + else + { + newSrgLayout->AddStaticSampler( + {samplerData.m_nameId, samplerData.m_descriptor, + useRegisterId ? samplerData.m_registerId : RHI::UndefinedRegisterSlot}); + } + } + + // Images + for (const TextureSrgData& textureData : srgData.m_textures) + { + const RHI::ShaderInputImageAccess imageAccess = + textureData.m_isReadOnlyType ? RHI::ShaderInputImageAccess::Read : RHI::ShaderInputImageAccess::ReadWrite; + + const RHI::ShaderInputImageType imageType = SrgLayoutUtility::ToShaderInputImageType(textureData.m_type); + + if (imageType != RHI::ShaderInputImageType::Unknown) + { + if (textureData.m_count != aznumeric_cast(-1)) + { + newSrgLayout->AddShaderInput( + {textureData.m_nameId, imageAccess, imageType, textureData.m_count, + useRegisterId ? textureData.m_registerId : RHI::UndefinedRegisterSlot}); + } + else + { + // unbounded array + newSrgLayout->AddShaderInput( + {textureData.m_nameId, imageAccess, imageType, + useRegisterId ? textureData.m_registerId : RHI::UndefinedRegisterSlot}); + } + } + else + { + AZ_Error( + builderName, false, "Failed to build Shader Resource Group Asset: Image %s has an unknown type.", + textureData.m_nameId.GetCStr()); + return false; + } + } + + // Buffers + { + for (const ConstantBufferData& cbData : srgData.m_constantBuffers) + { + newSrgLayout->AddShaderInput( + {cbData.m_nameId, RHI::ShaderInputBufferAccess::Constant, RHI::ShaderInputBufferType::Constant, + cbData.m_count, cbData.m_strideSize, useRegisterId ? cbData.m_registerId : RHI::UndefinedRegisterSlot}); + } + + for (const BufferSrgData& bufferData : srgData.m_buffers) + { + const RHI::ShaderInputBufferAccess bufferAccess = + bufferData.m_isReadOnlyType ? RHI::ShaderInputBufferAccess::Read : RHI::ShaderInputBufferAccess::ReadWrite; + + const RHI::ShaderInputBufferType bufferType = SrgLayoutUtility::ToShaderInputBufferType(bufferData.m_type); + + if (bufferType != RHI::ShaderInputBufferType::Unknown) + { + if (bufferData.m_count != aznumeric_cast(-1)) + { + newSrgLayout->AddShaderInput( + {bufferData.m_nameId, bufferAccess, bufferType, bufferData.m_count, bufferData.m_strideSize, + useRegisterId ? bufferData.m_registerId : RHI::UndefinedRegisterSlot}); + } + else + { + // unbounded array + newSrgLayout->AddShaderInput( + {bufferData.m_nameId, bufferAccess, bufferType, bufferData.m_strideSize, + useRegisterId ? bufferData.m_registerId : RHI::UndefinedRegisterSlot}); + } + } + else + { + AZ_Error( + builderName, false, + "Failed to build Shader Resource Group Asset: Buffer %s has un unknown type.", + bufferData.m_nameId.GetCStr()); + return false; + } + } + } + + // SRG Constants + uint32_t constantDataRegisterId = useRegisterId ? srgData.m_srgConstantDataRegisterId : RHI::UndefinedRegisterSlot; + for (const SrgConstantData& srgConstants : srgData.m_srgConstantData) + { + newSrgLayout->AddShaderInput( + {srgConstants.m_nameId, srgConstants.m_constantByteOffset, srgConstants.m_constantByteSize, + constantDataRegisterId}); + } + + // Shader Variant Key fallback + if (srgData.m_fallbackSize > 0) + { + // Designates this SRG as a ShaderVariantKey fallback + newSrgLayout->SetShaderVariantKeyFallback(srgData.m_fallbackName, srgData.m_fallbackSize); + } + + srgLayoutList.push_back(newSrgLayout); + } + + return true; + } + + } // namespace SrgLayoutUtility + } // namespace ShaderBuilder +} // AZ diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutUtility.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutUtility.h new file mode 100644 index 0000000000..43607f600c --- /dev/null +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutUtility.h @@ -0,0 +1,34 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +#include "CommonFiles/CommonTypes.h" +#include +#include "ShaderBuilderUtility.h" + +namespace AZ +{ + namespace ShaderBuilder + { + namespace SrgLayoutUtility + { + + bool LoadShaderResourceGroupLayouts( + [[maybe_unused]] const char* builderName, const SrgDataContainer& resourceGroups, const bool platformUsesRegisterSpaces, + RPI::ShaderResourceGroupLayoutList& srgLayoutList); + + } // SrgLayoutUtility namespace + } // ShaderBuilder namespace +} // AZ diff --git a/Gems/Atom/Asset/Shader/Code/atom_asset_shader_builders_files.cmake b/Gems/Atom/Asset/Shader/Code/atom_asset_shader_builders_files.cmake index 2032838f94..b3b2032c54 100644 --- a/Gems/Atom/Asset/Shader/Code/atom_asset_shader_builders_files.cmake +++ b/Gems/Atom/Asset/Shader/Code/atom_asset_shader_builders_files.cmake @@ -34,8 +34,14 @@ set(FILES Source/Editor/AzslCompiler.h Source/Editor/ShaderVariantAssetBuilder.cpp Source/Editor/ShaderVariantAssetBuilder.h + Source/Editor/ShaderVariantAssetBuilder2.cpp + Source/Editor/ShaderVariantAssetBuilder2.h Source/Editor/AtomShaderConfig.cpp Source/Editor/AtomShaderConfig.h Source/Editor/PrecompiledShaderBuilder.cpp Source/Editor/PrecompiledShaderBuilder.h + Source/Editor/ShaderAssetBuilder2.cpp + Source/Editor/ShaderAssetBuilder2.h + Source/Editor/SrgLayoutUtility.cpp + Source/Editor/SrgLayoutUtility.h ) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl index 84095ac163..456d7cbabe 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl @@ -101,7 +101,7 @@ struct VSOutput float2 m_uv[UvSetCount] : UV1; float2 m_detailUv : UV3; - float4 m_blendMask : UV8; + float4 m_wrinkleBlendFactors : UV8; }; #include @@ -132,11 +132,11 @@ VSOutput SkinVS(VSInput IN) if(o_blendMask_isBound) { - OUT.m_blendMask = IN.m_optional_blendMask; + OUT.m_wrinkleBlendFactors = IN.m_optional_blendMask; } else { - OUT.m_blendMask = float4(0,1,0,0); + OUT.m_wrinkleBlendFactors = float4(0,0,0,0); } VertexHelper(IN, OUT, worldPosition, false); @@ -214,7 +214,22 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) float2 normalUv = IN.m_uv[MaterialSrg::m_normalMapUvIndex]; float detailLayerNormalFactor = MaterialSrg::m_detail_normal_factor * detailLayerBlendFactor; - + + // ------- Wrinkle Map Setup ------- + + // Combine the optional per-morph target wrinkle masks + float4 wrinkleBlendFactors = float4(0.0, 0.0, 0.0, 0.0); + for(uint wrinkleMaskIndex = 0; wrinkleMaskIndex < ObjectSrg::m_wrinkle_mask_count; ++wrinkleMaskIndex) + { + wrinkleBlendFactors += ObjectSrg::m_wrinkle_masks[wrinkleMaskIndex].Sample(MaterialSrg::m_sampler, normalUv) * ObjectSrg::GetWrinkleMaskWeight(wrinkleMaskIndex); + } + + // If texture based morph target driven masks are being used, use those values instead of the per-vertex colors + if(ObjectSrg::m_wrinkle_mask_count) + { + IN.m_wrinkleBlendFactors = saturate(wrinkleBlendFactors); + } + // Since the wrinkle normal maps should all be in the same tangent space as the main normal map, we should be able to blend the raw normal map // texture values before doing all the tangent space transforms, so we only have to do the transforms once, for better performance. @@ -223,12 +238,12 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) { normalMapSample = SampleNormalXY(MaterialSrg::m_normalMap, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY); } - if(o_wrinkleLayers_enabled && o_blendMask_isBound && o_wrinkleLayers_normal_enabled) + if(o_wrinkleLayers_enabled && o_wrinkleLayers_normal_enabled) { - normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture1, normalMapSample, MaterialSrg::m_wrinkle_normal_texture1, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_blendMask.r); - normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture2, normalMapSample, MaterialSrg::m_wrinkle_normal_texture2, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_blendMask.g); - normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture3, normalMapSample, MaterialSrg::m_wrinkle_normal_texture3, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_blendMask.b); - normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture4, normalMapSample, MaterialSrg::m_wrinkle_normal_texture4, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_blendMask.a); + normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture1, normalMapSample, MaterialSrg::m_wrinkle_normal_texture1, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_wrinkleBlendFactors.r); + normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture2, normalMapSample, MaterialSrg::m_wrinkle_normal_texture2, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_wrinkleBlendFactors.g); + normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture3, normalMapSample, MaterialSrg::m_wrinkle_normal_texture3, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_wrinkleBlendFactors.b); + normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture4, normalMapSample, MaterialSrg::m_wrinkle_normal_texture4, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_wrinkleBlendFactors.a); } if(o_detail_normal_useTexture) @@ -255,7 +270,7 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) float3 baseColor = GetBaseColorInput(MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, MaterialSrg::m_baseColor, o_baseColor_useTexture); bool useSampledBaseColor = o_baseColor_useTexture; - if(o_wrinkleLayers_enabled && o_blendMask_isBound && o_wrinkleLayers_baseColor_enabled) + if(o_wrinkleLayers_enabled && o_wrinkleLayers_baseColor_enabled) { // If any of the wrinkle maps are applied, we will use the Base Color blend settings to apply the MaterialSrg::m_baseColor tint to the wrinkle maps, // even if the main base color map is not used. @@ -272,10 +287,10 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) baseColor = float3(1,1,1); } - baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture1, baseColor, MaterialSrg::m_wrinkle_baseColor_texture1, MaterialSrg::m_sampler, baseColorUv, IN.m_blendMask.r); - baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture2, baseColor, MaterialSrg::m_wrinkle_baseColor_texture2, MaterialSrg::m_sampler, baseColorUv, IN.m_blendMask.g); - baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture3, baseColor, MaterialSrg::m_wrinkle_baseColor_texture3, MaterialSrg::m_sampler, baseColorUv, IN.m_blendMask.b); - baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture4, baseColor, MaterialSrg::m_wrinkle_baseColor_texture4, MaterialSrg::m_sampler, baseColorUv, IN.m_blendMask.a); + baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture1, baseColor, MaterialSrg::m_wrinkle_baseColor_texture1, MaterialSrg::m_sampler, baseColorUv, IN.m_wrinkleBlendFactors.r); + baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture2, baseColor, MaterialSrg::m_wrinkle_baseColor_texture2, MaterialSrg::m_sampler, baseColorUv, IN.m_wrinkleBlendFactors.g); + baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture3, baseColor, MaterialSrg::m_wrinkle_baseColor_texture3, MaterialSrg::m_sampler, baseColorUv, IN.m_wrinkleBlendFactors.b); + baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture4, baseColor, MaterialSrg::m_wrinkle_baseColor_texture4, MaterialSrg::m_sampler, baseColorUv, IN.m_wrinkleBlendFactors.a); } @@ -283,13 +298,13 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) baseColor = ApplyTextureOverlay(o_detail_baseColor_useTexture, baseColor, MaterialSrg::m_detail_baseColor_texture, MaterialSrg::m_sampler, IN.m_detailUv, detailLayerBaseColorFactor); - if(o_wrinkleLayers_enabled && o_wrinkleLayers_showBlendMaskValues && o_blendMask_isBound) + if(o_wrinkleLayers_enabled && o_wrinkleLayers_showBlendMaskValues) { // Overlay debug colors to highlight the different blend weights coming from the vertex color stream. - if(o_wrinkleLayers_count > 0) { baseColor = lerp(baseColor, float3(1,0,0), IN.m_blendMask.r); } - if(o_wrinkleLayers_count > 1) { baseColor = lerp(baseColor, float3(0,1,0), IN.m_blendMask.g); } - if(o_wrinkleLayers_count > 2) { baseColor = lerp(baseColor, float3(0,0,1), IN.m_blendMask.b); } - if(o_wrinkleLayers_count > 3) { baseColor = lerp(baseColor, float3(1,1,1), IN.m_blendMask.a); } + if(o_wrinkleLayers_count > 0) { baseColor = lerp(baseColor, float3(1,0,0), IN.m_wrinkleBlendFactors.r); } + if(o_wrinkleLayers_count > 1) { baseColor = lerp(baseColor, float3(0,1,0), IN.m_wrinkleBlendFactors.g); } + if(o_wrinkleLayers_count > 2) { baseColor = lerp(baseColor, float3(0,0,1), IN.m_wrinkleBlendFactors.b); } + if(o_wrinkleLayers_count > 3) { baseColor = lerp(baseColor, float3(1,1,1), IN.m_wrinkleBlendFactors.a); } } // ------- Specular ------- diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype index b8951d69c7..101a03b907 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype @@ -987,15 +987,6 @@ { "file": "Shaders/MotionVector/SkinnedMeshMotionVector.shader", "tag": "SkinnedMeshMotionVector" - }, - // Used by the light culling system to produce accurate depth bounds for this object when it uses blended transparency - { - "file": "Shaders/Depth/DepthPassTransparentMin.shader", - "tag": "DepthPassTransparentMin" - }, - { - "file": "Shaders/Depth/DepthPassTransparentMax.shader", - "tag": "DepthPassTransparentMax" } ], "functors": [ diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli index 50896cdf25..abc4ec7fc4 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli @@ -31,6 +31,16 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject return SceneSrg::GetObjectToWorldInverseTransposeMatrix(m_objectId); } + //[GFX TODO][ATOM-15280] Move wrinkle mask data from the default object srg into something specific to the Skin shader + uint m_wrinkle_mask_count; + float4 m_wrinkle_mask_weights[4]; + Texture2D m_wrinkle_masks[16]; + + float GetWrinkleMaskWeight(uint index) + { + return m_wrinkle_mask_weights[index / 4][index % 4]; + } + //! Reflection Probe (smallest probe volume that overlaps the object position) struct ReflectionProbeData { diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ACES/AcesDisplayMapperFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ACES/AcesDisplayMapperFeatureProcessor.h index a89653c359..03fbb93923 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ACES/AcesDisplayMapperFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ACES/AcesDisplayMapperFeatureProcessor.h @@ -60,6 +60,12 @@ namespace AZ : public DisplayMapperFeatureProcessorInterface { public: + enum OutputDeviceTransformFlags + { + AlterSurround = 0x1, // Apply gamma adjustment to compensate for dim surround + ApplyDesaturation = 0x2, // Apply desaturation to compensate for luminance difference + ApplyCATD60toD65 = 0x4, // Apply Color appearance transform (CAT) from ACES white point to assumed observer adapted white point + }; AZ_RTTI(AZ::Render::AcesDisplayMapperFeatureProcessor, "{995C2B93-8B08-4313-89B0-02394F90F1B8}", AZ::Render::DisplayMapperFeatureProcessorInterface); @@ -92,12 +98,6 @@ namespace AZ static void ApplyLdrOdtParameters(DisplayMapperParameters* pOutParameters); static void ApplyHdrOdtParameters(DisplayMapperParameters* pOutParameters, const OutputDeviceTransformType& odtType); - enum OutputDeviceTransformFlags { - AlterSurround = 0x1, // Apply gamma adjustment to compensate for dim surround - ApplyDesaturation = 0x2, // Apply desaturation to compensate for luminance difference - ApplyCATD60toD65 = 0x4, // Apply Color appearance transform (CAT) from ACES white point to assumed observer adapted white point - }; - enum OutputDeviceTransformMode { Srgb = 0, PerceptualQuantizer, diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/DisplayMapper/AcesOutputTransformPass.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/DisplayMapper/AcesOutputTransformPass.h index 5fce71fc07..5a0ccdb32a 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/DisplayMapper/AcesOutputTransformPass.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/DisplayMapper/AcesOutputTransformPass.h @@ -46,6 +46,7 @@ namespace AZ static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); void SetDisplayBufferFormat(RHI::Format format); + void SetAcesParameterOverrides(const AcesParameterOverrides& acesParameterOverrides); private: explicit AcesOutputTransformPass(const RPI::PassDescriptor& descriptor); @@ -65,6 +66,8 @@ namespace AZ AZ::Render::DisplayMapperParameters m_displayMapperParameters = {}; RHI::Format m_displayBufferFormat = RHI::Format::Unknown; + + AcesParameterOverrides m_acesParameterOverrides; }; } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/DisplayMapper/DisplayMapperConfigurationDescriptor.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/DisplayMapper/DisplayMapperConfigurationDescriptor.h index b645df4f6f..4dc090b831 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/DisplayMapper/DisplayMapperConfigurationDescriptor.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/DisplayMapper/DisplayMapperConfigurationDescriptor.h @@ -24,6 +24,48 @@ namespace AZ namespace Render { + /** + * The ACES display mapper parameter overrides. + * These parameters override default ACES parameters when m_overrideDefaults is true. + */ + struct AcesParameterOverrides final + { + AZ_TYPE_INFO(AcesParameterOverrides, "{3EE8C0D4-3792-46C0-B91C-B89A81C36B91}"); + static void Reflect(ReflectContext* context); + + void LoadPreset(); + + // When enabled allows parameter overrides for ACES configuration + bool m_overrideDefaults = false; + + // Apply gamma adjustment to compensate for dim surround + bool m_alterSurround = true; + // Apply desaturation to compensate for luminance difference + bool m_applyDesaturation = true; + // Apply Color appearance transform (CAT) from ACES white point to assumed observer adapted white point + bool m_applyCATD60toD65 = true; + + // Reference white and black luminance values + float m_cinemaLimitsBlack = 0.02f; + float m_cinemaLimitsWhite = 48.0f; + + // luminance linear extension below this + float m_minPoint = 0.0028798957f; + // luminance mid grey + float m_midPoint = 4.8f; + // luminance linear extension above this + float m_maxPoint = 1005.71912f; + + // Gamma adjustment to be applied to compensate for the condition of the viewing environment. + // Note that ACES uses a value of 0.9811 for adjusting from dark to dim surrounding. + float m_surroundGamma = 0.9811f; + // Optional gamma value that is applied as basic gamma curve OETF + float m_gamma = 2.2f; + + // Allows specifying default preset for different ODT modes + OutputDeviceTransformType m_preset = OutputDeviceTransformType_48Nits; + }; + //! A descriptor used to configure the DisplayMapper struct DisplayMapperConfigurationDescriptor final { @@ -37,6 +79,8 @@ namespace AZ bool m_ldrGradingLutEnabled = false; Data::Asset m_ldrColorGradingLut; + + AcesParameterOverrides m_acesParameterOverrides; }; //! Custom pass data for DisplayMapperPass. diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h index 7875e38fc0..0d61ef82d1 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h @@ -148,6 +148,8 @@ namespace AZ Data::Instance GetModel(const MeshHandle& meshHandle) const override; Data::Asset GetModelAsset(const MeshHandle& meshHandle) const override; + Data::Instance GetObjectSrg(const MeshHandle& meshHandle) const override; + void QueueObjectSrgForCompile(const MeshHandle& meshHandle) const override; void SetMaterialAssignmentMap(const MeshHandle& meshHandle, const Data::Instance& material) override; void SetMaterialAssignmentMap(const MeshHandle& meshHandle, const MaterialAssignmentMap& materials) override; const MaterialAssignmentMap& GetMaterialAssignmentMap(const MeshHandle& meshHandle) const override; diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h index c2360068de..fb5bff5584 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h @@ -61,6 +61,14 @@ namespace AZ virtual Data::Instance GetModel(const MeshHandle& meshHandle) const = 0; //! Gets the underlying RPI::ModelAsset for a meshHandle. virtual Data::Asset GetModelAsset(const MeshHandle& meshHandle) const = 0; + //! Gets the ObjectSrg for a meshHandle. + //! Updating the ObjectSrg should be followed by a call to QueueObjectSrgForCompile, + //! instead of compiling the srg directly. This way, if the srg has already been queued for compile, + //! it will not be queued twice in the same frame. The ObjectSrg should not be updated during + //! Simulate, or it will create a race between updating the data and the call to Compile + virtual Data::Instance GetObjectSrg(const MeshHandle& meshHandle) const = 0; + //! Queues the object srg for compile. + virtual void QueueObjectSrgForCompile(const MeshHandle& meshHandle) const = 0; //! Sets the MaterialAssignmentMap for a meshHandle, using just a single material for the DefaultMaterialAssignmentId. //! Note if there is already a material assignment map, this will replace the entire map with just a single material. virtual void SetMaterialAssignmentMap(const MeshHandle& meshHandle, const Data::Instance& material) = 0; diff --git a/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h index 39fd7b4380..418ee0cfb8 100644 --- a/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h @@ -23,6 +23,8 @@ namespace UnitTest MOCK_METHOD1(CloneMesh, MeshHandle(const MeshHandle&)); MOCK_CONST_METHOD1(GetModel, AZStd::intrusive_ptr(const MeshHandle&)); MOCK_CONST_METHOD1(GetModelAsset, AZ::Data::Asset(const MeshHandle&)); + MOCK_CONST_METHOD1(GetObjectSrg, AZStd::intrusive_ptr(const MeshHandle&)); + MOCK_CONST_METHOD1(QueueObjectSrgForCompile, void(const MeshHandle&)); MOCK_CONST_METHOD1(GetMaterialAssignmentMap, const AZ::Render::MaterialAssignmentMap&(const MeshHandle&)); MOCK_METHOD2(ConnectModelChangeEventHandler, void(const MeshHandle&, ModelChangedEvent::Handler&)); MOCK_METHOD3(SetTransform, void(const MeshHandle&, const AZ::Transform&, const AZ::Vector3&)); diff --git a/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/AcesOutputTransformPass.cpp b/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/AcesOutputTransformPass.cpp index b2103739ba..6fe38ff032 100644 --- a/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/AcesOutputTransformPass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/AcesOutputTransformPass.cpp @@ -102,6 +102,36 @@ namespace AZ AcesDisplayMapperFeatureProcessor::GetAcesDisplayMapperParameters(&m_displayMapperParameters, OutputDeviceTransformType_48Nits); } } + + if (m_acesParameterOverrides.m_overrideDefaults) + { + m_displayMapperParameters.m_OutputDisplayTransformFlags = 0; + if (m_acesParameterOverrides.m_alterSurround) + { + m_displayMapperParameters.m_OutputDisplayTransformFlags |= AcesDisplayMapperFeatureProcessor::AlterSurround; + } + if (m_acesParameterOverrides.m_applyDesaturation) + { + m_displayMapperParameters.m_OutputDisplayTransformFlags |= AcesDisplayMapperFeatureProcessor::ApplyDesaturation; + } + if (m_acesParameterOverrides.m_applyCATD60toD65) + { + m_displayMapperParameters.m_OutputDisplayTransformFlags |= AcesDisplayMapperFeatureProcessor::ApplyCATD60toD65; + } + + m_displayMapperParameters.m_cinemaLimits[0] = m_acesParameterOverrides.m_cinemaLimitsBlack; + m_displayMapperParameters.m_cinemaLimits[1] = m_acesParameterOverrides.m_cinemaLimitsWhite; + m_displayMapperParameters.m_acesSplineParams.minPoint[0] = m_acesParameterOverrides.m_minPoint; + m_displayMapperParameters.m_acesSplineParams.midPoint[0] = m_acesParameterOverrides.m_midPoint; + m_displayMapperParameters.m_acesSplineParams.maxPoint[0] = m_acesParameterOverrides.m_maxPoint; + m_displayMapperParameters.m_surroundGamma = m_acesParameterOverrides.m_surroundGamma; + m_displayMapperParameters.m_gamma = m_acesParameterOverrides.m_gamma; + } + } + + void AcesOutputTransformPass::SetAcesParameterOverrides(const AcesParameterOverrides& acesParameterOverrides) + { + m_acesParameterOverrides = acesParameterOverrides; } } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/DisplayMapperConfigurationDescriptor.cpp b/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/DisplayMapperConfigurationDescriptor.cpp index d80a02d083..e91e125b40 100644 --- a/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/DisplayMapperConfigurationDescriptor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/DisplayMapperConfigurationDescriptor.cpp @@ -9,14 +9,54 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ + #include #include +#include namespace AZ { namespace Render { + void AcesParameterOverrides::Reflect(ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("OverrideDefaults", &AcesParameterOverrides::m_overrideDefaults) + ->Field("AlterSurround", &AcesParameterOverrides::m_alterSurround) + ->Field("ApplyDesaturation", &AcesParameterOverrides::m_applyDesaturation) + ->Field("ApplyCATD60toD65", &AcesParameterOverrides::m_applyCATD60toD65) + ->Field("PresetODT", &AcesParameterOverrides::m_preset) + ->Field("CinemaLimitsBlack", &AcesParameterOverrides::m_cinemaLimitsBlack) + ->Field("CinemaLimitsWhite", &AcesParameterOverrides::m_cinemaLimitsWhite) + ->Field("MinPoint", &AcesParameterOverrides::m_minPoint) + ->Field("MidPoint", &AcesParameterOverrides::m_midPoint) + ->Field("MaxPoint", &AcesParameterOverrides::m_maxPoint) + ->Field("SurroundGamma", &AcesParameterOverrides::m_surroundGamma) + ->Field("Gamma", &AcesParameterOverrides::m_gamma); + } + } + + void AcesParameterOverrides::LoadPreset() + { + DisplayMapperParameters displayMapperParameters; + AcesDisplayMapperFeatureProcessor::GetAcesDisplayMapperParameters(&displayMapperParameters, m_preset); + + m_alterSurround = (displayMapperParameters.m_OutputDisplayTransformFlags & AcesDisplayMapperFeatureProcessor::AlterSurround) != 0; + m_applyDesaturation = (displayMapperParameters.m_OutputDisplayTransformFlags & AcesDisplayMapperFeatureProcessor::ApplyDesaturation) != 0; + m_applyCATD60toD65 = (displayMapperParameters.m_OutputDisplayTransformFlags & AcesDisplayMapperFeatureProcessor::ApplyCATD60toD65) != 0; + m_cinemaLimitsBlack = displayMapperParameters.m_cinemaLimits[0]; + m_cinemaLimitsWhite = displayMapperParameters.m_cinemaLimits[1]; + m_minPoint = displayMapperParameters.m_acesSplineParams.minPoint[0]; + m_midPoint = displayMapperParameters.m_acesSplineParams.midPoint[0]; + m_maxPoint = displayMapperParameters.m_acesSplineParams.maxPoint[0]; + m_surroundGamma = displayMapperParameters.m_surroundGamma; + m_gamma = displayMapperParameters.m_gamma; + } + void DisplayMapperConfigurationDescriptor::Reflect(AZ::ReflectContext* context) { if (auto* serializeContext = azrtti_cast(context)) diff --git a/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/DisplayMapperPass.cpp b/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/DisplayMapperPass.cpp index 1de92c42f6..8ae790e12c 100644 --- a/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/DisplayMapperPass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/DisplayMapperPass.cpp @@ -106,6 +106,7 @@ namespace AZ { if (m_acesOutputTransformPass) { + m_acesOutputTransformPass->SetAcesParameterOverrides(m_displayMapperConfigurationDescriptor.m_acesParameterOverrides); m_acesOutputTransformPass->SetDisplayBufferFormat(m_displayBufferFormat); } if (m_bakeAcesOutputTransformLutPass) @@ -509,7 +510,8 @@ namespace AZ if (desc.m_operationType != m_displayMapperConfigurationDescriptor.m_operationType || desc.m_ldrGradingLutEnabled != m_displayMapperConfigurationDescriptor.m_ldrGradingLutEnabled || - desc.m_ldrColorGradingLut != m_displayMapperConfigurationDescriptor.m_ldrColorGradingLut) + desc.m_ldrColorGradingLut != m_displayMapperConfigurationDescriptor.m_ldrColorGradingLut || + desc.m_acesParameterOverrides.m_overrideDefaults != m_displayMapperConfigurationDescriptor.m_acesParameterOverrides.m_overrideDefaults) { m_needToRebuildChildren = true; } diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index 29f0636e9e..4059d65cbb 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -231,6 +231,19 @@ namespace AZ return {}; } + Data::Instance MeshFeatureProcessor::GetObjectSrg(const MeshHandle& meshHandle) const + { + return meshHandle.IsValid() ? meshHandle->m_shaderResourceGroup : nullptr; + } + + void MeshFeatureProcessor::QueueObjectSrgForCompile(const MeshHandle& meshHandle) const + { + if (meshHandle.IsValid()) + { + meshHandle->m_objectSrgNeedsUpdate = true; + } + } + void MeshFeatureProcessor::SetMaterialAssignmentMap(const MeshHandle& meshHandle, const Data::Instance& material) { Render::MaterialAssignmentMap materials; diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp index 388f4112a0..cb2d6a69ef 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp @@ -71,16 +71,6 @@ namespace AZ } - void SkinnedMeshFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet) - { - AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); - AZ_ATOM_PROFILE_FUNCTION("SkinnedMesh", "SkinnedMeshFeatureProcessor: Simulate"); - AZ_UNUSED(packet); - - SkinnedMeshFeatureProcessorNotificationBus::Broadcast(&SkinnedMeshFeatureProcessorNotificationBus::Events::OnUpdateSkinningMatrices); - - } - void SkinnedMeshFeatureProcessor::Render(const FeatureProcessor::RenderPacket& packet) { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); @@ -268,6 +258,8 @@ namespace AZ void SkinnedMeshFeatureProcessor::OnBeginPrepareRender() { m_renderProxiesChecker.soft_lock(); + + SkinnedMeshFeatureProcessorNotificationBus::Broadcast(&SkinnedMeshFeatureProcessorNotificationBus::Events::OnUpdateSkinningMatrices); } void SkinnedMeshFeatureProcessor::OnRenderEnd() diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h index bb3dd242a1..75d41742b2 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h @@ -49,7 +49,6 @@ namespace AZ // FeatureProcessor overrides ... void Activate() override; void Deactivate() override; - void Simulate(const FeatureProcessor::SimulatePacket& packet) override; void Render(const FeatureProcessor::RenderPacket& packet) override; void OnRenderEnd() override; diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/ShaderResourceGroupLayout.h b/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/ShaderResourceGroupLayout.h index c1d4fb49f0..fd14cadde8 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/ShaderResourceGroupLayout.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/ShaderResourceGroupLayout.h @@ -75,6 +75,8 @@ namespace AZ */ bool Finalize(); + void SetName(const Name& name) { m_name = name; } + const Name& GetName() const { return m_name; } /** * Designates this SRG as ShaderVariantKey fallback by providing the generated @@ -272,6 +274,9 @@ namespace AZ AZ_SERIALIZE_FRIEND(); + //! Name of the ShaderResourceGroup as specified in the original *.azsl/*.azsli file. + Name m_name; + AZStd::vector m_staticSamplers; AZStd::vector m_inputsForBuffers; diff --git a/Gems/Atom/RHI/Code/Source/RHI.Reflect/ShaderResourceGroupLayout.cpp b/Gems/Atom/RHI/Code/Source/RHI.Reflect/ShaderResourceGroupLayout.cpp index 210fe7ad45..2c552d7c56 100644 --- a/Gems/Atom/RHI/Code/Source/RHI.Reflect/ShaderResourceGroupLayout.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI.Reflect/ShaderResourceGroupLayout.cpp @@ -22,7 +22,8 @@ namespace AZ if (SerializeContext* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(6) + ->Version(7) + ->Field("m_name", &ShaderResourceGroupLayout::m_name) ->Field("m_staticSamplers", &ShaderResourceGroupLayout::m_staticSamplers) ->Field("m_inputsForBuffers", &ShaderResourceGroupLayout::m_inputsForBuffers) ->Field("m_inputsForImages", &ShaderResourceGroupLayout::m_inputsForImages) diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PipelineLayout.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PipelineLayout.cpp index 457768b357..9e6c38e7ac 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PipelineLayout.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PipelineLayout.cpp @@ -66,21 +66,21 @@ namespace AZ } } - RHI::ConstPtr PipelineLayout::MergeShaderResourceGroupLayouts(const AZStd::vector& srgLayouts) const + RHI::ConstPtr PipelineLayout::MergeShaderResourceGroupLayouts(const AZStd::vector& srgLayoutList) const { - if (srgLayouts.empty()) + if (srgLayoutList.empty()) { return nullptr; } - if (srgLayouts.size() == 1) + if (srgLayoutList.size() == 1) { - return srgLayouts.front(); + return srgLayoutList.front(); } RHI::Ptr mergedLayout = RHI::ShaderResourceGroupLayout::Create(); - mergedLayout->SetBindingSlot(srgLayouts.front()->GetBindingSlot()); - for (const RHI::ShaderResourceGroupLayout* srgLayout : srgLayouts) + mergedLayout->SetBindingSlot(srgLayoutList.front()->GetBindingSlot()); + for (const RHI::ShaderResourceGroupLayout* srgLayout : srgLayoutList) { const uint32_t bindingSlot = srgLayout->GetBindingSlot(); const auto& srgBindingInfo = m_layoutDescriptor->GetShaderResourceGroupBindingInfo(m_layoutDescriptor->GetShaderResourceGroupIndexFromBindingSlot(bindingSlot)); diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PipelineLayout.h b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PipelineLayout.h index 8af703fefb..4b7b344074 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PipelineLayout.h +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PipelineLayout.h @@ -85,7 +85,7 @@ namespace AZ RHI::ResultCode BuildMergedShaderResourceGroupPools(); // Creates a merged SRG layout from a list of SRG layouts. - RHI::ConstPtr MergeShaderResourceGroupLayouts(const AZStd::vector& srgLayouts) const; + RHI::ConstPtr MergeShaderResourceGroupLayouts(const AZStd::vector& srgLayoutList) const; VkPipelineLayout m_nativePipelineLayout = VK_NULL_HANDLE; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderSourceData.h index 8184f8b5e6..428898a17e 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderSourceData.h @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include @@ -38,12 +38,13 @@ namespace AZ AZ_TYPE_INFO(AZ::RPI::ShaderSourceData, "{B7F00402-872B-4F82-A210-E1A79A366686}"); AZ_CLASS_ALLOCATOR(ShaderSourceData, AZ::SystemAllocator, 0); - static const char* Extension; + static constexpr char Extension[] = "shader"; + static constexpr char Extension2[] = "shader2"; static void Reflect(ReflectContext* context); //! Helper function. Returns true if @rhiName is present in m_disabledRhiBackends - bool IsRhiBackendDisabled(const AZ::Name& rhiName); + bool IsRhiBackendDisabled(const AZ::Name& rhiName) const; struct EntryPoint { @@ -71,12 +72,49 @@ namespace AZ RHI::DepthStencilState m_depthStencilState; RHI::RasterState m_rasterState; RHI::TargetBlendState m_blendState; - - // Hints for building the shader option group layout - RPI::ShaderOptionGroupHints m_shaderOptionGroupHints; //! List of RHI Backends (aka ShaderPlatformInterface) for which this shader should not be compiled. AZStd::vector m_disabledRhiBackends; + + struct SupervariantInfo + { + AZ_TYPE_INFO(AZ::RPI::ShaderSourceData::SupervariantInfo, "{1132CF2A-C8AB-4DD2-AA90-3021D49AB955}"); + + //! Unique name of the supervariant. + //! If left empty, the data refers to the default supervariant. + AZ::Name m_name; + + //! + MCPP Macro definition arguments + AZSLc arguments. + //! These arguments are added after shader_global_build_options.json & m_compiler.m_azslcAdditionalFreeArguments. + //! Arguments that start with "-D" are given to MCPP. + //! Example: "-DMACRO1 -DMACRO2=3". + //! all other arguments are given to AZSLc. + //! Note the arguments are added in addition to the arguments + //! in /Config/shader_global_build_options.json + AZStd::string m_plusArguments; + + //! Opposite to @m_plusArguments. + //! - MCPP Macro definition arguments - AZSLc arguments. + //! Because there are global compilation arguments, this one is useful to remove some of those arguments + //! in order to customize the compilation of a particular supervariant. + AZStd::string m_minusArguments; + + //! Helper function. Parses @m_minusArguments and @m_plusArguments, looks for arguments of type -D[=] and returns + //! a list of to remove. + AZStd::vector GetCombinedListOfMacroDefinitionNamesToRemove() const; + + //! Helper function. Parses @m_plusArguments, looks for arguments of type "-D[=]" and returns + //! a list of "[=]". + AZStd::vector GetMacroDefinitionsToAdd() const; + + //! Helper function. Takes AZSLc arguments from @m_minusArguments and @m_plusArguments, removes them from @initialAzslcCompilerArguments. + //! Takes AZSLc arguments from @m_plusArguments and appends them to @initialAzslcCompilerArguments. + //! Returns a new string with customized arguments. + AZStd::string GetCustomizedArgumentsForAzslc(const AZStd::string& initialAzslcCompilerArguments) const; + }; + + //! Optional list of supervariants. + AZStd::vector m_supervariants; }; } // namespace RPI diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantAssetCreator2.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantAssetCreator2.h new file mode 100644 index 0000000000..07cec9a01f --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantAssetCreator2.h @@ -0,0 +1,54 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + //! The "builder" pattern class that creates a ShaderVariantAsset2. + class ShaderVariantAssetCreator2 final + : public AssetCreator + { + public: + //! Begins construction of the shader variant asset. + //! @param assetId The "initial" assetId that the resulting ShaderVariantAsset will get. + //! "initial" was quoted because in the end the asset processor will assign another assetId + //! because on the UUID of the source asset (a *.shadervariantlist file) and the product subid + //! that gets assign when returning the Job Response. + //! It is still useful, because when creating the Root Variant for the ShaderAsset this assetId should + //! match the value that will be assigned by the asset processor because the Root Variant is serialized + //! as a Data::Asset inside the ShaderAsset. + void Begin(const AZ::Data::AssetId& assetId, const ShaderVariantId& shaderVariantId, RPI::ShaderVariantStableId stableId, bool isFullyBaked); + + //! Finalizes and assigns ownership of the asset to result, if successful. + //! Otherwise false is returned and result is left untouched. + bool End(Data::Asset& result); + + ///////////////////////////////////////////////////////////////////// + // Methods for all shader variant types + + //! Set the timestamp value when the ProcessJob() started. + //! This is needed to synchronize between the ShaderAsset and ShaderVariantAsset when hot-reloading shaders. + //! The idea is that this timestamp must be greater or equal than the ShaderAsset. + void SetBuildTimestamp(AZStd::sys_time_t buildTimestamp); + + //! Assigns a shaderStageFunction, which contains the byte code, to the slot dictated by the shader stage. + void SetShaderFunction(RHI::ShaderStage shaderStage, RHI::Ptr shaderStageFunction); + + }; + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h index eaef4e093b..c284627cc5 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h @@ -30,6 +30,7 @@ namespace AZ AZ_CLASS_ALLOCATOR(ShaderVariantListSourceData, AZ::SystemAllocator, 0); static constexpr const char* Extension = "shadervariantlist"; + static constexpr const char* Extension2 = "shadervariantlist2"; static void Reflect(ReflectContext* context); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/Shader2.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/Shader2.h new file mode 100644 index 0000000000..eb82c2d31a --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/Shader2.h @@ -0,0 +1,194 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include + +#include +#include +#include + +#include +#include + +#include + +#include + +namespace AZ +{ + namespace RHI + { + class PipelineStateCache; + } + + namespace RPI + { + /** + * Shader2 is effectively an 'uber-shader' containing a collection of 'variants'. Variants are + * designed to be 'variations' on the same core shader technique. To enforce this, every variant + * in the shader shares the same pipeline layout (i.e. set of shader resource groups). + * + * A shader owns a library of pipeline states. When a variant is resolved to a pipeline state, its + * lifetime is determined by the lifetime of the Shader2 (unless an explicit reference is taken). If + * an asset reload event occurs, the pipeline state cache is reset. + * + * To use Shader2: + * 1) Construct a ShaderOptionGroup instance using CreateShaderOptionGroup. + * 2) Configure the group by setting values on shader options. + * 3) Find the ShaderVariantStableId using the ShaderVariantId generated from the configured ShaderOptionGroup. + * 4) Acquire the ShaderVariant2 instance using the ShaderVariantStableId. + * 5) Configure a pipeline state descriptor on the variant; make local overrides as necessary (e.g. to configure runtime render state). + * 6) Acquire a RHI::PipelineState instance from the shader using the configured pipeline state descriptor. + * + * Remember that the returned RHI::PipelineState instance lifetime is tied to the Shader2 lifetime. + * If you need guarantee lifetime, it is safe to take a reference on the returned pipeline state. + */ + class Shader2 final + : public Data::InstanceData + , public Data::AssetBus::Handler + , public ShaderVariantFinderNotificationBus2::Handler + { + friend class ShaderSystem; + public: + AZ_INSTANCE_DATA(Shader2, "{232D8BD6-3BD4-4842-ABD2-F380BD5B0863}"); + AZ_CLASS_ALLOCATOR(Shader2, SystemAllocator, 0); + + /// Returns the shader instance associated with the provided asset. + static Data::Instance FindOrCreate(const Data::Asset& shaderAsset, const Name& supervariantName); + + ~Shader2(); + AZ_DISABLE_COPY_MOVE(Shader2); + + /// Constructs a shader option group suitable to generate a shader variant key for this shader. + ShaderOptionGroup CreateShaderOptionGroup() const; + + /// Finds the best matching ShaderVariant2 for the given shaderVariantId, + /// If the variant is loaded and ready it will return the corresponding ShaderVariant2. + /// If the variant is not yet available it will return the root ShaderVariant2. + /// Callers should listen to ShaderReloadNotificationBus to get notified whenever the exact + /// variant is loaded and available or if a variant changes, etc. + /// This function should be your one stop shop to get a ShaderVariant2 from a ShaderVariantId. + /// Alternatively: You can call FindVariantStableId() followed by GetVariant(shaderVariantStableId). + const ShaderVariant2& GetVariant(const ShaderVariantId& shaderVariantId); + + /// Finds the best matching shader variant asset and returns its StableId. + /// In cases where you can't cache the ShaderVariant2, and recurrently you may need + /// the same ShaderVariant2 at different times, then it can be convenient (and more performant) to call + /// this method to cache the ShaderVariantStableId and call GetVariant(ShaderVariantStableId) + /// when needed. + /// If the asset is not immediately found in the file system, it will return the StableId + /// of the root variant. + /// Callers should listen to ShaderReloadNotificationBus to get notified whenever the exact + /// variant is loaded and available or if a variant changes, etc. + ShaderVariantSearchResult FindVariantStableId(const ShaderVariantId& shaderVariantId) const; + + /// Returns the variant associated with the provided StableId. + /// You should call FindVariantStableId() which caches the variant, later + /// when this function is called the variant is fetched from a local map. + /// If the variant is not found, the root variant is returned. + /// "Alternatively: a more convenient approach is to call GetVariant(ShaderVariantId) which does both, the find and the get." + const ShaderVariant2& GetVariant(ShaderVariantStableId shaderVariantStableId); + + /// Convenient function that returns the root variant. + const ShaderVariant2& GetRootVariant(); + + /// Returns the pipeline state type generated by variants of this shader. + RHI::PipelineStateType GetPipelineStateType() const; + + //! Returns the ShaderInputContract which describes which inputs the shader requires + const ShaderInputContract& GetInputContract() const; + + //! Returns the ShaderOutputContract which describes which outputs the shader requires + const ShaderOutputContract& GetOutputContract() const; + + /// Acquires a pipeline state directly from a descriptor. + const RHI::PipelineState* AcquirePipelineState(const RHI::PipelineStateDescriptor& descriptor) const; + + /// Finds and returns the shader resource group asset with the requested name. Returns an empty handle if no matching group was found. + const RHI::Ptr FindShaderResourceGroupLayout(const Name& shaderResourceGroupName) const; + + /// Finds and returns the shader resource group asset associated with the requested binding slot. Returns an empty handle if no matching group was found. + const RHI::Ptr FindShaderResourceGroupLayout(uint32_t bindingSlot) const; + + /// Finds and returns the shader resource group asset designated as a ShaderVariantKey fallback. + const RHI::Ptr FindFallbackShaderResourceGroupLayout() const; + + /// Returns the set of shader resource groups referenced by all variants in the shader asset. + AZStd::array_view> GetShaderResourceGroupLayouts() const; + + /// Returns a reference to the asset used to initialize this shader. + const Data::Asset& GetAsset() const; + + //! Returns the DrawListTag that identifies which Pass and View objects will process this shader. + //! This tag corresponds to the ShaderAsset2 object's DrawListName. + RHI::DrawListTag GetDrawListTag() const; + + private: + Shader2() = default; + + static Data::Instance CreateInternal(ShaderAsset2& shaderAsset); + + bool SelectSupervariant(const Name& supervariantName); + + RHI::ResultCode Init(ShaderAsset2& shaderAsset); + + void Shutdown(); + + ConstPtr LoadPipelineLibrary() const; + void SavePipelineLibrary() const; + + /////////////////////////////////////////////////////////////////// + /// AssetBus overrides + void OnAssetReloaded(Data::Asset asset) override; + /////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + /// ShaderVariantFinderNotificationBus overrides + void OnShaderVariantTreeAssetReady(Data::Asset /*shaderVariantTreeAsset*/, bool /*isError*/) override {}; + void OnShaderVariantAssetReady(Data::Asset shaderVariantAsset, bool IsError) override; + /////////////////////////////////////////////////////////////////// + + //! Returns the path to the pipeline library cache file. + AZStd::string GetPipelineLibraryPath() const; + + //! A strong reference to the shader asset. + Data::Asset m_asset; + + //! Selects current supervariant to be used. + //! This value is defined at instantiation. + SupervariantIndex m_supervariantIndex; + + //! The pipeline state type required by this shader. + RHI::PipelineStateType m_pipelineStateType = RHI::PipelineStateType::Draw; + + //! A cached pointer to the pipeline state cache owned by RHISystem. + RHI::PipelineStateCache* m_pipelineStateCache = nullptr; + + //! A handle to the pipeline library in the pipeline state cache. + RHI::PipelineLibraryHandle m_pipelineLibraryHandle; + + //! Used for thread safety for FindVariantStableId() and GetVariant(). + AZStd::shared_mutex m_variantCacheMutex; + + //! The root variant always exist. + ShaderVariant2 m_rootVariant; + + //! Local cache of ShaderVariants (except for the root variant), searchable by StableId. + //! Gets populated when GetVariant() is called. + AZStd::unordered_map m_shaderVariants; + + //! DrawListTag associated with this shader. + RHI::DrawListTag m_drawListTag; + }; + } +} diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderReloadNotificationBus2.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderReloadNotificationBus2.h new file mode 100644 index 0000000000..0822336ffa --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderReloadNotificationBus2.h @@ -0,0 +1,58 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include +#include + +//#include +#include + +namespace AZ +{ + namespace RPI + { + class Shader2; + class ShaderAsset2; + + /** + * Connect to this EBus to get notifications whenever a Data::Instance reloads its ShaderAsset. + * The bus address is the AssetId of the ShaderAsset. + */ + class ShaderReloadNotifications2 + : public EBusTraits + { + + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; + typedef Data::AssetId BusIdType; + ////////////////////////////////////////////////////////////////////////// + + virtual ~ShaderReloadNotifications2() {} + + //! Called when the ShaderAsset reinitializes itself in response to another asset being reloaded. + virtual void OnShaderAssetReinitialized(const Data::Asset& shaderAsset) { AZ_UNUSED(shaderAsset); } + + //! Called when the Shader instance reinitializes itself in response to the ShaderAsset being reloaded. + virtual void OnShaderReinitialized(const Shader2& shader) { AZ_UNUSED(shader); } + + //! Called when a particular shader variant is reinitialized. + virtual void OnShaderVariantReinitialized(const Shader2& shader, const ShaderVariantId& shaderVariantId, ShaderVariantStableId shaderVariantStableId) + { AZ_UNUSED(shader); AZ_UNUSED(shaderVariantId); AZ_UNUSED(shaderVariantStableId) } + }; + + typedef EBus ShaderReloadNotificationBus2; + + } // namespace RPI +} //namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderResourceGroup.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderResourceGroup.h index 6d99d287f3..d99b8f51b5 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderResourceGroup.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderResourceGroup.h @@ -13,6 +13,8 @@ #include #include +#include +#include #include #include @@ -63,6 +65,10 @@ namespace AZ /// Instantiates a unique shader resource group instance using its paired asset. static Data::Instance Create(const Data::Asset& srgAsset); + /// [GFX TODO] [ATOM-15472] Shader Build Pipeline: Remove Deprecated Files And Functions That Predate The Shader Supervariants + /// This is a temporary hack to enable integration of the new supervariant system. + bool ReplaceSrgLayoutUsingShaderAsset(Data::Asset shaderAsset, const Name& supervariantName, const Name& srgName); + /// Queues a request that the underlying hardware shader resource group be compiled. void Compile(); @@ -278,6 +284,7 @@ namespace AZ ShaderResourceGroup() = default; RHI::ResultCode Init(ShaderResourceGroupAsset& shaderResourceGroupAsset); + static AZ::Data::Instance CreateInternal(ShaderResourceGroupAsset& srgAsset); /// A name to be used in error messages @@ -298,9 +305,12 @@ namespace AZ /// The shader resource group that can be submitted to the renderer RHI::Ptr m_shaderResourceGroup; - /// A reference to the parent template asset used to initialize and manipulate this group. + /// A reference to the SRG asset used to initialize and manipulate this group. AZ::Data::Asset m_asset; + /// A reference to the shader asset used to initialize and manipulate this group. + AZ::Data::Asset m_shaderAsset; + /// A pointer to the layout inside of m_srgAsset const RHI::ShaderResourceGroupLayout* m_layout = nullptr; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant.h index 30c1a6b18d..d189d26b13 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant.h @@ -61,9 +61,6 @@ namespace AZ const ShaderAsset& shaderAsset, Data::Asset shaderVariantAsset); - // Returns a shader stage function associated with the provided enum value, or null if no function exists. - const RHI::ShaderStageFunction* GetShaderStageFunction(RHI::ShaderStage shaderStage) const; - // Cached state from the asset to avoid an indirection. RHI::PipelineStateType m_pipelineStateType = RHI::PipelineStateType::Count; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant2.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant2.h new file mode 100644 index 0000000000..524ebf6a3c --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant2.h @@ -0,0 +1,71 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include + +#include + +namespace AZ +{ + namespace RPI + { + //! Represents the concrete state to configure a PipelineStateDescriptor. ShaderVariant2's match + //! the RHI::PipelineStateType of the parent Shader instance. For shaders on the raster + //! pipeline, the RHI::DrawFilterTag is also provided. + class ShaderVariant2 final + { + friend class Shader2; + public: + ShaderVariant2() = default; + AZ_DEFAULT_COPY_MOVE(ShaderVariant2); + + //! Fills a pipeline state descriptor with settings provided by the ShaderVariant2. (Note that + //! this does not fill the InputStreamLayout or OutputAttachmentLayout as that also requires + //! information from the mesh data and pass system and must be done as a separate step). + void ConfigurePipelineState(RHI::PipelineStateDescriptor& descriptor) const; + + const ShaderVariantId& GetShaderVariantId() const { return m_shaderVariantAsset->GetShaderVariantId(); } + + //! Returns whether the variant is fully baked variant (all options are static branches), or false if the + //! variant uses dynamic branches for some shader options. + //! If the shader variant is not fully baked, the ShaderVariantKeyFallbackValue must be correctly set when drawing. + bool IsFullyBaked() const { return m_shaderVariantAsset->IsFullyBaked(); } + + //! Return the timestamp when this asset was built. + //! This is used to synchronize versions of the ShaderAsset and ShaderVariantAsset, especially during hot-reload. + //! This timestamp must be >= than the ShaderAsset timestamp. + AZStd::sys_time_t GetBuildTimestamp() const { return m_shaderVariantAsset->GetBuildTimestamp(); } + + bool IsRootVariant() const { return m_shaderVariantAsset->IsRootVariant(); } + + ShaderVariantStableId GetStableId() const { return m_shaderVariantAsset->GetStableId(); } + + private: + // Called by Shader. Initializes runtime data from asset data. Returns whether the call succeeded. + bool Init( + const ShaderAsset2& shaderAsset, + Data::Asset shaderVariantAsset, + SupervariantIndex supervariantIndex); + + // Cached state from the asset to avoid an indirection. + RHI::PipelineStateType m_pipelineStateType = RHI::PipelineStateType::Count; + + // State assigned to the pipeline state descriptor. + RHI::ConstPtr m_pipelineLayoutDescriptor; + + Data::Asset m_shaderVariantAsset; + + const RHI::RenderStates* m_renderStates = nullptr; // Cached from ShaderAsset2. + }; + } +} diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/MorphTargetMetaAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/MorphTargetMetaAsset.h index 5b92047226..4aa3faa6c9 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/MorphTargetMetaAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/MorphTargetMetaAsset.h @@ -15,6 +15,7 @@ #include #include #include +#include namespace AZ::RPI { @@ -56,6 +57,9 @@ namespace AZ::RPI float m_minPositionDelta; float m_maxPositionDelta; + //! Reference to the wrinkle mask, if it exists + AZ::Data::Asset m_wrinkleMask; + //! Boolean to indicate the presence or absence of color deltas bool m_hasColorDeltas = false; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/IShaderVariantFinder2.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/IShaderVariantFinder2.h new file mode 100644 index 0000000000..e09169c9a0 --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/IShaderVariantFinder2.h @@ -0,0 +1,113 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include + +#include +#include + +namespace AZ +{ + namespace RPI + { + class ShaderAsset2; + class ShaderVariantTreeAsset; + class ShaderVariantAsset2; + + //! This is the AZ::Interface<> declaration for the singleton responsible + //! for finding the best ShaderVariantAsset a shader can use. + //! This interface is public only to the ShaderAsset class. + //! The expectation is that when in need of shader variants the developer + //! should use AZ::RPI::Shader::GetVariant(). + class IShaderVariantFinder2 + { + public: + AZ_TYPE_INFO(IShaderVariantFinder2, "{4E041C2C-F158-412E-8961-76987EC75692}"); + + static constexpr const char* LogName = "IShaderVariantFinder2"; + + virtual ~IShaderVariantFinder2() = default; + + //! This function should be your one stop shop. + //! It simply queues the request to load a shader variant asset. + //! This function will automatically queue the ShaderVariantTreeAsset for loading if not available. + //! Afther the ShaderVariantTreeAsset is loaded and ready, it is used to find the best matching ShaderVariantStableId + //! from the given ShaderVariantId. If a valid ShaderVariantStableId is found, it will be queued for loading. + //! Eventually the caller will be notified via ShaderVariantFinderNotificationBus::OnShaderVariantAssetReady() + //! The notification will occur on the Main Thread. + virtual bool QueueLoadShaderVariantAssetByVariantId( + Data::Asset shaderAsset, const ShaderVariantId& shaderVariantId, SupervariantIndex supervariantIndex) = 0; + + //! This function does the first half of the work. It simply queues the loading of the ShaderVariantTreeAsset. + //! Given the AssetId of a ShaderAsset it will try to find and load its corresponding ShaderVariantTreeAsset from + //! the asset cache. If found, the asset will be loaded asynchronously and the caller will be notified via + //! ShaderVariantFinderNotificationBus on main thread when the ShaderVariantTreeAsset is fully loaded. + //! It is possible the requested ShaderVariantTreeAsset will never come into existence and in such + //! case the caller will NEVER be notified. + //! Returns true if the request was queued successfully. + virtual bool QueueLoadShaderVariantTreeAsset(const Data::AssetId& shaderAssetId) = 0; + + //! This function does the second half of the work. + //! Given the AssetId of a ShaderVariantTreeAsset and the stable id of a ShaderVariantAsset it will try to + //! find its corresponding ShaderVariantAsset from the asset cache. If found, the asset will be loaded + //! asynchronously and the caller will be notified via ShaderVariantFinderNotificationBus on main thread when the + //! ShaderVariantAsset is fully loaded. + //! Returns true if the request was queued successfully. + virtual bool QueueLoadShaderVariantAsset( + const Data::AssetId& shaderVariantTreeAssetId, ShaderVariantStableId variantStableId, + SupervariantIndex supervariantIndex) = 0; + + //! This is a quick blocking call that will return a valid asset only if it's been fully loaded already, + //! Otherwise it returns an invalid asset and the caller is supposed to call QueueLoadShaderVariantAssetByVariantId(). + virtual Data::Asset GetShaderVariantAssetByVariantId( + Data::Asset shaderAsset, const ShaderVariantId& shaderVariantId, SupervariantIndex supervariantIndex) = 0; + + virtual Data::Asset GetShaderVariantAssetByStableId( + Data::Asset shaderAsset, ShaderVariantStableId shaderVariantStableId, SupervariantIndex supervariantIndex) = 0; + + //! This is a quick blocking call that will return a valid asset only if it's been fully loaded already, + //! Otherwise it returns an invalid asset and the caller is supposed to call QueueLoadShaderVariantTreeAsset(). + virtual Data::Asset GetShaderVariantTreeAsset(const Data::AssetId& shaderAssetId) = 0; + + //! This is a quick blocking call that will return a valid asset only if i's been fully loaded already, + //! Otherwise it returns an invalid asset and the caller is supposed to call QueueLoadShaderVariantAsset(). + virtual Data::Asset GetShaderVariantAsset( + const Data::AssetId& shaderVariantTreeAssetId, ShaderVariantStableId variantStableId, + SupervariantIndex supervariantIndex) = 0; + + //! Clears the cache of loaded ShaderVariantTreeAsset and ShaderVariantAsset objects. + //! This is intended for testing. + virtual void Reset() = 0; + }; + + //! IShaderVariantFinder2 will call on this notification bus on the main thread. + //! Only the following classes are supposed to register to this notification bus: + //! AZ::RPI::ShaderAsset & AZ::RPI::Shader + class ShaderVariantFinderNotification2 + : public EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; + using MutexType = AZStd::recursive_mutex; + typedef Data::AssetId BusIdType; // The AssetId of the shader asset. + ////////////////////////////////////////////////////////////////////////// + + virtual void OnShaderVariantTreeAssetReady(Data::Asset shaderVariantTreeAsset, bool isError) = 0; + virtual void OnShaderVariantAssetReady(Data::Asset shaderVariantAsset, bool isError) = 0; + }; + using ShaderVariantFinderNotificationBus2 = AZ::EBus; + + } // namespace RPI +}// namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h index 4afc3a1a46..cc31b0735a 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,8 @@ namespace AZ //! The default shader variant (i.e. the one without any options set). static const ShaderVariantStableId RootShaderVariantStableId; + // @subProductType is one of ShaderAssetSubId, or (ShaderAssetSubId::GeneratedHlslSource + 1)+ + static uint32_t MakeAssetProductSubId(uint32_t rhiApiUniqueIndex, uint32_t subProductType); ShaderAsset() = default; ~ShaderAsset(); @@ -218,83 +221,21 @@ namespace AZ ////////////////////////////////////////////////////////////////////////// // Deprecated System - enum class ShaderStageType : uint32_t - { - Vertex, - Geometry, - TessellationControl, - TessellationEvaluation, - Fragment, - Compute, - RayTracing - }; - - const char* ToString(ShaderStageType shaderStageType); - - void ReflectShaderStageType(ReflectContext* context); - enum class ShaderAssetSubId : uint32_t { ShaderAsset = 0, - StreamLayout, - GraphicsPipelineState, - OutputMergerState, RootShaderVariantAsset, - //[GFX TODO][LY-82895] (arsentuf) These shader stages are going to get reworked when virtual stages are implemented - AzVertexShader, - AzGeometryShader, - AzTessellationControlShader, - AzTessellationEvaluationShader, - AzFragmentShader, - AzComputeShader, - AzRayTracingShader, - DebugByProduct, PostPreprocessingPureAzsl, // .azslin IaJson, OmJson, SrgJson, OptionsJson, BindingdepJson, - GeneratedSource // This must be last because we use this as a base for adding the RHI::APIType when generating shadersource for multiple RHI APIs. - }; - - ShaderAssetSubId ShaderStageToSubId(ShaderStageType stageType); - - class ShaderStageDescriptor final - { - public: - AZ_TYPE_INFO(ShaderStageDescriptor, "{3E7822F7-B952-4379-B0A0-48507681845A}"); - AZ_CLASS_ALLOCATOR(ShaderStageDescriptor, AZ::SystemAllocator, 0); - - static void Reflect(ReflectContext* context); - - ShaderStageType m_stageType; - AZStd::vector m_byteCode; - AZStd::vector m_sourceCode; - AZStd::string m_entryFunctionName; + GeneratedHlslSource // This must be last because we use this as a base for adding the RHI::APIType when generating shadersource for multiple RHI APIs. }; - //[GFX TODO][LY-82803] (arsentuf) Remove this when we've fleshed out Virtual Shader stages - class ShaderStageAsset final - : public AZ::Data::AssetData - { - public: - AZ_RTTI(ShaderStageAsset, "{975F48B5-1577-41C9-B8F5-A1024E2D01F1}", AZ::Data::AssetData); - AZ_CLASS_ALLOCATOR(ShaderStageAsset, AZ::SystemAllocator, 0); - - static void Reflect(ReflectContext* context); - - ShaderStageAsset() = default; - ShaderStageAsset(const ShaderStageAsset&); - ShaderStageAsset& operator= (const ShaderStageAsset&); - ShaderStageAsset(ShaderStageAsset&& rhs); - - AZStd::shared_ptr m_descriptor; - AZStd::vector m_srgLayouts; - }; ////////////////////////////////////////////////////////////////////////// } // namespace RPI - AZ_TYPE_INFO_SPECIALIZE(RPI::ShaderStageType, "{A6408508-748B-4963-B618-E1E6ECA3629A}"); } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset2.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset2.h new file mode 100644 index 0000000000..632c35bb82 --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset2.h @@ -0,0 +1,339 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +namespace AZ +{ + namespace RPI + { + using ShaderResourceGroupLayoutList = AZStd::fixed_vector, RHI::Limits::Pipeline::ShaderResourceGroupCountMax>; + + enum class ShaderAsset2ProductSubId : uint32_t + { + ShaderAsset2 = 0, //!< for .azshader file, One per .shader. + RootShaderVariantAsset, //!< for .azshadervariant, one per supervariant and referenced inside the .azshader. + AzslFlat, //!< .azslin, this file contains the result of preprocessing an azsl file with MCPP, along with prepending the per-RHI azsli header. + IaJson, //!< .ia.json, Input Assembly reflection data. + OmJson, //!< .om.json, Output Merger reflection data. + SrgJson, //!< .srg.json, Shader Resource Group reflection data. + OptionsJson, //!< .options.json, Shader Options reflection data. + BindingdepJson, //!<.bindingdep.json, Binding dependencies. + GeneratedHlslSource, //!<.hlsl code generated with AZSLc. + FirstByProduct, //!< This must be last because we use this as a base for adding all the debug byProducts generated + //!< with dxc, or spirv-cross, etc. + }; + + class ShaderAsset2 final + : public Data::AssetData + , public ShaderVariantFinderNotificationBus2::Handler + , public Data::AssetBus::Handler + { + friend class ShaderAssetCreator2; + friend class ShaderAssetHandler2; + friend class ShaderAssetTester2; + public: + AZ_RTTI(ShaderAsset2, "{823395A3-D570-49F4-99A9-D820CD1DEF98}", Data::AssetData); + static void Reflect(ReflectContext* context); + + static constexpr char DisplayName[] = "Shader"; + static constexpr char Extension[] = "azshader2"; + static constexpr char Group[] = "Shader"; + + //! The default shader variant (i.e. the one without any options set). + static const ShaderVariantStableId RootShaderVariantStableId; + + // @subProductType is one of ShaderAsset2ProductSubId, or ShaderAsset2ProductSubId::FirstByProduct+ + static uint32_t MakeProductAssetSubId(uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, uint32_t subProductType); + static SupervariantIndex GetSupervariantIndexFromProductAssetSubId(uint32_t assetProducSubId); + static SupervariantIndex GetSupervariantIndexFromAssetId(const Data::AssetId& assetId); + + + ShaderAsset2() = default; + ~ShaderAsset2(); + + AZ_DISABLE_COPY_MOVE(ShaderAsset2); + + + //! Returns the name of the shader. + const Name& GetName() const; + + //! Returns the pipeline state type generated by variants of this shader. + RHI::PipelineStateType GetPipelineStateType() const; + + //! Returns the draw list tag name. + //! To get the corresponding DrawListTag use DrawListTagRegistry's FindTag() or AcquireTag() (see + //! RHISystemInterface::GetDrawListTagRegistry()). The DrawListTag is also available in the Shader that corresponds to this + //! ShaderAsset2. + const Name& GetDrawListName() const; + + //! Return the timestamp when the shader asset was built. + //! This is used to synchronize versions of the ShaderAsset2 and ShaderVariantTreeAsset, especially during hot-reload. + AZStd::sys_time_t GetShaderAssetBuildTimestamp() const; + + //! Returns the shader option group layout. + const ShaderOptionGroupLayout* GetShaderOptionGroupLayout() const; + + SupervariantIndex GetSupervariantIndex(const AZ::Name& supervariantName) const; + + //! This function should be your one stop shop to get a ShaderVariantAsset. + //! Finds and returns the best matching ShaderVariantAsset given a ShaderVariantId. + //! If the ShaderVariantAsset is not fully loaded and ready at the moment, this function + //! will QueueLoad the ShaderVariantTreeAsset and subsequently will QueueLoad the ShaderVariantAsset. + //! The called will be notified via the ShaderVariantFinderNotificationBus when the + //! ShaderVariantAsset is loaded and ready. + //! In the mean time, if the required variant is not available this function + //! returns the Root Variant. + Data::Asset GetVariant( + const ShaderVariantId& shaderVariantId, SupervariantIndex supervariantIndex); + Data::Asset GetVariant(const ShaderVariantId& shaderVariantId) { return GetVariant(shaderVariantId, DefaultSupervariantIndex); } + + //! Finds the best matching shader variant and returns its StableId. + //! This function first loads and caches the ShaderVariantTreeAsset (if not done before). + //! If the ShaderVariantTreeAsset is not found (either the AssetProcessor has not generated it yet, or it simply doesn't exist), then + //! it returns a search result that identifies the root variant. + //! This function is thread safe. + ShaderVariantSearchResult FindVariantStableId(const ShaderVariantId& shaderVariantId); + + //! Returns the variant asset associated with the provided StableId. + //! The user should call FindVariantStableId() first to get a ShaderVariantStableId from a ShaderVariantId, + //! Or better yet, call GetVariant(ShaderVariantId) for maximum convenience. + //! If the requested variant is not found, the root variant will be returned AND the requested variant will be queued for loading. + //! Next time around if the variant has been loaded this function will return it. Alternatively + //! the caller can register with the ShaderVariantFinderNotificationBus to get the asset as soon as is available. + //! This function is thread safe. + Data::Asset GetVariant( + ShaderVariantStableId shaderVariantStableId, SupervariantIndex supervariantIndex) const; + Data::Asset GetVariant(ShaderVariantStableId shaderVariantStableId) const { return GetVariant(shaderVariantStableId, DefaultSupervariantIndex); } + + Data::Asset GetRootVariant(SupervariantIndex supervariantIndex) const; + Data::Asset GetRootVariant() const { return GetRootVariant(DefaultSupervariantIndex); } + + + //! Finds and returns the shader resource group asset with the requested name. Returns an empty handle if no matching group was + //! found. + const RHI::Ptr FindShaderResourceGroupLayout( + const Name& shaderResourceGroupName, SupervariantIndex supervariantIndex) const; + const RHI::Ptr FindShaderResourceGroupLayout(const Name& shaderResourceGroupName) const + { + return FindShaderResourceGroupLayout(shaderResourceGroupName, DefaultSupervariantIndex); + } + + //! Finds and returns the shader resource group layout associated with the requested binding slot. Returns an empty handle if no matching srg was found. + const RHI::Ptr FindShaderResourceGroupLayout( + uint32_t bindingSlot, SupervariantIndex supervariantIndex) const; + const RHI::Ptr FindShaderResourceGroupLayout(uint32_t bindingSlot) const + { + return FindShaderResourceGroupLayout(bindingSlot, DefaultSupervariantIndex); + } + + //! Finds and returns the shader resource group layout designated as a ShaderVariantKey fallback. + const RHI::Ptr FindFallbackShaderResourceGroupLayout( SupervariantIndex supervariantIndex) const; + const RHI::Ptr FindFallbackShaderResourceGroupLayout() const + { + return FindFallbackShaderResourceGroupLayout(DefaultSupervariantIndex); + } + + + //! Returns the set of shader resource group layouts owned by a given supervariant. + AZStd::array_view> GetShaderResourceGroupLayouts( SupervariantIndex supervariantIndex) const; + AZStd::array_view> GetShaderResourceGroupLayouts() const + { + return GetShaderResourceGroupLayouts(DefaultSupervariantIndex); + } + + //! Returns the pipeline layout descriptor shared by all variants in the asset. + const RHI::PipelineLayoutDescriptor* GetPipelineLayoutDescriptor(SupervariantIndex supervariantIndex) const; + const RHI::PipelineLayoutDescriptor* GetPipelineLayoutDescriptor() const + { + return GetPipelineLayoutDescriptor(DefaultSupervariantIndex); + } + + //! Returns the shader resource group asset that has per-draw frequency, which is added to every draw packet. + const RHI::Ptr GetDrawSrgLayout(SupervariantIndex supervariantIndex) const; + const RHI::Ptr GetDrawSrgLayout() const + { + return GetDrawSrgLayout(DefaultSupervariantIndex); + } + + + //! Returns the ShaderInputContract which describes which inputs the shader requires + const ShaderInputContract& GetInputContract(SupervariantIndex supervariantIndex) const; + const ShaderInputContract& GetInputContract() const + { + return GetInputContract(DefaultSupervariantIndex); + } + + + //! Returns the ShaderOuputContract which describes which outputs the shader requires + const ShaderOutputContract& GetOutputContract(SupervariantIndex supervariantIndex) const; + const ShaderOutputContract& GetOutputContract() const + { + return GetOutputContract(DefaultSupervariantIndex); + } + + + //! Returns the render states for the draw pipeline. Only used for draw pipelines. + const RHI::RenderStates& GetRenderStates(SupervariantIndex supervariantIndex) const; + const RHI::RenderStates& GetRenderStates() const + { + return GetRenderStates(DefaultSupervariantIndex); + } + + + //! Returns a list of arguments for the specified attribute, or nullopt_t if the attribute is not found. The list can be empty which is still valid. + AZStd::optional GetAttribute( + const RHI::ShaderStage& shaderStage, const Name& attributeName, SupervariantIndex supervariantIndex) const; + AZStd::optional GetAttribute( + const RHI::ShaderStage& shaderStage, const Name& attributeName) const + { + return GetAttribute(shaderStage, attributeName, DefaultSupervariantIndex); + } + + + private: + /////////////////////////////////////////////////////////////////// + /// AssetBus overrides + void OnAssetReloaded(Data::Asset asset) override; + /////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + /// ShaderVariantFinderNotificationBus2 overrides + void OnShaderVariantTreeAssetReady(Data::Asset shaderVariantTreeAsset, bool isError) override; + void OnShaderVariantAssetReady(Data::Asset /*shaderVariantAsset*/, bool /*isError*/) override {}; + /////////////////////////////////////////////////////////////////// + + //! A Supervariant represents a set of static shader compilation parameters. + //! Those parameters can be predefined c-preprocessor macros or specific arguments + //! for AZSLc. + //! For each Supervariant there's a unique Root ShaderVariantAsset, and possibly an N amount + //! of ShaderVariantAssets. The 'N' amount is the same across all Supervariants because all Supervariants + //! share the same ShaderVariantTreeAsset. + struct Supervariant + { + AZ_TYPE_INFO(Supervariant, "{850826EF-B267-4752-92F6-A85E4175CAB8}"); + static void Reflect(AZ::ReflectContext* context); + + AZ::Name m_name; + ShaderResourceGroupLayoutList m_srgLayoutList; + RHI::Ptr m_pipelineLayoutDescriptor; + ShaderInputContract m_inputContract; + ShaderOutputContract m_outputContract; + RHI::RenderStates m_renderStates; + RHI::ShaderStageAttributeMapList m_attributeMaps; + Data::Asset m_rootShaderVariantAsset; + }; + + //! Container of shader data that is specific to an RHI API. + //! A ShaderAsset2 can contain shader data for multiple RHI APIs if + //! the platform support multiple RHIs. + struct ShaderApiDataContainer + { + AZ_TYPE_INFO(ShaderApiDataContainer, "{C636722C-60B9-421C-ACAD-9750BF634A27}"); + static void Reflect(AZ::ReflectContext* context); + + //! RHI API Type for this shader data. + RHI::APIType m_APIType; + // Index 0, will always be the default Supervariant. (see DefaultSupervariantIndex) + AZStd::vector m_supervariants; + }; + + bool FinalizeAfterLoad(); + void SetReady(); + ShaderApiDataContainer& GetCurrentShaderApiData(); + const ShaderApiDataContainer& GetCurrentShaderApiData() const; + + //! Returning pointers instead of references to allow for error checking + //! and not having to assert. + Supervariant* GetSupervariant(SupervariantIndex supervariantIndex); + const Supervariant* GetSupervariant(SupervariantIndex supervariantIndex) const; + + + //! The name is the stem of the source .shader file. + Name m_name; + + //! Dictates the type of pipeline state generated by this asset (Draw / Dispatch / etc.). + //! All shader variants, across all supervariants, in the asset adhere to this type. + RHI::PipelineStateType m_pipelineStateType = RHI::PipelineStateType::Count; + + //! Defines the layout of the shader options in the asset. + Ptr m_shaderOptionGroupLayout; + + //! List with shader data per RHI backend. + AZStd::vector m_perAPIShaderData; + + Name m_drawListName; + + //! Use to synchronize versions of the ShaderAsset2 and ShaderVariantTreeAsset, especially during hot-reload. + AZStd::sys_time_t m_shaderAssetBuildTimestamp = 0; + + + /////////////////////////////////////////////////////////////////// + //! Do Not Serialize! + + static constexpr size_t InvalidAPITypeIndex = std::numeric_limits::max(); + + //! Index that indicates which ShaderDataContainer to use. + //! At runtime, the asset checks the current active RHI Backend + //! and based on the results this variable gets set on asset load. + //! The vector @m_perAPIShaderData will be indexed with this variable. + size_t m_currentAPITypeIndex = InvalidAPITypeIndex; + + //! We can not know the ShaderVariantTreeAsset by the time this asset is being created. + //! This is a value that is discovered at run time. It becomes valid when FindVariantStableId is called at least once. + Data::Asset m_shaderVariantTree; + + //! Used for thread safety for FindVariantStableId(). + mutable AZStd::shared_mutex m_variantTreeMutex; + + bool m_shaderVariantTreeLoadWasRequested = false; + }; + + class ShaderAssetHandler2 final + : public AssetHandler + { + using Base = AssetHandler; + public: + ShaderAssetHandler2() = default; + + private: + Data::AssetHandler::LoadResult LoadAssetData( + const Data::Asset& asset, + AZStd::shared_ptr stream, + const Data::AssetFilterCB& assetLoadFilterCB) override; + Data::AssetHandler::LoadResult PostLoadInit(const Data::Asset& asset); + }; + + ////////////////////////////////////////////////////////////////////////// + } // namespace RPI + +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAssetCreator2.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAssetCreator2.h new file mode 100644 index 0000000000..263d2e5107 --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAssetCreator2.h @@ -0,0 +1,97 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include +#include + +#include + +namespace AZ +{ + namespace RPI + { + class ShaderAssetCreator2 + : public AssetCreator + { + public: + //! Begins creation of a shader asset. + void Begin(const Data::AssetId& assetId); + + //! [Optional] Set the timestamp for when the ShaderAsset build process began. + //! This is needed to synchronize between the ShaderAsset and ShaderVariantTreeAsset when hot-reloading shaders. + void SetShaderAssetBuildTimestamp(AZStd::sys_time_t shaderAssetBuildTimestamp); + + //! [Optional] Sets the name of the shader asset from content. + void SetName(const Name& name); + + //! [Optional] Sets the DrawListTag name associated with this shader. + void SetDrawListName(const Name& name); + + //! [Required] Assigns the layout used to construct and parse shader options packed into shader variant keys. + //! Requires that the keys assigned to shader variants were constructed using the same layout. + void SetShaderOptionGroupLayout(const Ptr& shaderOptionGroupLayout); + + //! Begins the shader creation for a specific RHI API. + //! Begin must be called before the BeginAPI function is called. + //! @param type The target RHI API type. + void BeginAPI(RHI::APIType type); + + //! Begins the creation of a Supervariant for the current RHI::APIType. + //! If this is the first supervariant its name must be empty. The first + //! supervariant is always the default, nameless, supervariant. + void BeginSupervariant(const Name& name); + + void SetSrgLayoutList(const ShaderResourceGroupLayoutList& srgLayoutList); + + //! [Required] Assigns the pipeline layout descriptor shared by all variants in the shader. Shader variants + //! embedded in a single shader asset are required to use the same pipeline layout. It is not necessary to call + //! Finalize() on the pipeline layout prior to assignment, but still permitted. + void SetPipelineLayout(RHI::Ptr m_pipelineLayoutDescriptor); + + //! Assigns the contract for inputs required by the shader. + void SetInputContract(const ShaderInputContract& contract); + + //! Assigns the contract for outputs required by the shader. + void SetOutputContract(const ShaderOutputContract& contract); + + //! Assigns the render states for the draw pipeline. Ignored for non-draw pipelines. + void SetRenderStates(const RHI::RenderStates& renderStates); + + //! [Optional] Not all shaders have attributes before functions. Some attributes do not exist for all RHI::APIType either. + void SetShaderStageAttributeMapList(const RHI::ShaderStageAttributeMapList& shaderStageAttributeMapList); + + //! [Required] There's always a root variant for each supervariant. + void SetRootShaderVariantAsset(Data::Asset shaderVariantAsset); + + bool EndSupervariant(); + + bool EndAPI(); + + bool End(Data::Asset& shaderAsset); + + //! Clones an existing ShaderAsset. + void Clone(const Data::AssetId& assetId, + const ShaderAsset2& sourceShaderAsset); + + private: + + // Shader variants will use this draw list when they don't specify one. + Name m_defaultDrawList; + + // The current supervariant is cached here to facilitate asset + // construction. Additionally, prevents BeginSupervariant to be called more than once before calling EndSupervariant. + ShaderAsset2::Supervariant* m_currentSupervariant = nullptr; + + }; + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderCommonTypes.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderCommonTypes.h new file mode 100644 index 0000000000..35a12fbf45 --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderCommonTypes.h @@ -0,0 +1,56 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include +#include + +namespace AZ +{ + namespace RPI + { + // Common bit positions for ShaderAsset2 and ShaderVariantAsset2 product SubIds. + static constexpr uint32_t RhiIndexBitPosition = 30; + static constexpr uint32_t RhiIndexNumBits = 32 - RhiIndexBitPosition; + static constexpr uint32_t RhiIndexMaxValue = (1 << RhiIndexNumBits) - 1; + + static constexpr uint32_t SupervariantIndexBitPosition = 22; + static constexpr uint32_t SupervariantIndexNumBits = RhiIndexBitPosition - SupervariantIndexBitPosition; + static constexpr uint32_t SupervariantIndexMaxValue = (1 << SupervariantIndexNumBits) - 1; + + //! A wrapper around a supervariant index for type conformity. + //! A supervariant index is required to find shader data from + //! Shader2 and ShaderAsset2 related APIs. + using SupervariantIndex = RHI::Handle; + static const SupervariantIndex DefaultSupervariantIndex(0); + static const SupervariantIndex InvalidSupervariantIndex; + + enum class ShaderStageType : uint32_t + { + Vertex, + Geometry, + TessellationControl, + TessellationEvaluation, + Fragment, + Compute, + RayTracing + }; + + const char* ToString(ShaderStageType shaderStageType); + + void ReflectShaderStageType(ReflectContext* context); + + } // namespace RPI + + AZ_TYPE_INFO_SPECIALIZE(RPI::ShaderStageType, "{A6408508-748B-4963-B618-E1E6ECA3629A}"); + +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h index 6a1f2af651..22ce16b9f8 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h @@ -43,8 +43,13 @@ namespace AZ static constexpr const char* DisplayName = "ShaderVariant"; static constexpr const char* Group = "Shader"; + static constexpr uint32_t ShaderVariantAssetSubProductType = 0; //! @rhiApiUniqueIndex comes from RHI::Factory::GetAPIUniqueIndex() - static uint32_t GetAssetSubId(uint32_t rhiApiUniqueIndex, ShaderVariantStableId variantStableId); + //! @subProductType is always 0 for a regular ShaderVariantAsset, for all other debug subProducts created + //! by ShaderVariantAssetBuilder this is 1+. + static uint32_t MakeAssetProductSubId( + uint32_t rhiApiUniqueIndex, ShaderVariantStableId variantStableId, + uint32_t subProductType = ShaderVariantAssetSubProductType); ShaderVariantAsset() = default; ~ShaderVariantAsset() = default; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h new file mode 100644 index 0000000000..82e868fda8 --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h @@ -0,0 +1,104 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include + +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + //! A ShaderVariantAsset2 contains the shader byte code for each shader stage (Vertex, Fragment, Tessellation, etc) for a given RHI::APIType (dx12, vulkan, metal, etc). + //! One independent file per RHI::APIType. + class ShaderVariantAsset2 final + : public Data::AssetData + { + friend class ShaderVariantAssetHandler2; + friend class ShaderVariantAssetCreator2; + + public: + AZ_RTTI(ShaderVariantAsset2, "{51BED815-36D8-410E-90F0-1FA9FF765FBA}", Data::AssetData); + + static void Reflect(ReflectContext* context); + + static constexpr const char* Extension = "azshadervariant2"; + static constexpr const char* DisplayName = "ShaderVariant"; + static constexpr const char* Group = "Shader"; + + static constexpr uint32_t ShaderVariantAsset2SubProductType = 1; + //! @rhiApiUniqueIndex comes from RHI::Factory::GetAPIUniqueIndex() + //! @subProductType is always 0 for a regular ShaderVariantAsset2, for all other debug subProducts created + //! by ShaderVariantAssetBuilder2 this is 1+. + static uint32_t MakeAssetProductSubId( + uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, ShaderVariantStableId variantStableId, + uint32_t subProductType = ShaderVariantAsset2SubProductType); + + ShaderVariantAsset2() = default; + ~ShaderVariantAsset2() = default; + + AZ_DISABLE_COPY_MOVE(ShaderVariantAsset2); + + RPI::ShaderVariantStableId GetStableId() const { return m_stableId; } + + const ShaderVariantId& GetShaderVariantId() const { return m_shaderVariantId; } + + //! Returns the shader stage function associated with the provided stage enum value. + const RHI::ShaderStageFunction* GetShaderStageFunction(RHI::ShaderStage shaderStage) const; + + //! Returns whether the variant is fully baked variant (all options are static branches), or false if the + //! variant uses dynamic branches for some shader options. + //! If the shader variant is not fully baked, the ShaderVariantKeyFallbackValue must be correctly set when drawing. + bool IsFullyBaked() const; + + //! Return the timestamp when this asset was built, and it must be >= than the timestamp of the main ShaderAsset. + //! This is used to synchronize versions of the ShaderAsset and ShaderVariantAsset2, especially during hot-reload. + AZStd::sys_time_t GetBuildTimestamp() const; + + bool IsRootVariant() const { return m_stableId == RPI::RootShaderVariantStableId; } + + private: + //! Called by asset creators to assign the asset to a ready state. + void SetReady(); + bool FinalizeAfterLoad(); + + //! See AZ::RPI::ShaderVariantListSourceData::VariantInfo::m_stableId for details. + RPI::ShaderVariantStableId m_stableId; + + ShaderVariantId m_shaderVariantId; + + bool m_isFullyBaked = false; + + AZStd::array, RHI::ShaderStageCount> m_functionsByStage; + + //! Used to synchronize versions of the ShaderAsset and ShaderVariantAsset2, especially during hot-reload. + AZStd::sys_time_t m_buildTimestamp = 0; + }; + + class ShaderVariantAssetHandler2 final + : public AssetHandler + { + using Base = AssetHandler; + public: + ShaderVariantAssetHandler2() = default; + + private: + LoadResult LoadAssetData(const Data::Asset& asset, AZStd::shared_ptr stream, const AZ::Data::AssetFilterCB& assetLoadFilterCB) override; + bool PostLoadInit(const Data::Asset& asset); + }; + + } // namespace RPI + +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/BuilderComponent.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/BuilderComponent.cpp index e3386ebb5c..70752bf02b 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/BuilderComponent.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/BuilderComponent.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,7 @@ #include #include #include +#include #include #include @@ -88,6 +90,7 @@ namespace AZ m_assetWorkers.emplace_back(MakeAssetBuilder()); m_assetHandlers.emplace_back(MakeAssetHandler()); + m_assetHandlers.emplace_back(MakeAssetHandler()); m_assetHandlers.emplace_back(MakeAssetHandler()); m_assetHandlers.emplace_back(MakeAssetHandler()); m_assetHandlers.emplace_back(MakeAssetHandler()); @@ -98,6 +101,7 @@ namespace AZ m_assetHandlers.emplace_back(MakeAssetHandler()); m_assetHandlers.emplace_back(MakeAssetHandler()); m_assetHandlers.emplace_back(MakeAssetHandler()); + m_assetHandlers.emplace_back(MakeAssetHandler()); m_assetHandlers.emplace_back(MakeAssetHandler()); m_assetHandlers.emplace_back(MakeAssetHandler()); m_assetHandlers.emplace_back(MakeAssetHandler()); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.cpp index 7aace50760..3d0cbca8e6 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.cpp @@ -18,6 +18,9 @@ #include #include +#include +#include + namespace AZ::RPI { using namespace AZ::SceneAPI; @@ -114,7 +117,7 @@ namespace AZ::RPI meshNodeName, sourceMesh.m_name.GetCStr()); const DataTypes::MatrixType globalTransform = Utilities::BuildWorldTransform(sceneGraph, sceneNodeIndex); - BuildMorphTargetMesh(vertexOffset, sourceMesh, productMesh, metaAssetCreator, blendShapeName, blendShapeData, globalTransform, coordSysConverter); + BuildMorphTargetMesh(vertexOffset, sourceMesh, productMesh, metaAssetCreator, blendShapeName, blendShapeData, globalTransform, coordSysConverter, scene.GetSourceFilename()); } } } @@ -157,7 +160,8 @@ namespace AZ::RPI const AZStd::string& blendShapeName, const AZStd::shared_ptr& blendShapeData, const DataTypes::MatrixType& globalTransform, - const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter) + const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter, + const AZStd::string& sourceSceneFilename) { const float tolerance = CalcPositionDeltaTolerance(sourceMesh); AZ::Aabb deltaPositionAabb = AZ::Aabb::CreateNull(); @@ -288,6 +292,8 @@ namespace AZ::RPI metaData.m_maxPositionDelta = maxValue; } + metaData.m_wrinkleMask = GetWrinkleMask(sourceSceneFilename, blendShapeName); + metaAssetCreator.AddMorphTarget(metaData); AZ_Assert(uncompressedPositionDeltas.size() == compressedDeltas.size(), "Number of uncompressed (%d) and compressed position delta components (%d) do not match.", @@ -312,4 +318,47 @@ namespace AZ::RPI AZ_Assert((packedCompressedMorphTargetVertexData.size() - metaData.m_startIndex) == numMorphedVertices, "Vertex index range (%d) in morph target meta data does not match number of morphed vertices (%d).", packedCompressedMorphTargetVertexData.size() - metaData.m_startIndex, numMorphedVertices); } + + Data::Asset MorphTargetExporter::GetWrinkleMask(const AZStd::string& sourceSceneFullFilePath, const AZStd::string& blendShapeName) const + { + AZ::Data::Asset imageAsset; + + // See if there is a wrinkle map mask for this mesh + AZStd::string sceneRelativeFilePath; + bool relativePathFound = true; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(relativePathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetRelativeProductPathFromFullSourceOrProductPath, sourceSceneFullFilePath, sceneRelativeFilePath); + + if (relativePathFound) + { + AZ::StringFunc::Path::StripFullName(sceneRelativeFilePath); + + // Get the folder the masks are supposed to be in + AZStd::string folderName; + AZ::StringFunc::Path::GetFileName(sourceSceneFullFilePath.c_str(), folderName); + folderName += "_wrinklemasks"; + + // Note: for now, we're assuming the mask is always authored as a .tif + AZStd::string blendMaskFileName = blendShapeName + "_wrinklemask.tif.streamingimage"; + + AZStd::string maskFolderAndFile; + AZ::StringFunc::Path::Join(folderName.c_str(), blendMaskFileName.c_str(), maskFolderAndFile); + + AZStd::string maskRelativePath; + AZ::StringFunc::Path::Join(sceneRelativeFilePath.c_str(), maskFolderAndFile.c_str(), maskRelativePath); + AZ::StringFunc::Path::Normalize(maskRelativePath); + + // Now see if the file exists + AZ::Data::AssetId maskAssetId; + Data::AssetCatalogRequestBus::BroadcastResult(maskAssetId, &Data::AssetCatalogRequests::GetAssetIdByPath, maskRelativePath.c_str(), AZ::Data::s_invalidAssetType, false); + + if (maskAssetId.IsValid()) + { + // Flush asset manager events to ensure no asset references are held by closures queued on Ebuses. + AZ::Data::AssetManager::Instance().DispatchEvents(); + + imageAsset.Create(maskAssetId, AZ::Data::AssetLoadBehavior::PreLoad, false); + } + } + return imageAsset; + } } // namespace AZ::RPI diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.h b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.h index d968a803d6..4845d7d1da 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.h +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.h @@ -64,7 +64,11 @@ namespace AZ const AZStd::string& blendShapeName, const AZStd::shared_ptr& blendShapeData, const AZ::SceneAPI::DataTypes::MatrixType& globalTransform, - const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter); + const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter, + const AZStd::string& sourceSceneFilename); + + // Find a wrinkle mask for this morph target, if it exists + Data::Asset GetWrinkleMask(const AZStd::string& sourceSceneFullFilePath, const AZStd::string& blendShapeName) const; }; } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index 2ab6ee92e9..109af70166 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -422,7 +422,7 @@ namespace AZ const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAssetCreator.GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyIndex); AZ::Name enumName = AZ::Name(property.m_value.GetValue()); - uint32_t enumValue = propertyDescriptor->GetEnumValue(enumName); + uint32_t enumValue = propertyDescriptor ? propertyDescriptor->GetEnumValue(enumName) : MaterialPropertyDescriptor::InvalidEnumValue; if (enumValue == MaterialPropertyDescriptor::InvalidEnumValue) { materialTypeAssetCreator.ReportError("Enum value '%s' couldn't be found in the 'enumValues' list", enumName.GetCStr()); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderSourceData.cpp index e67480e6f9..aac81a6e26 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderSourceData.cpp @@ -11,19 +11,19 @@ */ #include +#include +#include namespace AZ { namespace RPI { - const char* ShaderSourceData::Extension = "shader"; - void ShaderSourceData::Reflect(ReflectContext* context) { if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(3) + ->Version(4) ->Field("Source", &ShaderSourceData::m_source) ->Field("DrawList", &ShaderSourceData::m_drawListName) ->Field("DepthStencilState", &ShaderSourceData::m_depthStencilState) @@ -31,8 +31,8 @@ namespace AZ ->Field("BlendState", &ShaderSourceData::m_blendState) ->Field("ProgramSettings", &ShaderSourceData::m_programSettings) ->Field("CompilerHints", &ShaderSourceData::m_compiler) - ->Field("ShaderVariantHints", &ShaderSourceData::m_shaderOptionGroupHints) ->Field("DisabledRHIBackends", &ShaderSourceData::m_disabledRhiBackends) + ->Field("Supervariants", &ShaderSourceData::m_supervariants) ; serializeContext->Class() @@ -45,15 +45,145 @@ namespace AZ ->Field("Name", &EntryPoint::m_name) ->Field("Type", &EntryPoint::m_type) ; + + serializeContext->Class() + ->Version(1) + ->Field("Name", &SupervariantInfo::m_name) + ->Field("PlusArguments", &SupervariantInfo::m_plusArguments) + ->Field("MinusArguments", &SupervariantInfo::m_minusArguments); + } } - bool ShaderSourceData::IsRhiBackendDisabled(const AZ::Name& rhiName) + bool ShaderSourceData::IsRhiBackendDisabled(const AZ::Name& rhiName) const { return AZStd::any_of(m_disabledRhiBackends.begin(), m_disabledRhiBackends.end(), [&](const AZStd::string& currentRhiName) { return currentRhiName == rhiName.GetStringView(); }); } + + + //! Helper function. + //! Parses a string of command line arguments looking for c-preprocessor macro definitions and appends the name of macro definition arguments. + //! Example: + //! Input string: "--switch1 -DMACRO1 -v -DMACRO2=23" + //! append the following items: ["MACRO1", "MACRO2"] + static void GetListOfMacroDefinitionNames( + const AZStd::string& stringWithArguments, AZStd::vector& macroDefinitionNames) + { + static const AZStd::regex macroRegex("-D\\s*(\\w+)", AZStd::regex::ECMAScript); + + AZStd::cmatch match; + if (AZStd::regex_search(stringWithArguments.c_str(), match, macroRegex)) + { + // First pattern is always the entire string + for (unsigned i = 1; i < match.size(); ++i) + { + if (match[i].matched) + { + macroDefinitionNames.push_back(match[i].str().c_str()); + } + } + } + } + + AZStd::vector ShaderSourceData::SupervariantInfo::GetCombinedListOfMacroDefinitionNamesToRemove() const + { + AZStd::vector macroDefinitionNames; + GetListOfMacroDefinitionNames(m_minusArguments, macroDefinitionNames); + GetListOfMacroDefinitionNames(m_plusArguments, macroDefinitionNames); + return macroDefinitionNames; + } + + + //! Helper function. + //! Parses a string of command line arguments looking for c-preprocessor macro definitions and appends macro definition + //! arguments. Example: Input string: "--switch1 -DMACRO1 -v -DMACRO2=23" append the following items: ["MACRO1", "MACRO2=23"] + static void GetListOfMacroDefinitions( + const AZStd::string& stringWithArguments, AZStd::vector& macroDefinitions) + { + static const AZStd::regex macroRegex("-D\\s*(\\w+(=\\w+)?)", AZStd::regex::ECMAScript); + + AZStd::cmatch match; + if (AZStd::regex_search(stringWithArguments.c_str(), match, macroRegex)) + { + // First pattern is always the entire string + for (unsigned i = 1; i < match.size(); ++i) + { + if (match[i].matched) + { + macroDefinitions.push_back(match[i].str().c_str()); + } + } + } + } + + AZStd::vector ShaderSourceData::SupervariantInfo::GetMacroDefinitionsToAdd() const + { + AZStd::vector parsedMacroDefinitions; + GetListOfMacroDefinitions(m_plusArguments, parsedMacroDefinitions); + return parsedMacroDefinitions; + } + + + // Helper. + // @arguments: A string with command line arguments for a console application of the form: + // "- -- --[=] ..." + // Example: "--use-spaces --namespace=vk" + // Returns: A list with just the [-|--]: + // ["-", "--", "--arg3"] + // For the example shown above it will return this vector: + // ["--use-spaces", "--namespace"] + AZStd::vector GetListOfArgumentNames(const AZStd::string& arguments) + { + AZStd::vector listOfTokens; + AzFramework::StringFunc::Tokenize(arguments, listOfTokens); + AZStd::vector listOfArguments; + for (const AZStd::string& token : listOfTokens) + { + AZStd::vector splitArguments; + AzFramework::StringFunc::Tokenize(token, splitArguments, "="); + listOfArguments.push_back(splitArguments[0]); + } + return listOfArguments; + } + + AZStd::string ShaderSourceData::SupervariantInfo::GetCustomizedArgumentsForAzslc( + const AZStd::string& initialAzslcCompilerArguments) const + { + static const AZStd::regex macroRegex("-D\\s*(\\w+(=\\S+)?)", AZStd::regex::ECMAScript); + + // We are only concerned with AZSLc arguments. Let's remove the C-Preprocessor macro definitions + // from @minusArguments. + const AZStd::string minusArguments = AZStd::regex_replace(m_minusArguments, macroRegex, ""); + const AZStd::string plusArguments = AZStd::regex_replace(m_plusArguments, macroRegex, ""); + AZStd::string azslcArgumentsToRemove = minusArguments + " " + plusArguments; + AZStd::vector azslcArgumentNamesToRemove = GetListOfArgumentNames(azslcArgumentsToRemove); + + // At this moment @azslcArgumentsToRemove contains arguments for AZSLc that can be of the form: + // - + // --[=] + // We need to remove those from @initialAzslcCompilerArguments. + AZStd::string customizedArguments = initialAzslcCompilerArguments; + for (const AZStd::string& azslcArgumentName : azslcArgumentNamesToRemove) + { + AZStd::string regexStr = AZStd::string::format("%s(=\\S+)?", azslcArgumentName.c_str()); + AZStd::regex replaceRegex(regexStr, AZStd::regex::ECMAScript); + customizedArguments = AZStd::regex_replace(customizedArguments, replaceRegex, ""); + } + + customizedArguments += " " + plusArguments; + + // Will contain the results that will be joined by a space. + // This is used to get a clean string to return without excess spaces. + AZStd::vector argumentList; + AzFramework::StringFunc::Tokenize(customizedArguments, argumentList, " \t\n"); + customizedArguments.clear(); // Need to clear because Join appends. + AzFramework::StringFunc::Join(customizedArguments, argumentList.begin(), argumentList.end(), " "); + return customizedArguments; + } + + } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderVariantAssetCreator2.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderVariantAssetCreator2.cpp new file mode 100644 index 0000000000..2936ed50f9 --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderVariantAssetCreator2.cpp @@ -0,0 +1,112 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#include + +#include + +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + void ShaderVariantAssetCreator2::Begin(const AZ::Data::AssetId& assetId, const ShaderVariantId& shaderVariantId, RPI::ShaderVariantStableId stableId, bool isFullyBaked) + { + BeginCommon(assetId); + + if (ValidateIsReady()) + { + m_asset->m_stableId = stableId; + m_asset->m_shaderVariantId = shaderVariantId; + m_asset->m_isFullyBaked = isFullyBaked; + } + } + + bool ShaderVariantAssetCreator2::End(Data::Asset& result) + { + if (!ValidateIsReady()) + { + return false; + } + + if (!m_asset->FinalizeAfterLoad()) + { + ReportError("Failed to finalize the ShaderResourceGroupAsset."); + return false; + } + + bool foundDrawFunctions = false; + bool foundDispatchFunctions = false; + + if (m_asset->GetShaderStageFunction(RHI::ShaderStage::Vertex) || + m_asset->GetShaderStageFunction(RHI::ShaderStage::Tessellation) || + m_asset->GetShaderStageFunction(RHI::ShaderStage::Fragment)) + { + foundDrawFunctions = true; + } + + if (m_asset->GetShaderStageFunction(RHI::ShaderStage::Compute)) + { + foundDispatchFunctions = true; + } + + + if (foundDrawFunctions && foundDispatchFunctions) + { + ReportError("ShaderVariant contains both Draw functions and Dispatch functions."); + return false; + } + + if (m_asset->GetShaderStageFunction(RHI::ShaderStage::Fragment) && + !m_asset->GetShaderStageFunction(RHI::ShaderStage::Vertex)) + { + ReportError("Shader Variant with StableId '%u' has a fragment function but no vertex function.", m_asset->m_stableId); + return false; + } + + if (m_asset->GetShaderStageFunction(RHI::ShaderStage::Tessellation) && + !m_asset->GetShaderStageFunction(RHI::ShaderStage::Vertex)) + { + ReportError("Shader Variant with StableId '%u' has a tessellation function but no vertex function.", m_asset->m_stableId); + return false; + } + + + + m_asset->SetReady(); + return EndCommon(result); + } + + + ///////////////////////////////////////////////////////////////////// + // Methods for all shader variant types + + void ShaderVariantAssetCreator2::SetBuildTimestamp(AZStd::sys_time_t buildTimestamp) + { + if (ValidateIsReady()) + { + m_asset->m_buildTimestamp = buildTimestamp; + } + } + + void ShaderVariantAssetCreator2::SetShaderFunction(RHI::ShaderStage shaderStage, RHI::Ptr shaderStageFunction) + { + if (ValidateIsReady()) + { + m_asset->m_functionsByStage[static_cast(shaderStage)] = shaderStageFunction; + } + } + + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp index 1fad385fa6..3f5c16678c 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp @@ -163,6 +163,11 @@ namespace AZ binding.m_shaderInputIndex = idx.IsValid() ? static_cast(idx.GetIndex()) : PassAttachmentBinding::ShaderInputNoBind; } } + else + { + AZ_Error("Pass System", false, "[Pass %s] Could not bind shader buffer index '%s' because it has no attachment.", GetName().GetCStr(), shaderName.GetCStr()); + binding.m_shaderInputIndex = PassAttachmentBinding::ShaderInputNoBind; + } } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader2.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader2.cpp new file mode 100644 index 0000000000..f56a18e2b9 --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader2.cpp @@ -0,0 +1,413 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#include + +#include + +#include + +#include +#include + +#include + +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + Data::Instance Shader2::FindOrCreate(const Data::Asset& shaderAsset, const Name& supervariantName) + { + Data::Instance shaderInstance = Data::InstanceDatabase::Instance().FindOrCreate( + Data::InstanceId::CreateFromAssetId(shaderAsset.GetId()), + shaderAsset); + if (!shaderInstance) + { + return nullptr; + } + + if (!shaderInstance->SelectSupervariant(supervariantName)) + { + return nullptr; + } + + const RHI::ResultCode resultCode = shaderInstance->Init(*shaderAsset.Get()); + if (resultCode != RHI::ResultCode::Success) + { + return nullptr; + } + return shaderInstance; + } + + Data::Instance Shader2::CreateInternal([[maybe_unused]] ShaderAsset2& shaderAsset) + { + Data::Instance shader = aznew Shader2(); + return shader; + } + + Shader2::~Shader2() + { + Shutdown(); + } + + bool Shader2::SelectSupervariant(const Name& supervariantName) + { + if (supervariantName.IsEmpty()) + { + m_supervariantIndex = DefaultSupervariantIndex; + return true; + } + + auto supervariantIndex = m_asset->GetSupervariantIndex(supervariantName); + if (supervariantIndex == InvalidSupervariantIndex) + { + return false; + } + + m_supervariantIndex = supervariantIndex; + return true; + } + + RHI::ResultCode Shader2::Init(ShaderAsset2& shaderAsset) + { + AZ_Assert(m_supervariantIndex != InvalidSupervariantIndex, "Invalid supervariant index"); + + ShaderVariantFinderNotificationBus2::Handler::BusDisconnect(); + ShaderVariantFinderNotificationBus2::Handler::BusConnect(shaderAsset.GetId()); + + RHI::RHISystemInterface* rhiSystem = RHI::RHISystemInterface::Get(); + RHI::DrawListTagRegistry* drawListTagRegistry = rhiSystem->GetDrawListTagRegistry(); + + m_asset = { &shaderAsset, AZ::Data::AssetLoadBehavior::PreLoad }; + m_pipelineStateType = shaderAsset.GetPipelineStateType(); + + { + AZStd::unique_lock lock(m_variantCacheMutex); + m_shaderVariants.clear(); + } + m_rootVariant.Init(shaderAsset, shaderAsset.GetRootVariant(m_supervariantIndex), m_supervariantIndex); + + if (m_pipelineLibraryHandle.IsNull()) + { + // We set up a pipeline library only once for the lifetime of the Shader2 instance. + // This should allow the Shader2 to be reloaded at runtime many times, and cache and reuse PipelineState objects rather than rebuild them. + // It also fixes a particular TDR crash that occurred on some hardware when hot-reloading shaders and building pipeline states + // in a new pipeline library every time. + + RHI::PipelineStateCache* pipelineStateCache = rhiSystem->GetPipelineStateCache(); + ConstPtr serializedData = LoadPipelineLibrary(); + RHI::PipelineLibraryHandle pipelineLibraryHandle = pipelineStateCache->CreateLibrary(serializedData.get()); + + if (pipelineLibraryHandle.IsNull()) + { + AZ_Error("Shader2", false, "Failed to create pipeline library from pipeline state cache."); + return RHI::ResultCode::Fail; + } + + m_pipelineLibraryHandle = pipelineLibraryHandle; + m_pipelineStateCache = pipelineStateCache; + } + + const Name& drawListName = shaderAsset.GetDrawListName(); + if (!drawListName.IsEmpty()) + { + m_drawListTag = drawListTagRegistry->AcquireTag(drawListName); + if (!m_drawListTag.IsValid()) + { + AZ_Error("Shader2", false, "Failed to acquire a DrawListTag. Entries are full."); + } + } + + Data::AssetBus::Handler::BusConnect(m_asset.GetId()); + + return RHI::ResultCode::Success; + } + + void Shader2::Shutdown() + { + ShaderVariantFinderNotificationBus2::Handler::BusDisconnect(); + Data::AssetBus::Handler::BusDisconnect(); + + if (m_pipelineLibraryHandle.IsValid()) + { + SavePipelineLibrary(); + + m_pipelineStateCache->ReleaseLibrary(m_pipelineLibraryHandle); + m_pipelineStateCache = nullptr; + m_pipelineLibraryHandle = {}; + } + + if (m_drawListTag.IsValid()) + { + RHI::DrawListTagRegistry* drawListTagRegistry = RHI::RHISystemInterface::Get()->GetDrawListTagRegistry(); + drawListTagRegistry->ReleaseTag(m_drawListTag); + m_drawListTag.Reset(); + } + } + + /////////////////////////////////////////////////////////////////////// + // AssetBus overrides + void Shader2::OnAssetReloaded(Data::Asset asset) + { + ShaderReloadDebugTracker::ScopedSection reloadSection("Shader2::OnAssetReloaded %s", asset.GetHint().c_str()); + + if (asset->GetId() == m_asset->GetId()) + { + Data::Asset newAsset = { asset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; + AZ_Assert(newAsset, "Reloaded ShaderAsset2 is null"); + + Data::AssetBus::Handler::BusDisconnect(); + Init(*newAsset.Get()); + ShaderReloadNotificationBus2::Event(asset.GetId(), &ShaderReloadNotificationBus2::Events::OnShaderReinitialized, *this); + } + } + /////////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + /// ShaderVariantFinderNotificationBus2 overrides + void Shader2::OnShaderVariantAssetReady(Data::Asset shaderVariantAsset, bool isError) + { + AZ_Assert(shaderVariantAsset, "Reloaded ShaderVariantAsset is null"); + const ShaderVariantStableId stableId = shaderVariantAsset->GetStableId(); + const ShaderVariantId& shaderVariantId = shaderVariantAsset->GetShaderVariantId(); + + if (isError) + { + //Remark: We do not assert if the stableId == RootShaderVariantStableId, because we can not trust in the asset data + //on error. so it is possible that on error the stbleId == RootShaderVariantStableId; + if (stableId == RootShaderVariantStableId) + { + return; + } + AZStd::unique_lock lock(m_variantCacheMutex); + m_shaderVariants.erase(stableId); + } + else + { + AZ_Assert(stableId != RootShaderVariantStableId, + "The root variant is expected to be updated by the ShaderAsset2."); + AZStd::unique_lock lock(m_variantCacheMutex); + + auto iter = m_shaderVariants.find(stableId); + if (iter != m_shaderVariants.end()) + { + ShaderVariant2& shaderVariant = iter->second; + + if (!shaderVariant.Init(*m_asset.Get(), shaderVariantAsset, m_supervariantIndex)) + { + AZ_Error("Shader2", false, "Failed to init shaderVariant with StableId=%u", shaderVariantAsset->GetStableId()); + m_shaderVariants.erase(stableId); + } + } + else + { + //This is the first time the shader variant asset comes to life. + ShaderVariant2 newVariant; + newVariant.Init(*m_asset, shaderVariantAsset, m_supervariantIndex); + m_shaderVariants.emplace(stableId, newVariant); + } + } + + //Even if there was an error, the interested parties should be notified. + ShaderReloadNotificationBus2::Event(m_asset.GetId(), &ShaderReloadNotificationBus2::Events::OnShaderVariantReinitialized, *this, shaderVariantId, stableId); + } + /////////////////////////////////////////////////////////////////// + + ConstPtr Shader2::LoadPipelineLibrary() const + { + if (IO::FileIOBase::GetInstance()) + { + return Utils::LoadObjectFromFile(GetPipelineLibraryPath()); + } + return nullptr; + } + + void Shader2::SavePipelineLibrary() const + { + if (auto* fileIOBase = IO::FileIOBase::GetInstance()) + { + RHI::ConstPtr serializedData = m_pipelineStateCache->GetLibrarySerializedData(m_pipelineLibraryHandle); + if (serializedData) + { + const AZStd::string pipelineLibraryPath = GetPipelineLibraryPath(); + + char pipelineLibraryPathResolved[AZ_MAX_PATH_LEN] = { 0 }; + fileIOBase->ResolvePath(pipelineLibraryPath.c_str(), pipelineLibraryPathResolved, AZ_MAX_PATH_LEN); + Utils::SaveObjectToFile(pipelineLibraryPathResolved, DataStream::ST_BINARY, serializedData.get()); + } + } + else + { + AZ_Error("Shader2", false, "FileIOBase is not initialized"); + } + } + + AZStd::string Shader2::GetPipelineLibraryPath() const + { + const Data::InstanceId& instanceId = GetId(); + Name platformName = RHI::Factory::Get().GetName(); + Name shaderName = m_asset->GetName(); + + AZStd::string uuidString; + instanceId.m_guid.ToString(uuidString, false, false); + + return AZStd::string::format("@user@/Atom/PipelineStateCache/%s/%s_%s_%d.bin", platformName.GetCStr(), shaderName.GetCStr(), uuidString.data(), instanceId.m_subId); + } + + ShaderOptionGroup Shader2::CreateShaderOptionGroup() const + { + return ShaderOptionGroup(m_asset->GetShaderOptionGroupLayout()); + } + + const ShaderVariant2& Shader2::GetVariant(const ShaderVariantId& shaderVariantId) + { + AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); + Data::Asset shaderVariantAsset = m_asset->GetVariant(shaderVariantId, m_supervariantIndex); + if (!shaderVariantAsset || shaderVariantAsset->IsRootVariant()) + { + return m_rootVariant; + } + + return GetVariant(shaderVariantAsset->GetStableId()); + } + + const ShaderVariant2& Shader2::GetRootVariant() + { + return m_rootVariant; + } + + ShaderVariantSearchResult Shader2::FindVariantStableId(const ShaderVariantId& shaderVariantId) const + { + AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); + ShaderVariantSearchResult variantSearchResult = m_asset->FindVariantStableId(shaderVariantId); + return variantSearchResult; + } + + const ShaderVariant2& Shader2::GetVariant(ShaderVariantStableId shaderVariantStableId) + { + AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); + + if (!shaderVariantStableId.IsValid() || shaderVariantStableId == ShaderAsset2::RootShaderVariantStableId) + { + return m_rootVariant; + } + + { + AZStd::shared_lock lock(m_variantCacheMutex); + + auto findIt = m_shaderVariants.find(shaderVariantStableId); + if (findIt != m_shaderVariants.end()) + { + // When rebuilding shaders we may be in a state where the ShaderAsset2 and root ShaderVariantAsset have been rebuilt and + // reloaded, but some (or all) shader variants haven't been built yet. Since we want to use the latest version of the + // shader code, ignore the old variants and fall back to the newer root variant instead. There's no need to report a + // warning here because m_asset->GetVariant below will report one. + if (findIt->second.GetBuildTimestamp() >= m_asset->GetShaderAssetBuildTimestamp()) + { + return findIt->second; + } + } + } + + // By calling GetVariant, an asynchronous asset load request is enqueued if the variant + // is not fully ready. + Data::Asset shaderVariantAsset = m_asset->GetVariant(shaderVariantStableId, m_supervariantIndex); + if (!shaderVariantAsset || shaderVariantAsset == m_asset->GetRootVariant()) + { + // Return the root variant when the requested variant is not ready. + return m_rootVariant; + } + + AZStd::unique_lock lock(m_variantCacheMutex); + + // For performance reasons We are breaking this function into two locking steps. + // which means We must check again if the variant is already in the cache. + auto findIt = m_shaderVariants.find(shaderVariantStableId); + if (findIt != m_shaderVariants.end()) + { + if (findIt->second.GetBuildTimestamp() >= m_asset->GetShaderAssetBuildTimestamp()) + { + return findIt->second; + } + else + { + // This is probably very rare, but if the variant was loaded on another thread and it's out of date + // we just return the root variant. Otherwise we could end up replacing the variant in the map below while + // it's being used for rendering. + AZ_Warning( + "Shader2", false, + "Detected an uncommon state during shader reload. Returning the root variant instead of replacing the old one."); + return m_rootVariant; + } + } + + ShaderVariant2 newVariant; + newVariant.Init(*m_asset, shaderVariantAsset, m_supervariantIndex); + m_shaderVariants.emplace(shaderVariantStableId, newVariant); + + return m_shaderVariants.at(shaderVariantStableId); + } + + RHI::PipelineStateType Shader2::GetPipelineStateType() const + { + return m_pipelineStateType; + } + + const ShaderInputContract& Shader2::GetInputContract() const + { + return m_asset->GetInputContract(m_supervariantIndex); + } + + const ShaderOutputContract& Shader2::GetOutputContract() const + { + return m_asset->GetOutputContract(m_supervariantIndex); + } + + const RHI::PipelineState* Shader2::AcquirePipelineState(const RHI::PipelineStateDescriptor& descriptor) const + { + return m_pipelineStateCache->AcquirePipelineState(m_pipelineLibraryHandle, descriptor); + } + + const RHI::Ptr Shader2::FindShaderResourceGroupLayout(const Name& shaderResourceGroupName) const + { + return m_asset->FindShaderResourceGroupLayout(shaderResourceGroupName, m_supervariantIndex); + } + + const RHI::Ptr Shader2::FindShaderResourceGroupLayout(uint32_t bindingSlot) const + { + return m_asset->FindShaderResourceGroupLayout(bindingSlot, m_supervariantIndex); + } + + const RHI::Ptr Shader2::FindFallbackShaderResourceGroupLayout() const + { + return m_asset->FindFallbackShaderResourceGroupLayout(m_supervariantIndex); + } + + AZStd::array_view> Shader2::GetShaderResourceGroupLayouts() const + { + return m_asset->GetShaderResourceGroupLayouts(m_supervariantIndex); + } + + const Data::Asset& Shader2::GetAsset() const + { + return m_asset; + } + + RHI::DrawListTag Shader2::GetDrawListTag() const + { + return m_drawListTag; + } + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderResourceGroup.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderResourceGroup.cpp index 77ea0b9494..86cb3cd0a4 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderResourceGroup.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderResourceGroup.cpp @@ -88,6 +88,41 @@ namespace AZ return RHI::ResultCode::Success; } + bool ShaderResourceGroup::ReplaceSrgLayoutUsingShaderAsset( + Data::Asset shaderAsset, const Name& supervariantName, const Name& srgName) + { + AZ_TRACE_METHOD(); + + SupervariantIndex supervariantIndex = shaderAsset->GetSupervariantIndex(supervariantName); + if (supervariantIndex == InvalidSupervariantIndex) + { + AZ_Assert( + false, "Supervariant with name [%s] not found in shader asset [%s]", supervariantName.GetCStr(), + shaderAsset->GetName().GetCStr()); + return false; + } + + m_layout = shaderAsset->FindShaderResourceGroupLayout(srgName, supervariantIndex).get(); + + if (!m_layout) + { + AZ_Assert(false, "ShaderResourceGroup cannot be initialized due to invalid ShaderResourceGroupLayout"); + return false; + } + + m_shaderResourceGroup->SetName(m_layout->GetName()); + m_data = RHI::ShaderResourceGroupData(m_layout); + m_shaderAsset = shaderAsset; + + // The RPI groups match the same dimensions as the RHI group. + m_imageGroup.clear(); + m_imageGroup.resize(m_layout->GetGroupSizeForImages()); + m_bufferGroup.clear(); + m_bufferGroup.resize(m_layout->GetGroupSizeForBuffers()); + + return true; + } + void ShaderResourceGroup::Compile() { m_shaderResourceGroup->Compile(m_data); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderSystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderSystem.cpp index 1527e81744..b5125ba964 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderSystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderSystem.cpp @@ -12,15 +12,18 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include +#include #include #include @@ -42,9 +45,11 @@ namespace AZ ShaderVariantId::Reflect(context); ShaderVariantStableId::Reflect(context); ShaderAsset::Reflect(context); + ShaderAsset2::Reflect(context); ShaderInputContract::Reflect(context); ShaderOutputContract::Reflect(context); ShaderVariantAsset::Reflect(context); + ShaderVariantAsset2::Reflect(context); ShaderVariantTreeAsset::Reflect(context); ReflectShaderStageType(context); PrecompiledShaderAssetSourceData::Reflect(context); @@ -58,8 +63,10 @@ namespace AZ void ShaderSystem::GetAssetHandlers(AssetHandlerPtrList& assetHandlers) { assetHandlers.emplace_back(MakeAssetHandler()); + assetHandlers.emplace_back(MakeAssetHandler()); assetHandlers.emplace_back(MakeAssetHandler()); assetHandlers.emplace_back(MakeAssetHandler()); + assetHandlers.emplace_back(MakeAssetHandler()); assetHandlers.emplace_back(MakeAssetHandler()); } @@ -78,6 +85,14 @@ namespace AZ Data::InstanceDatabase::Create(azrtti_typeid(), handler); } + { + Data::InstanceHandler handler; + handler.m_createFunction = [](Data::AssetData* shaderAsset) { + return Shader2::CreateInternal(*(azrtti_cast(shaderAsset))); + }; + Data::InstanceDatabase::Create(azrtti_typeid(), handler); + } + { Data::InstanceHandler handler; handler.m_createFunction = [](Data::AssetData* srgAsset) @@ -100,6 +115,7 @@ namespace AZ void ShaderSystem::Shutdown() { Data::InstanceDatabase::Destroy(); + Data::InstanceDatabase::Destroy(); Data::InstanceDatabase::Destroy(); Data::InstanceDatabase::Destroy(); Interface::Unregister(this); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariant2.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariant2.cpp new file mode 100644 index 0000000000..d25b87fab3 --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariant2.cpp @@ -0,0 +1,76 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#include + +#include +#include + +#include + +namespace AZ +{ + namespace RPI + { + bool ShaderVariant2::Init( + const ShaderAsset2& shaderAsset, + Data::Asset shaderVariantAsset, + SupervariantIndex supervariantIndex) + { + m_pipelineStateType = shaderAsset.GetPipelineStateType(); + m_pipelineLayoutDescriptor = shaderAsset.GetPipelineLayoutDescriptor(supervariantIndex); + m_shaderVariantAsset = shaderVariantAsset; + m_renderStates = &shaderAsset.GetRenderStates(supervariantIndex); + return true; + } + + void ShaderVariant2::ConfigurePipelineState(RHI::PipelineStateDescriptor& descriptor) const + { + descriptor.m_pipelineLayoutDescriptor = m_pipelineLayoutDescriptor; + + switch (descriptor.GetType()) + { + case RHI::PipelineStateType::Draw: + { + AZ_Assert(m_pipelineStateType == RHI::PipelineStateType::Draw, "ShaderVariant2 is not intended for the raster pipeline."); + AZ_Assert(m_renderStates, "Invalid RenderStates"); + RHI::PipelineStateDescriptorForDraw& descriptorForDraw = static_cast(descriptor); + descriptorForDraw.m_vertexFunction = m_shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Vertex); + descriptorForDraw.m_tessellationFunction = m_shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Tessellation); + descriptorForDraw.m_fragmentFunction = m_shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Fragment); + descriptorForDraw.m_renderStates = *m_renderStates; + break; + } + + case RHI::PipelineStateType::Dispatch: + { + AZ_Assert(m_pipelineStateType == RHI::PipelineStateType::Dispatch, "ShaderVariant2 is not intended for the compute pipeline."); + RHI::PipelineStateDescriptorForDispatch& descriptorForDispatch = static_cast(descriptor); + descriptorForDispatch.m_computeFunction = m_shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Compute); + break; + } + + case RHI::PipelineStateType::RayTracing: + { + AZ_Assert(m_pipelineStateType == RHI::PipelineStateType::RayTracing, "ShaderVariant2 is not intended for the ray tracing pipeline."); + RHI::PipelineStateDescriptorForRayTracing& descriptorForRayTracing = static_cast(descriptor); + descriptorForRayTracing.m_rayTracingFunction = m_shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::RayTracing); + break; + } + + default: + AZ_Assert(false, "Unexpected PipelineStateType"); + break; + } + } + + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariantAsyncLoader.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariantAsyncLoader.cpp index 53bad2f05c..6579e6a98d 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariantAsyncLoader.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariantAsyncLoader.cpp @@ -111,7 +111,7 @@ namespace AZ ShaderMetricsSystem::Get()->RequestShaderVariant(pairItor->m_shaderAsset.Get(), pairItor->m_shaderVariantId, searchResult); uint32_t shaderVariantProductSubId = - ShaderVariantAsset::GetAssetSubId(RHI::Factory::Get().GetAPIUniqueIndex(), searchResult.GetStableId()); + ShaderVariantAsset::MakeAssetProductSubId(RHI::Factory::Get().GetAPIUniqueIndex(), searchResult.GetStableId()); Data::AssetId shaderVariantAssetId(shaderVariantTreeAsset.GetId().m_guid, shaderVariantProductSubId); shaderVariantPendingRequests.insert(shaderVariantAssetId); pairItor = newShaderVariantPendingRequests.erase(pairItor); @@ -211,7 +211,7 @@ namespace AZ AZ_Assert(variantStableId != RootShaderVariantStableId, "Root Variants Are Found inside ShaderAssets"); uint32_t shaderVariantProductSubId = - ShaderVariantAsset::GetAssetSubId(RHI::Factory::Get().GetAPIUniqueIndex(), variantStableId); + ShaderVariantAsset::MakeAssetProductSubId(RHI::Factory::Get().GetAPIUniqueIndex(), variantStableId); Data::AssetId shaderVariantAssetId(shaderVariantTreeAssetId.m_guid, shaderVariantProductSubId); { AZStd::unique_lock lock(m_mutex); @@ -299,7 +299,7 @@ namespace AZ { AZ_Assert(variantStableId != RootShaderVariantStableId, "Root Variants Are Found inside ShaderAssets"); - uint32_t shaderVariantProductSubId = ShaderVariantAsset::GetAssetSubId(RHI::Factory::Get().GetAPIUniqueIndex(), variantStableId); + uint32_t shaderVariantProductSubId = ShaderVariantAsset::MakeAssetProductSubId(RHI::Factory::Get().GetAPIUniqueIndex(), variantStableId); Data::AssetId shaderVariantAssetId(shaderVariantTreeAssetId.m_guid, shaderVariantProductSubId); AZStd::unique_lock lock(m_mutex); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/MorphTargetMetaAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/MorphTargetMetaAsset.cpp index 313e0bea31..3c0f832807 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/MorphTargetMetaAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/MorphTargetMetaAsset.cpp @@ -28,6 +28,7 @@ namespace AZ::RPI ->Field("numVertices", &MorphTargetMetaAsset::MorphTarget::m_numVertices) ->Field("minPositionDelta", &MorphTargetMetaAsset::MorphTarget::m_minPositionDelta) ->Field("maxPositionDelta", &MorphTargetMetaAsset::MorphTarget::m_maxPositionDelta) + ->Field("wrinkleMask", &MorphTargetMetaAsset::MorphTarget::m_wrinkleMask) ->Field("hasColorDeltas", &MorphTargetMetaAsset::MorphTarget::m_hasColorDeltas) ; } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp index fa791537e7..0a59772d6c 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp @@ -32,6 +32,25 @@ namespace AZ const ShaderVariantStableId ShaderAsset::RootShaderVariantStableId{ 0 }; + uint32_t ShaderAsset::MakeAssetProductSubId(uint32_t rhiApiUniqueIndex, uint32_t subProductType) + { + static constexpr uint32_t RhiIndexBitPosition = 30; + static constexpr uint32_t RhiIndexNumBits = 32 - RhiIndexBitPosition; + static constexpr uint32_t RhiIndexMaxValue = (1 << RhiIndexNumBits) - 1; + + static constexpr uint32_t SubProductTypeBitPosition = 0; + static constexpr uint32_t SubProductTypeNumBits = RhiIndexBitPosition - SubProductTypeBitPosition; + static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1; + + static_assert(RhiIndexMaxValue == RHI::Limits::APIType::PerPlatformApiUniqueIndexMax); + AZ_Assert(rhiApiUniqueIndex <= RhiIndexMaxValue, "Invalid rhiApiUniqueIndex [%u]", rhiApiUniqueIndex); + AZ_Assert(subProductType <= SubProductTypeMaxValue, "Invalid subProductType [%u]", subProductType); + + const uint32_t assetProductSubId = (rhiApiUniqueIndex << RhiIndexBitPosition) | + (subProductType << SubProductTypeBitPosition); + return assetProductSubId; + } + void ShaderAsset::ShaderApiDataContainer::Reflect(AZ::ReflectContext* context) { if (auto* serializeContext = azrtti_cast(context)) @@ -430,115 +449,5 @@ namespace AZ /////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - // Deprecated System - ////////////////////////////////////////////////////////////////////////// - - const char* ToString(ShaderStageType shaderStageType) - { - switch (shaderStageType) - { - case ShaderStageType::Vertex: return "Vertex"; - case ShaderStageType::Geometry: return "Geometry"; - case ShaderStageType::TessellationControl: return "TessellationControl"; - case ShaderStageType::TessellationEvaluation: return "TessellationEvaluation"; - case ShaderStageType::Fragment: return "Fragment"; - case ShaderStageType::Compute: return "Compute"; - case ShaderStageType::RayTracing: return "RayTracing"; - default: - AZ_Assert(false, "Unhandled type"); - return ""; - } - } - - void ReflectShaderStageType(ReflectContext* context) - { - if (auto* serializeContext = azrtti_cast(context)) - { - serializeContext->Enum() - ->Value(ToString(ShaderStageType::Vertex), ShaderStageType::Vertex) - ->Value(ToString(ShaderStageType::Geometry), ShaderStageType::Geometry) - ->Value(ToString(ShaderStageType::TessellationControl), ShaderStageType::TessellationControl) - ->Value(ToString(ShaderStageType::TessellationEvaluation), ShaderStageType::TessellationEvaluation) - ->Value(ToString(ShaderStageType::Fragment), ShaderStageType::Fragment) - ->Value(ToString(ShaderStageType::Compute), ShaderStageType::Compute) - ->Value(ToString(ShaderStageType::RayTracing), ShaderStageType::RayTracing) - ; - } - } - - ShaderAssetSubId ShaderStageToSubId(ShaderStageType stageType) - { - switch (stageType) - { - case RPI::ShaderStageType::Vertex: - return ShaderAssetSubId::AzVertexShader; - case RPI::ShaderStageType::Geometry: - return ShaderAssetSubId::AzGeometryShader; - case RPI::ShaderStageType::TessellationControl: - return ShaderAssetSubId::AzTessellationControlShader; - case RPI::ShaderStageType::TessellationEvaluation: - return ShaderAssetSubId::AzTessellationEvaluationShader; - case RPI::ShaderStageType::Fragment: - return ShaderAssetSubId::AzFragmentShader; - case RPI::ShaderStageType::Compute: - return ShaderAssetSubId::AzComputeShader; - case RPI::ShaderStageType::RayTracing: - return ShaderAssetSubId::AzRayTracingShader; - default: - AZ_Assert(false, "Trying to get a ShaderAssetSubId from an unknown ShaderStageType. Defaulting to a vertex shader."); - break; - } - - return ShaderAssetSubId::AzVertexShader; - } - void ShaderStageDescriptor::Reflect(ReflectContext* context) - { - if (auto* serializeContext = azrtti_cast(context)) - { - serializeContext->Class() - ->Version(1) - ->Field("m_stageType", &ShaderStageDescriptor::m_stageType) - ->Field("m_byteCode", &ShaderStageDescriptor::m_byteCode) - ; - } - } - - - /////////////////////////////////////////////////////////////////////// - // ShaderStageAsset - - void ShaderStageAsset::Reflect(ReflectContext* context) - { - ShaderStageDescriptor::Reflect(context); - - if (auto* serializeContext = azrtti_cast(context)) - { - serializeContext->Class() - ->Version(1) - ->Field("m_descriptor", &ShaderStageAsset::m_descriptor) - ->Field("m_srgLayouts", &ShaderStageAsset::m_srgLayouts) - ; - } - } - - ShaderStageAsset::ShaderStageAsset(const ShaderStageAsset& rhs) - { - *this = rhs; - } - - ShaderStageAsset::ShaderStageAsset(ShaderStageAsset&& rhs) - : m_descriptor(AZStd::move(rhs.m_descriptor)) - , m_srgLayouts(AZStd::move(rhs.m_srgLayouts)) - {} - - ShaderStageAsset& ShaderStageAsset::operator= (const ShaderStageAsset& rhs) - { - m_descriptor = rhs.m_descriptor; - m_srgLayouts = rhs.m_srgLayouts; - return *this; - } - /////////////////////////////////////////////////////////////////////// - } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset2.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset2.cpp new file mode 100644 index 0000000000..749f53aae7 --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset2.cpp @@ -0,0 +1,589 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + const ShaderVariantStableId ShaderAsset2::RootShaderVariantStableId{0}; + + static constexpr uint32_t SubProductTypeBitPosition = 0; + static constexpr uint32_t SubProductTypeNumBits = SupervariantIndexBitPosition - SubProductTypeBitPosition; + static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1; + + static_assert(RhiIndexMaxValue == RHI::Limits::APIType::PerPlatformApiUniqueIndexMax); + + uint32_t ShaderAsset2::MakeProductAssetSubId( + uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, uint32_t subProductType) + { + AZ_Assert(rhiApiUniqueIndex <= RhiIndexMaxValue, "Invalid rhiApiUniqueIndex [%u]", rhiApiUniqueIndex); + AZ_Assert(supervariantIndex <= SupervariantIndexMaxValue, "Invalid supervariantIndex [%u]", supervariantIndex); + AZ_Assert(subProductType <= SubProductTypeMaxValue, "Invalid subProductType [%u]", subProductType); + + const uint32_t assetProductSubId = (rhiApiUniqueIndex << RhiIndexBitPosition) | + (supervariantIndex << SupervariantIndexBitPosition) | (subProductType << SubProductTypeBitPosition); + return assetProductSubId; + } + + SupervariantIndex ShaderAsset2::GetSupervariantIndexFromProductAssetSubId(uint32_t assetProducSubId) + { + const uint32_t supervariantIndex = assetProducSubId >> SupervariantIndexBitPosition; + return SupervariantIndex{supervariantIndex & SupervariantIndexMaxValue}; + } + + SupervariantIndex ShaderAsset2::GetSupervariantIndexFromAssetId(const Data::AssetId& assetId) + { + return GetSupervariantIndexFromProductAssetSubId(assetId.m_subId); + } + + void ShaderAsset2::Supervariant::Reflect(AZ::ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1) + ->Field("Name", &Supervariant::m_name) + ->Field("SrgLayoutList", &Supervariant::m_srgLayoutList) + ->Field("PipelineLayout", &Supervariant::m_pipelineLayoutDescriptor) + ->Field("InputContract", &Supervariant::m_inputContract) + ->Field("OutputContract", &Supervariant::m_outputContract) + ->Field("RenderStates", &Supervariant::m_renderStates) + ->Field("AttributeMapList", &Supervariant::m_attributeMaps) + ->Field("RootVariantAsset", &Supervariant::m_rootShaderVariantAsset) + ; + } + } + + void ShaderAsset2::ShaderApiDataContainer::Reflect(AZ::ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1) + ->Field("APIType", &ShaderApiDataContainer::m_APIType) + ->Field("Supervariants", &ShaderApiDataContainer::m_supervariants) + ; + } + } + + void ShaderAsset2::Reflect(ReflectContext* context) + { + Supervariant::Reflect(context); + + ShaderApiDataContainer::Reflect(context); + + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1) + ->Field("name", &ShaderAsset2::m_name) + ->Field("pipelineStateType", &ShaderAsset2::m_pipelineStateType) + ->Field("shaderOptionGroupLayout", &ShaderAsset2::m_shaderOptionGroupLayout) + ->Field("drawListName", &ShaderAsset2::m_drawListName) + ->Field("shaderAssetBuildTimestamp", &ShaderAsset2::m_shaderAssetBuildTimestamp) + ->Field("perAPIShaderData", &ShaderAsset2::m_perAPIShaderData) + ; + } + } + + ShaderAsset2::~ShaderAsset2() + { + Data::AssetBus::Handler::BusDisconnect(); + ShaderVariantFinderNotificationBus2::Handler::BusDisconnect(); + } + + const Name& ShaderAsset2::GetName() const + { + return m_name; + } + + RHI::PipelineStateType ShaderAsset2::GetPipelineStateType() const + { + return m_pipelineStateType; + } + + const ShaderOptionGroupLayout* ShaderAsset2::GetShaderOptionGroupLayout() const + { + AZ_Assert(m_shaderOptionGroupLayout, "m_shaderOptionGroupLayout is null"); + return m_shaderOptionGroupLayout.get(); + } + + const Name& ShaderAsset2::GetDrawListName() const + { + return m_drawListName; + } + + AZStd::sys_time_t ShaderAsset2::GetShaderAssetBuildTimestamp() const + { + return m_shaderAssetBuildTimestamp; + } + + void ShaderAsset2::SetReady() + { + m_status = AssetStatus::Ready; + } + + + SupervariantIndex ShaderAsset2::GetSupervariantIndex(const AZ::Name& supervariantName) const + { + const auto& supervariants = GetCurrentShaderApiData().m_supervariants; + const uint32_t supervariantCount = supervariants.size(); + for (uint32_t index = 0; index < supervariantCount; ++index) + { + if (supervariants[index].m_name == supervariantName) + { + return SupervariantIndex{index}; + } + } + return InvalidSupervariantIndex; + } + + + Data::Asset ShaderAsset2::GetVariant( + const ShaderVariantId& shaderVariantId, SupervariantIndex supervariantIndex) + { + AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); + + auto variantFinder = AZ::Interface::Get(); + AZ_Assert(variantFinder, "The IShaderVariantFinder doesn't exist"); + + Data::Asset thisAsset(this, Data::AssetLoadBehavior::Default); + Data::Asset shaderVariantAsset = + variantFinder->GetShaderVariantAssetByVariantId(thisAsset, shaderVariantId, supervariantIndex); + if (!shaderVariantAsset) + { + variantFinder->QueueLoadShaderVariantAssetByVariantId(thisAsset, shaderVariantId, supervariantIndex); + } + return shaderVariantAsset; + } + + ShaderVariantSearchResult ShaderAsset2::FindVariantStableId(const ShaderVariantId& shaderVariantId) + { + AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); + + uint32_t dynamicOptionCount = aznumeric_cast(GetShaderOptionGroupLayout()->GetShaderOptions().size()); + ShaderVariantSearchResult variantSearchResult{RootShaderVariantStableId, dynamicOptionCount }; + + if (!dynamicOptionCount) + { + // The shader has no options at all. There's nothing to search. + return variantSearchResult; + } + + auto variantFinder = AZ::Interface::Get(); + AZ_Assert(variantFinder, "The IShaderVariantFinder doesn't exist"); + + { + AZStd::shared_lock lock(m_variantTreeMutex); + if (m_shaderVariantTree) + { + return m_shaderVariantTree->FindVariantStableId(GetShaderOptionGroupLayout(), shaderVariantId); + } + } + + AZStd::unique_lock lock(m_variantTreeMutex); + if (!m_shaderVariantTree) + { + m_shaderVariantTree = variantFinder->GetShaderVariantTreeAsset(GetId()); + if (!m_shaderVariantTree) + { + if (!m_shaderVariantTreeLoadWasRequested) + { + variantFinder->QueueLoadShaderVariantTreeAsset(GetId()); + m_shaderVariantTreeLoadWasRequested = true; + } + + // The variant tree could be under construction or simply doesn't exist at all. + return variantSearchResult; + } + } + return m_shaderVariantTree->FindVariantStableId(GetShaderOptionGroupLayout(), shaderVariantId); + } + + Data::Asset ShaderAsset2::GetVariant( + ShaderVariantStableId shaderVariantStableId, SupervariantIndex supervariantIndex) const + { + if (!shaderVariantStableId.IsValid() || shaderVariantStableId == RootShaderVariantStableId) + { + return GetRootVariant(supervariantIndex); + } + + auto variantFinder = AZ::Interface::Get(); + AZ_Assert(variantFinder, "No Variant Finder For shaderAsset with name [%s] and stableId [%u]", GetName().GetCStr(), shaderVariantStableId.GetIndex()); + Data::Asset variant = + variantFinder->GetShaderVariantAsset(m_shaderVariantTree.GetId(), shaderVariantStableId, supervariantIndex); + if (!variant.IsReady()) + { + // Enqueue a request to load the variant, next time around the caller will get the asset. + Data::AssetId variantTreeAssetId; + { + AZStd::shared_lock lock(m_variantTreeMutex); + if (m_shaderVariantTree) + { + variantTreeAssetId = m_shaderVariantTree.GetId(); + } + } + if (variantTreeAssetId.IsValid()) + { + variantFinder->QueueLoadShaderVariantAsset(variantTreeAssetId, shaderVariantStableId, supervariantIndex); + } + return GetRootVariant(supervariantIndex); + } + else if (variant->GetBuildTimestamp() >= m_shaderAssetBuildTimestamp) + { + return variant; + } + else + { + // When rebuilding shaders we may be in a state where the ShaderAsset2 and root ShaderVariantAsset have been rebuilt and reloaded, but some (or all) + // shader variants haven't been built yet. Since we want to use the latest version of the shader code, ignore the old variants and fall back to the newer root variant instead. + AZ_Warning("ShaderAsset2", false, "ShaderAsset2 and ShaderVariantAsset are out of sync; defaulting to root shader variant. (This is common while reloading shaders)."); + return GetRootVariant(supervariantIndex); + } + } + + Data::Asset ShaderAsset2::GetRootVariant(SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + if (!supervariant) + { + return Data::Asset(); + } + return supervariant->m_rootShaderVariantAsset; + } + + const RHI::Ptr ShaderAsset2::FindShaderResourceGroupLayout( + const Name& shaderResourceGroupName, SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + if (!supervariant) + { + return nullptr; + } + const auto& srgLayoutList = supervariant->m_srgLayoutList; + const auto findIt = AZStd::find_if(srgLayoutList.begin(), srgLayoutList.end(), [&](const RHI::Ptr& layout) + { + return layout->GetName() == shaderResourceGroupName; + }); + + if (findIt != srgLayoutList.end()) + { + return *findIt; + } + + return nullptr; + } + + const RHI::Ptr ShaderAsset2::FindShaderResourceGroupLayout( + uint32_t bindingSlot, SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + if (!supervariant) + { + return nullptr; + } + const auto& srgLayoutList = supervariant->m_srgLayoutList; + const auto findIt = + AZStd::find_if(srgLayoutList.begin(), srgLayoutList.end(), [&](const RHI::Ptr& layout) + { + return layout && layout->GetBindingSlot() == bindingSlot; + }); + + if (findIt != srgLayoutList.end()) + { + return *findIt; + } + + return nullptr; + } + + const RHI::Ptr ShaderAsset2::FindFallbackShaderResourceGroupLayout( + SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + if (!supervariant) + { + return nullptr; + } + const auto& srgLayoutList = supervariant->m_srgLayoutList; + const auto findIt = + AZStd::find_if(srgLayoutList.begin(), srgLayoutList.end(), [&](const RHI::Ptr& layout) + { + return layout && layout->HasShaderVariantKeyFallbackEntry(); + }); + + if (findIt != srgLayoutList.end()) + { + return *findIt; + } + + return nullptr; + } + + AZStd::array_view> ShaderAsset2::GetShaderResourceGroupLayouts( + SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + if (!supervariant) + { + return {}; + } + return supervariant->m_srgLayoutList; + } + + + const RHI::Ptr ShaderAsset2::GetDrawSrgLayout(SupervariantIndex supervariantIndex) const + { + return FindShaderResourceGroupLayout(SrgBindingSlot::Draw, supervariantIndex); + } + + const ShaderInputContract& ShaderAsset2::GetInputContract(SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + return supervariant->m_inputContract; + } + + const ShaderOutputContract& ShaderAsset2::GetOutputContract(SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + return supervariant->m_outputContract; + } + + const RHI::RenderStates& ShaderAsset2::GetRenderStates(SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + return supervariant->m_renderStates; + } + + const RHI::PipelineLayoutDescriptor* ShaderAsset2::GetPipelineLayoutDescriptor(SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + if (!supervariant) + { + return nullptr; + } + AZ_Assert(supervariant->m_pipelineLayoutDescriptor, "m_pipelineLayoutDescriptor is null"); + return supervariant->m_pipelineLayoutDescriptor.get(); + } + + AZStd::optional ShaderAsset2::GetAttribute(const RHI::ShaderStage& shaderStage, const Name& attributeName, + SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + if (!supervariant) + { + return AZStd::nullopt; + } + const auto stageIndex = static_cast(shaderStage); + AZ_Assert(stageIndex < RHI::ShaderStageCount, "Invalid shader stage specified!"); + + const auto& attributeMaps = supervariant->m_attributeMaps; + const auto& attrPair = attributeMaps[stageIndex].find(attributeName); + if (attrPair == attributeMaps[stageIndex].end()) + { + return AZStd::nullopt; + } + + return attrPair->second; + } + + ShaderAsset2::ShaderApiDataContainer& ShaderAsset2::GetCurrentShaderApiData() + { + const size_t perApiShaderDataCount = m_perAPIShaderData.size(); + AZ_Assert(perApiShaderDataCount > 0, "Invalid m_perAPIShaderData"); + + if (m_currentAPITypeIndex < perApiShaderDataCount) + { + return m_perAPIShaderData[m_currentAPITypeIndex]; + } + + // We may only endup here when running in a Builder context. + return m_perAPIShaderData[0]; + } + + const ShaderAsset2::ShaderApiDataContainer& ShaderAsset2::GetCurrentShaderApiData() const + { + const size_t perApiShaderDataCount = m_perAPIShaderData.size(); + AZ_Assert(perApiShaderDataCount > 0, "Invalid m_perAPIShaderData"); + + if (m_currentAPITypeIndex < perApiShaderDataCount) + { + return m_perAPIShaderData[m_currentAPITypeIndex]; + } + + // We may only endup here when running in a Builder context. + return m_perAPIShaderData[0]; + } + + ShaderAsset2::Supervariant* ShaderAsset2::GetSupervariant(SupervariantIndex supervariantIndex) + { + auto& supervariants = GetCurrentShaderApiData().m_supervariants; + auto index = supervariantIndex.GetIndex(); + if (index >= supervariants.size()) + { + AZ_Error( + "ShaderAsset2", false, "Supervariant index = %u is invalid because there are only %zu supervariants", index, + supervariants.size()); + return nullptr; + } + + return &supervariants[index]; + } + + const ShaderAsset2::Supervariant* ShaderAsset2::GetSupervariant(SupervariantIndex supervariantIndex) const + { + const auto& supervariants = GetCurrentShaderApiData().m_supervariants; + auto index = supervariantIndex.GetIndex(); + if (index >= supervariants.size()) + { + AZ_Error( + "ShaderAsset2", false, "Supervariant index = %u is invalid because there are only %zu supervariants", index, + supervariants.size()); + return nullptr; + } + + return &supervariants[index]; + } + + bool ShaderAsset2::FinalizeAfterLoad() + { + // Use the current RHI that is active to select which shader data to use. + // We don't assert if the Factory is not available because this method could be called during build time, + // when no Factory is available. Some assets (like the material asset) need to load the ShaderAsset2 + // in order to get some non API specific data (like a ShaderResourceGroup) during their build + // process. If they try to access any RHI API specific data, an assert will be trigger because the + // correct API index will not set. + if (RHI::Factory::IsReady()) + { + auto rhiType = RHI::Factory::Get().GetType(); + auto findIt = AZStd::find_if(m_perAPIShaderData.begin(), m_perAPIShaderData.end(), [&rhiType](const auto& shaderData) + { + return shaderData.m_APIType == rhiType; + }); + + if (findIt != m_perAPIShaderData.end()) + { + m_currentAPITypeIndex = AZStd::distance(m_perAPIShaderData.begin(), findIt); + } + else + { + AZ_Error("ShaderAsset2", false, "Could not find shader for API %s in shader %s", RHI::Factory::Get().GetName().GetCStr(), GetName().GetCStr()); + return false; + } + } + + // Common finalize check + for (const auto& shaderApiData : m_perAPIShaderData) + { + const auto& supervariants = shaderApiData.m_supervariants; + for (const auto& supervariant : supervariants) + { + bool beTrue = supervariant.m_attributeMaps.size() == RHI::ShaderStageCount; + if (!beTrue) + { + AZ_Error("ShaderAsset2", false, "Unexpected number of shader stages at supervariant with name [%s]!", supervariant.m_name.GetCStr()); + return false; + } + } + } + + // Once the ShaderAsset2 is loaded, it is necessary to listen for changes in the Root Variant Asset. + Data::AssetBus::Handler::BusConnect(GetRootVariant().GetId()); + ShaderVariantFinderNotificationBus2::Handler::BusConnect(GetId()); + + return true; + } + + /////////////////////////////////////////////////////////////////////// + // AssetBus overrides... + void ShaderAsset2::OnAssetReloaded(Data::Asset asset) + { + ShaderReloadDebugTracker::ScopedSection reloadSection("ShaderAsset2::OnAssetReloaded %s", asset.GetHint().c_str()); + + Data::Asset shaderVariantAsset = { asset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; + AZ_Assert(shaderVariantAsset->GetStableId() == RootShaderVariantStableId, + "Was expecting to update the root variant"); + SupervariantIndex supervariantIndex = GetSupervariantIndexFromAssetId(asset.GetId()); + GetCurrentShaderApiData().m_supervariants[supervariantIndex.GetIndex()].m_rootShaderVariantAsset = asset; + + ShaderReloadNotificationBus2::Event(GetId(), &ShaderReloadNotificationBus2::Events::OnShaderAssetReinitialized, Data::Asset{ this, AZ::Data::AssetLoadBehavior::PreLoad } ); + } + /////////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + /// ShaderVariantFinderNotificationBus2 overrides + void ShaderAsset2::OnShaderVariantTreeAssetReady(Data::Asset shaderVariantTreeAsset, bool isError) + { + ShaderReloadDebugTracker::ScopedSection reloadSection("ShaderAsset2::OnShaderVariantTreeAssetReady %s", shaderVariantTreeAsset.GetHint().c_str()); + + AZStd::unique_lock lock(m_variantTreeMutex); + if (isError) + { + m_shaderVariantTree = {}; //This will force to attempt to reload later. + m_shaderVariantTreeLoadWasRequested = false; + } + else + { + m_shaderVariantTree = shaderVariantTreeAsset; + } + lock.unlock(); + ShaderReloadNotificationBus2::Event(GetId(), &ShaderReloadNotificationBus2::Events::OnShaderAssetReinitialized, Data::Asset{ this, AZ::Data::AssetLoadBehavior::PreLoad }); + } + + /////////////////////////////////////////////////////////////////// + + + /////////////////////////////////////////////////////////////////////// + // ShaderAssetHandler + + Data::AssetHandler::LoadResult ShaderAssetHandler2::LoadAssetData( + const Data::Asset& asset, + AZStd::shared_ptr stream, + const Data::AssetFilterCB& assetLoadFilterCB) + { + if (Base::LoadAssetData(asset, stream, assetLoadFilterCB) == Data::AssetHandler::LoadResult::LoadComplete) + { + return PostLoadInit(asset); + } + return Data::AssetHandler::LoadResult::Error; + } + + Data::AssetHandler::LoadResult ShaderAssetHandler2::PostLoadInit(const Data::Asset& asset) + { + if (ShaderAsset2* shaderAsset = asset.GetAs()) + { + if (!shaderAsset->FinalizeAfterLoad()) + { + AZ_Error("ShaderAssetHandler", false, "Shader asset failed to finalize."); + return Data::AssetHandler::LoadResult::Error; + } + return Data::AssetHandler::LoadResult::LoadComplete; + } + return Data::AssetHandler::LoadResult::Error; + } + + /////////////////////////////////////////////////////////////////////// + + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAssetCreator2.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAssetCreator2.cpp new file mode 100644 index 0000000000..af8340a343 --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAssetCreator2.cpp @@ -0,0 +1,404 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +namespace AZ +{ + namespace RPI + { + void ShaderAssetCreator2::Begin(const Data::AssetId& assetId) + { + BeginCommon(assetId); + } + + void ShaderAssetCreator2::SetShaderAssetBuildTimestamp(AZStd::sys_time_t shaderAssetBuildTimestamp) + { + if (ValidateIsReady()) + { + m_asset->m_shaderAssetBuildTimestamp = shaderAssetBuildTimestamp; + } + } + + void ShaderAssetCreator2::SetName(const Name& name) + { + if (ValidateIsReady()) + { + m_asset->m_name = name; + } + } + + void ShaderAssetCreator2::SetDrawListName(const Name& name) + { + if (ValidateIsReady()) + { + m_asset->m_drawListName = name; + } + } + + void ShaderAssetCreator2::SetShaderOptionGroupLayout(const Ptr& shaderOptionGroupLayout) + { + if (ValidateIsReady()) + { + m_asset->m_shaderOptionGroupLayout = shaderOptionGroupLayout; + } + } + + void ShaderAssetCreator2::BeginAPI(RHI::APIType type) + { + if (ValidateIsReady()) + { + ShaderAsset2::ShaderApiDataContainer shaderData; + shaderData.m_APIType = type; + m_asset->m_currentAPITypeIndex = m_asset->m_perAPIShaderData.size(); + m_asset->m_perAPIShaderData.push_back(shaderData); + } + } + + void ShaderAssetCreator2::BeginSupervariant(const Name& name) + { + if (!ValidateIsReady()) + { + return; + } + + if (m_currentSupervariant) + { + ReportError("Call EndSupervariant() before calling BeginSupervariant again."); + return; + } + + if (m_asset->m_currentAPITypeIndex == ShaderAsset2::InvalidAPITypeIndex) + { + ReportError("Can not begin supervariant with name [%s] because this function must be called between BeginAPI()/EndAPI()", name.GetCStr()); + return; + } + + if (m_asset->m_perAPIShaderData.empty()) + { + ReportError("Can not add supervariant with name [%s] because there's no per API shader data", name.GetCStr()); + return; + } + + ShaderAsset2::ShaderApiDataContainer& perAPIShaderData = m_asset->m_perAPIShaderData[m_asset->m_perAPIShaderData.size() - 1]; + if (perAPIShaderData.m_supervariants.empty()) + { + if (!name.IsEmpty()) + { + ReportError("The first supervariant must be nameless. Name [%s] is invalid", name.GetCStr()); + return; + } + } + else + { + if (name.IsEmpty()) + { + ReportError( + "Only the first supervariant can be nameless. So far there are %zu supervariants", + perAPIShaderData.m_supervariants.size()); + return; + } + } + + perAPIShaderData.m_supervariants.push_back({}); + m_currentSupervariant = &perAPIShaderData.m_supervariants[perAPIShaderData.m_supervariants.size() - 1]; + m_currentSupervariant->m_name = name; + } + + void ShaderAssetCreator2::SetSrgLayoutList(const ShaderResourceGroupLayoutList& srgLayoutList) + { + if (!ValidateIsReady()) + { + return; + } + + if (!m_currentSupervariant) + { + ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__); + return; + } + + m_currentSupervariant->m_srgLayoutList = srgLayoutList; + for (auto srgLayout : m_currentSupervariant->m_srgLayoutList) + { + if (!srgLayout->Finalize()) + { + ReportError( + "The current supervariant [%s], failed to finalize SRG Layout [%s]", m_currentSupervariant->m_name.GetCStr(), + srgLayout->GetName().GetCStr()); + return; + } + } + } + + //! [Required] Assigns the pipeline layout descriptor shared by all variants in the shader. Shader variants + //! embedded in a single shader asset are required to use the same pipeline layout. It is not necessary to call + //! Finalize() on the pipeline layout prior to assignment, but still permitted. + void ShaderAssetCreator2::SetPipelineLayout(RHI::Ptr pipelineLayoutDescriptor) + { + if (!ValidateIsReady()) + { + return; + } + if (!m_currentSupervariant) + { + ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__); + return; + } + if (m_currentSupervariant->m_srgLayoutList.empty()) + { + ReportError( + "Before setting the pipeline layout, the supervariant [%s] needs the SRG layouts", + m_currentSupervariant->m_name.GetCStr()); + return; + } + m_currentSupervariant->m_pipelineLayoutDescriptor = pipelineLayoutDescriptor; + } + + //! Assigns the contract for inputs required by the shader. + void ShaderAssetCreator2::SetInputContract(const ShaderInputContract& contract) + { + if (!ValidateIsReady()) + { + return; + } + if (!m_currentSupervariant) + { + ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__); + return; + } + m_currentSupervariant->m_inputContract = contract; + } + + //! Assigns the contract for outputs required by the shader. + void ShaderAssetCreator2::SetOutputContract(const ShaderOutputContract& contract) + { + if (!ValidateIsReady()) + { + return; + } + if (!m_currentSupervariant) + { + ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__); + return; + } + m_currentSupervariant->m_outputContract = contract; + } + + //! Assigns the render states for the draw pipeline. Ignored for non-draw pipelines. + void ShaderAssetCreator2::SetRenderStates(const RHI::RenderStates& renderStates) + { + if (!ValidateIsReady()) + { + return; + } + if (!m_currentSupervariant) + { + ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__); + return; + } + m_currentSupervariant->m_renderStates = renderStates; + } + + //! [Optional] Not all shaders have attributes before functions. Some attributes do not exist for all RHI::APIType either. + void ShaderAssetCreator2::SetShaderStageAttributeMapList(const RHI::ShaderStageAttributeMapList& shaderStageAttributeMapList) + { + if (!ValidateIsReady()) + { + return; + } + if (!m_currentSupervariant) + { + ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__); + return; + } + m_currentSupervariant->m_attributeMaps = shaderStageAttributeMapList; + } + + //! [Required] There's always a root variant for each supervariant. + void ShaderAssetCreator2::SetRootShaderVariantAsset(Data::Asset shaderVariantAsset) + { + if (!ValidateIsReady()) + { + return; + } + if (!m_currentSupervariant) + { + ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__); + return; + } + m_currentSupervariant->m_rootShaderVariantAsset = shaderVariantAsset; + } + + static RHI::PipelineStateType GetPipelineStateType(const Data::Asset& shaderVariantAsset) + { + if (shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Vertex) || + shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Tessellation) || + shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Fragment)) + { + return RHI::PipelineStateType::Draw; + } + + if (shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Compute)) + { + return RHI::PipelineStateType::Dispatch; + } + + if (shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::RayTracing)) + { + return RHI::PipelineStateType::RayTracing; + } + + return RHI::PipelineStateType::Count; + } + + bool ShaderAssetCreator2::EndSupervariant() + { + if (!ValidateIsReady()) + { + return false; + } + + if (!m_currentSupervariant) + { + ReportError("Can not end a supervariant that has not started"); + return false; + } + + if (!m_currentSupervariant->m_rootShaderVariantAsset.IsReady()) + { + ReportError( + "The current supervariant [%s], is missing the root ShaderVariantAsset", m_currentSupervariant->m_name.GetCStr()); + return false; + } + + // Supervariant specific resources + if (m_currentSupervariant->m_pipelineLayoutDescriptor) + { + if (!m_currentSupervariant->m_pipelineLayoutDescriptor->IsFinalized()) + { + if (m_currentSupervariant->m_pipelineLayoutDescriptor->Finalize() != RHI::ResultCode::Success) + { + ReportError("Failed to finalize pipeline layout descriptor."); + return false; + } + } + } + else + { + ReportError("PipelineLayoutDescriptor not specified."); + return false; + } + + const ShaderInputContract& shaderInputContract = m_currentSupervariant->m_inputContract; + // Validate that each stream ID appears only once. + for (const auto& channel : shaderInputContract.m_streamChannels) + { + int count = 0; + + for (const auto& searchChannel : shaderInputContract.m_streamChannels) + { + if (channel.m_semantic == searchChannel.m_semantic) + { + ++count; + } + } + + if (count > 1) + { + ReportError( + "Input stream channel [%s] appears multiple times. For supervariant with name [%s]", + channel.m_semantic.ToString().c_str(), m_currentSupervariant->m_name.GetCStr()); + return false; + } + } + + auto pipelineStateType = GetPipelineStateType(m_currentSupervariant->m_rootShaderVariantAsset); + if (pipelineStateType == RHI::PipelineStateType::Count) + { + ReportError("Invalid pipelineStateType for supervariant [%s]", m_currentSupervariant->m_name.GetCStr()); + return false; + } + + + if (m_currentSupervariant->m_name.IsEmpty()) + { + m_asset->m_pipelineStateType = pipelineStateType; + } + else + { + if (m_asset->m_pipelineStateType != pipelineStateType) + { + ReportError("All supervariants must be of the same pipelineStateType. Current pipelineStateType is [%d], but for supervariant [%s] the pipelineStateType is [%d]", + m_asset->m_pipelineStateType, m_currentSupervariant->m_name.GetCStr(), pipelineStateType); + return false; + } + } + + m_currentSupervariant = nullptr; + return true; + } + + bool ShaderAssetCreator2::EndAPI() + { + if (!ValidateIsReady()) + { + return false; + } + if (m_currentSupervariant) + { + ReportError("EndSupervariant() must be called before calling EndAPI()"); + return false; + } + + m_asset->m_currentAPITypeIndex = ShaderAsset2::InvalidAPITypeIndex; + return true; + } + + bool ShaderAssetCreator2::End(Data::Asset& shaderAsset) + { + if (!ValidateIsReady()) + { + return false; + } + + if (m_asset->m_perAPIShaderData.empty()) + { + ReportError("Empty shader data. Check that a valid RHI is enabled for this platform."); + return false; + } + + if (!m_asset->FinalizeAfterLoad()) + { + ReportError("Failed to finalize the ShaderAsset2."); + return false; + } + + m_asset->SetReady(); + + return EndCommon(shaderAsset); + } + + void ShaderAssetCreator2::Clone(const Data::AssetId& assetId, const ShaderAsset2& sourceShaderAsset) + { + BeginCommon(assetId); + + m_asset->m_name = sourceShaderAsset.m_name; + m_asset->m_pipelineStateType = sourceShaderAsset.m_pipelineStateType; + m_asset->m_drawListName = sourceShaderAsset.m_drawListName; + m_asset->m_shaderOptionGroupLayout = sourceShaderAsset.m_shaderOptionGroupLayout; + m_asset->m_shaderAssetBuildTimestamp = sourceShaderAsset.m_shaderAssetBuildTimestamp; + m_asset->m_perAPIShaderData = sourceShaderAsset.m_perAPIShaderData; + + } + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderStageType.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderStageType.cpp new file mode 100644 index 0000000000..845966708a --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderStageType.cpp @@ -0,0 +1,54 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +namespace AZ +{ + namespace RPI + { + const char* ToString(ShaderStageType shaderStageType) + { + switch (shaderStageType) + { + case ShaderStageType::Vertex: return "Vertex"; + case ShaderStageType::Geometry: return "Geometry"; + case ShaderStageType::TessellationControl: return "TessellationControl"; + case ShaderStageType::TessellationEvaluation: return "TessellationEvaluation"; + case ShaderStageType::Fragment: return "Fragment"; + case ShaderStageType::Compute: return "Compute"; + case ShaderStageType::RayTracing: return "RayTracing"; + default: + AZ_Assert(false, "Unhandled type"); + return ""; + } + } + + void ReflectShaderStageType(ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Enum() + ->Value(ToString(ShaderStageType::Vertex), ShaderStageType::Vertex) + ->Value(ToString(ShaderStageType::Geometry), ShaderStageType::Geometry) + ->Value(ToString(ShaderStageType::TessellationControl), ShaderStageType::TessellationControl) + ->Value(ToString(ShaderStageType::TessellationEvaluation), ShaderStageType::TessellationEvaluation) + ->Value(ToString(ShaderStageType::Fragment), ShaderStageType::Fragment) + ->Value(ToString(ShaderStageType::Compute), ShaderStageType::Compute) + ->Value(ToString(ShaderStageType::RayTracing), ShaderStageType::RayTracing) + ; + } + } + + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp index 3b52dda326..7864768351 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp @@ -21,6 +21,34 @@ namespace AZ { namespace RPI { + uint32_t ShaderVariantAsset::MakeAssetProductSubId( + uint32_t rhiApiUniqueIndex, ShaderVariantStableId variantStableId, uint32_t subProductType) + { + static constexpr uint32_t RhiIndexBitPosition = 30; + static constexpr uint32_t RhiIndexNumBits = 32 - RhiIndexBitPosition; + static constexpr uint32_t RhiIndexMaxValue = (1 << RhiIndexNumBits) - 1; + + static constexpr uint32_t SubProductTypeBitPosition = 17; + static constexpr uint32_t SubProductTypeNumBits = RhiIndexBitPosition - SubProductTypeBitPosition; + static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1; + + static constexpr uint32_t StableIdBitPosition = 0; + static constexpr uint32_t StableIdNumBits = SubProductTypeBitPosition - StableIdBitPosition; + static constexpr uint32_t StableIdMaxValue = (1 << StableIdNumBits) - 1; + + static_assert(RhiIndexMaxValue == RHI::Limits::APIType::PerPlatformApiUniqueIndexMax); + + // The 2 Most significant bits encode the the RHI::API unique index. + AZ_Assert(rhiApiUniqueIndex <= RhiIndexMaxValue, "Invalid rhiApiUniqueIndex [%u]", rhiApiUniqueIndex); + AZ_Assert(subProductType <= SubProductTypeMaxValue, "Invalid subProductType [%u]", subProductType); + AZ_Assert(variantStableId.GetIndex() <= StableIdMaxValue, "Invalid variantStableId [%u]", variantStableId.GetIndex()); + + const uint32_t assetProductSubId = (rhiApiUniqueIndex << RhiIndexBitPosition) | + (subProductType << SubProductTypeBitPosition) | + (variantStableId.GetIndex() << StableIdBitPosition); + return assetProductSubId; + } + void ShaderVariantAsset::Reflect(ReflectContext* context) { if (auto* serializeContext = azrtti_cast(context)) @@ -44,16 +72,6 @@ namespace AZ return m_shaderAssetBuildTimestamp; } - uint32_t ShaderVariantAsset::GetAssetSubId(uint32_t rhiApiUniqueIndex, ShaderVariantStableId variantStableId) - { - //The 2 Most significant bits encode the the RHI::API unique index. - AZ_Assert(rhiApiUniqueIndex <= RHI::Limits::APIType::PerPlatformApiUniqueIndexMax, "Invalid rhiApiUniqueIndex [%u]", rhiApiUniqueIndex); - AZ_Assert(variantStableId != RootShaderVariantStableId, "The product subId for the root variant is built differently."); - const uint32_t rhiApiSubId = rhiApiUniqueIndex << 30; - const uint32_t productSubId = rhiApiSubId | variantStableId.GetIndex(); - return productSubId; - } - const RHI::ShaderStageFunction* ShaderVariantAsset::GetShaderStageFunction(RHI::ShaderStage shaderStage) const { return m_functionsByStage[static_cast(shaderStage)].get(); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset2.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset2.cpp new file mode 100644 index 0000000000..34daff560a --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset2.cpp @@ -0,0 +1,114 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#include + +#include +#include +#include + +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + uint32_t ShaderVariantAsset2::MakeAssetProductSubId( + uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, ShaderVariantStableId variantStableId, uint32_t subProductType) + { + static constexpr uint32_t SubProductTypeBitPosition = 17; + static constexpr uint32_t SubProductTypeNumBits = SupervariantIndexBitPosition - SubProductTypeBitPosition; + static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1; + + static constexpr uint32_t StableIdBitPosition = 0; + static constexpr uint32_t StableIdNumBits = SubProductTypeBitPosition - StableIdBitPosition; + static constexpr uint32_t StableIdMaxValue = (1 << StableIdNumBits) - 1; + + static_assert(RhiIndexMaxValue == RHI::Limits::APIType::PerPlatformApiUniqueIndexMax); + + // The 2 Most significant bits encode the the RHI::API unique index. + AZ_Assert(rhiApiUniqueIndex <= RhiIndexMaxValue, "Invalid rhiApiUniqueIndex [%u]", rhiApiUniqueIndex); + AZ_Assert(supervariantIndex <= SupervariantIndexMaxValue, "Invalid supervariantIndex [%u]", supervariantIndex); + AZ_Assert(subProductType <= SubProductTypeMaxValue, "Invalid subProductType [%u]", subProductType); + AZ_Assert(variantStableId.GetIndex() <= StableIdMaxValue, "Invalid variantStableId [%u]", variantStableId.GetIndex()); + + const uint32_t assetProductSubId = (rhiApiUniqueIndex << RhiIndexBitPosition) | + (supervariantIndex << SupervariantIndexBitPosition) | (subProductType << SubProductTypeBitPosition) | + (variantStableId.GetIndex() << StableIdBitPosition); + return assetProductSubId; + } + + void ShaderVariantAsset2::Reflect(ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1) + ->Field("StableId", &ShaderVariantAsset2::m_stableId) + ->Field("ShaderVariantId", &ShaderVariantAsset2::m_shaderVariantId) + ->Field("IsFullyBaked", &ShaderVariantAsset2::m_isFullyBaked) + ->Field("FunctionsByStage", &ShaderVariantAsset2::m_functionsByStage) + ->Field("BuildTimestamp", &ShaderVariantAsset2::m_buildTimestamp) + ; + } + } + + AZStd::sys_time_t ShaderVariantAsset2::GetBuildTimestamp() const + { + return m_buildTimestamp; + } + + const RHI::ShaderStageFunction* ShaderVariantAsset2::GetShaderStageFunction(RHI::ShaderStage shaderStage) const + { + return m_functionsByStage[static_cast(shaderStage)].get(); + } + + bool ShaderVariantAsset2::IsFullyBaked() const + { + return m_isFullyBaked; + } + + void ShaderVariantAsset2::SetReady() + { + m_status = AssetStatus::Ready; + } + + bool ShaderVariantAsset2::FinalizeAfterLoad() + { + return true; + } + + ShaderVariantAssetHandler2::LoadResult ShaderVariantAssetHandler2::LoadAssetData(const Data::Asset& asset, AZStd::shared_ptr stream, const AZ::Data::AssetFilterCB& assetLoadFilterCB) + { + if (Base::LoadAssetData(asset, stream, assetLoadFilterCB) == LoadResult::LoadComplete) + { + return PostLoadInit(asset) ? LoadResult::LoadComplete : LoadResult::Error; + } + return LoadResult::Error; + } + + bool ShaderVariantAssetHandler2::PostLoadInit(const Data::Asset& asset) + { + if (ShaderVariantAsset2* shaderVariantAsset = asset.GetAs()) + { + if (!shaderVariantAsset->FinalizeAfterLoad()) + { + AZ_Error("ShaderVariantAssetHandler", false, "Shader asset failed to finalize."); + return false; + } + return true; + } + return false; + } + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/atom_rpi_edit_files.cmake b/Gems/Atom/RPI/Code/atom_rpi_edit_files.cmake index a8d3a0230d..2fcae8be90 100644 --- a/Gems/Atom/RPI/Code/atom_rpi_edit_files.cmake +++ b/Gems/Atom/RPI/Code/atom_rpi_edit_files.cmake @@ -35,6 +35,7 @@ set(FILES Include/Atom/RPI.Edit/Shader/ShaderSourceData.h Include/Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h Include/Atom/RPI.Edit/Shader/ShaderVariantAssetCreator.h + Include/Atom/RPI.Edit/Shader/ShaderVariantAssetCreator2.h Include/Atom/RPI.Edit/Shader/ShaderVariantTreeAssetCreator.h Source/RPI.Edit/Material/LuaMaterialFunctorSourceData.cpp Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -52,6 +53,7 @@ set(FILES Source/RPI.Edit/Shader/ShaderSourceData.cpp Source/RPI.Edit/Shader/ShaderVariantListSourceData.cpp Source/RPI.Edit/Shader/ShaderVariantAssetCreator.cpp + Source/RPI.Edit/Shader/ShaderVariantAssetCreator2.cpp Source/RPI.Edit/Shader/ShaderVariantTreeAssetCreator.cpp Source/RPI.Edit/Common/AssetUtils.cpp Source/RPI.Edit/Common/AssetAliasesSourceData.cpp diff --git a/Gems/Atom/RPI/Code/atom_rpi_public_files.cmake b/Gems/Atom/RPI/Code/atom_rpi_public_files.cmake index 71c3190a2e..0d5c19758b 100644 --- a/Gems/Atom/RPI/Code/atom_rpi_public_files.cmake +++ b/Gems/Atom/RPI/Code/atom_rpi_public_files.cmake @@ -81,8 +81,11 @@ set(FILES Include/Atom/RPI.Public/Pass/Specific/SelectorPass.h Include/Atom/RPI.Public/Pass/Specific/SwapChainPass.h Include/Atom/RPI.Public/Shader/Shader.h + Include/Atom/RPI.Public/Shader/Shader2.h Include/Atom/RPI.Public/Shader/ShaderReloadNotificationBus.h + Include/Atom/RPI.Public/Shader/ShaderReloadNotificationBus2.h Include/Atom/RPI.Public/Shader/ShaderVariant.h + Include/Atom/RPI.Public/Shader/ShaderVariant2.h Include/Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h Include/Atom/RPI.Public/Shader/ShaderResourceGroup.h Include/Atom/RPI.Public/Shader/ShaderResourceGroupPool.h @@ -155,7 +158,9 @@ set(FILES Source/RPI.Public/Pass/Specific/SelectorPass.cpp Source/RPI.Public/Pass/Specific/SwapChainPass.cpp Source/RPI.Public/Shader/Shader.cpp + Source/RPI.Public/Shader/Shader2.cpp Source/RPI.Public/Shader/ShaderVariant.cpp + Source/RPI.Public/Shader/ShaderVariant2.cpp Source/RPI.Public/Shader/ShaderReloadDebugTracker.cpp Source/RPI.Public/Shader/ShaderResourceGroup.cpp Source/RPI.Public/Shader/ShaderResourceGroupPool.cpp diff --git a/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake b/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake index 307f3a046b..3a4e1cacc6 100644 --- a/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake +++ b/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake @@ -76,8 +76,11 @@ set(FILES Include/Atom/RPI.Reflect/Pass/PassTemplate.h Include/Atom/RPI.Reflect/Pass/RasterPassData.h Include/Atom/RPI.Reflect/Pass/RenderPassData.h + Include/Atom/RPI.Reflect/Shader/ShaderCommonTypes.h Include/Atom/RPI.Reflect/Shader/ShaderAsset.h Include/Atom/RPI.Reflect/Shader/ShaderAssetCreator.h + Include/Atom/RPI.Reflect/Shader/ShaderAsset2.h + Include/Atom/RPI.Reflect/Shader/ShaderAssetCreator2.h Include/Atom/RPI.Reflect/Shader/ShaderInputContract.h Include/Atom/RPI.Reflect/Shader/ShaderOptionGroup.h Include/Atom/RPI.Reflect/Shader/ShaderOptionGroupLayout.h @@ -88,7 +91,9 @@ set(FILES Include/Atom/RPI.Reflect/Shader/ShaderVariantKey.h Include/Atom/RPI.Reflect/Shader/ShaderVariantTreeAsset.h Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h + Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h Include/Atom/RPI.Reflect/Shader/IShaderVariantFinder.h + Include/Atom/RPI.Reflect/Shader/IShaderVariantFinder2.h Include/Atom/RPI.Reflect/Shader/PrecompiledShaderAssetSourceData.h Include/Atom/RPI.Reflect/System/AnyAsset.h Include/Atom/RPI.Reflect/System/AssetAliases.h @@ -147,8 +152,11 @@ set(FILES Source/RPI.Reflect/Pass/PassAttachmentReflect.cpp Source/RPI.Reflect/Pass/PassRequest.cpp Source/RPI.Reflect/Pass/PassTemplate.cpp + Source/RPI.Reflect/Shader/ShaderStageType.cpp Source/RPI.Reflect/Shader/ShaderAsset.cpp Source/RPI.Reflect/Shader/ShaderAssetCreator.cpp + Source/RPI.Reflect/Shader/ShaderAsset2.cpp + Source/RPI.Reflect/Shader/ShaderAssetCreator2.cpp Source/RPI.Reflect/Shader/ShaderInputContract.cpp Source/RPI.Reflect/Shader/ShaderOptionGroup.cpp Source/RPI.Reflect/Shader/ShaderOptionGroupLayout.cpp @@ -158,6 +166,7 @@ set(FILES Source/RPI.Reflect/Shader/ShaderVariantKey.cpp Source/RPI.Reflect/Shader/ShaderVariantTreeAsset.cpp Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp + Source/RPI.Reflect/Shader/ShaderVariantAsset2.cpp Source/RPI.Reflect/Shader/PrecompiledShaderAssetSourceData.cpp Source/RPI.Reflect/System/AnyAsset.cpp Source/RPI.Reflect/System/AssetAliases.cpp diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Include/Atom/Document/ShaderManagementConsoleDocumentRequestBus.h b/Gems/Atom/Tools/ShaderManagementConsole/Code/Include/Atom/Document/ShaderManagementConsoleDocumentRequestBus.h index 06b8f13f65..d575049b23 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Include/Atom/Document/ShaderManagementConsoleDocumentRequestBus.h +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Include/Atom/Document/ShaderManagementConsoleDocumentRequestBus.h @@ -16,6 +16,7 @@ #include #include +#include #include #include diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Document/ShaderManagementConsoleDocument.h b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Document/ShaderManagementConsoleDocument.h index f0570841f3..6c3a2f55bf 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Document/ShaderManagementConsoleDocument.h +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Document/ShaderManagementConsoleDocument.h @@ -14,6 +14,7 @@ #include #include +#include #include #include diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/PostProcess/DisplayMapper/DisplayMapperComponentConfig.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/PostProcess/DisplayMapper/DisplayMapperComponentConfig.h index 81645b25a9..3c5bcbea00 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/PostProcess/DisplayMapper/DisplayMapperComponentConfig.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/PostProcess/DisplayMapper/DisplayMapperComponentConfig.h @@ -20,7 +20,6 @@ namespace AZ { namespace Render { - class DisplayMapperComponentConfig final : public ComponentConfig { @@ -33,6 +32,7 @@ namespace AZ DisplayMapperOperationType m_displayMapperOperation = DisplayMapperOperationType::Aces; bool m_ldrColorGradingLutEnabled = false; Data::Asset m_ldrColorGradingLut = {}; + AcesParameterOverrides m_acesParameterOverrides; }; } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/DisplayMapperComponentConfig.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/DisplayMapperComponentConfig.cpp index e6afd4f21f..317fa96450 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/DisplayMapperComponentConfig.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/DisplayMapperComponentConfig.cpp @@ -20,16 +20,18 @@ namespace AZ { void DisplayMapperComponentConfig::Reflect(ReflectContext* context) { + AcesParameterOverrides::Reflect(context); + if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(0) + ->Version(1) ->Field("DisplayMapperOperationType", &DisplayMapperComponentConfig::m_displayMapperOperation) ->Field("LdrColorGradingLutEnabled", &DisplayMapperComponentConfig::m_ldrColorGradingLutEnabled) ->Field("LdrColorGradingLut", &DisplayMapperComponentConfig::m_ldrColorGradingLut) + ->Field("AcesParameterOverrides", &DisplayMapperComponentConfig::m_acesParameterOverrides) ; } } - } // namespace Render } // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/DisplayMapperComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/DisplayMapperComponentController.cpp index d90e170322..0e283199e7 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/DisplayMapperComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/DisplayMapperComponentController.cpp @@ -85,6 +85,7 @@ namespace AZ desc.m_operationType = m_configuration.m_displayMapperOperation; desc.m_ldrGradingLutEnabled = m_configuration.m_ldrColorGradingLutEnabled; desc.m_ldrColorGradingLut = m_configuration.m_ldrColorGradingLut; + desc.m_acesParameterOverrides = m_configuration.m_acesParameterOverrides; fp->RegisterDisplayMapperConfiguration(desc); } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/EditorDisplayMapperComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/EditorDisplayMapperComponent.cpp index 80aad79218..64cd450940 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/EditorDisplayMapperComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/EditorDisplayMapperComponent.cpp @@ -10,6 +10,8 @@ * */ +#include "Atom/Feature/ACES/AcesDisplayMapperFeatureProcessor.h" + #include #include @@ -47,6 +49,76 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ; + editContext->Class( + "AcesParameterOverrides", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + + ->DataElement( + AZ::Edit::UIHandlers::CheckBox, &AcesParameterOverrides::m_overrideDefaults, "Override Defaults", + "When enabled allows parameter overrides for ACES configuration") + ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + + ->DataElement( + AZ::Edit::UIHandlers::CheckBox, &AcesParameterOverrides::m_alterSurround, "Alter Surround", + "Apply gamma adjustment to compensate for dim surround") + ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + ->DataElement( + AZ::Edit::UIHandlers::CheckBox, &AcesParameterOverrides::m_applyDesaturation, "Alter Desaturation", + "Apply desaturation to compensate for luminance difference") + ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + ->DataElement( + AZ::Edit::UIHandlers::CheckBox, &AcesParameterOverrides::m_applyCATD60toD65, "Alter CAT D60 to D65", + "Apply Color appearance transform (CAT) from ACES white point to assumed observer adapted white point") + ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + + ->DataElement( + Edit::UIHandlers::Default, &AcesParameterOverrides::m_cinemaLimitsBlack, + "Cinema Limit (black)", + "Reference black luminance value") + ->DataElement( + Edit::UIHandlers::Default, &AcesParameterOverrides::m_cinemaLimitsWhite, + "Cinema Limit (white)", + "Reference white luminance value") + ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + + ->DataElement( + Edit::UIHandlers::Vector2, &AcesParameterOverrides::m_minPoint, "Min Point (luminance)", + "Linear extension below this") + ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + ->DataElement( + Edit::UIHandlers::Vector2, &AcesParameterOverrides::m_midPoint, "Mid Point (luminance)", + "Middle gray") + ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + ->DataElement( + Edit::UIHandlers::Vector2, &AcesParameterOverrides::m_maxPoint, "Max Point (luminance)", + "Linear extension above this") + ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + + ->DataElement( + AZ::Edit::UIHandlers::Default, &AcesParameterOverrides::m_surroundGamma, "Surround Gamma", + "Gamma adjustment to be applied to compensate for the condition of the viewing environment") + ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + ->DataElement( + AZ::Edit::UIHandlers::Default, &AcesParameterOverrides::m_gamma, "Gamma", + "Optional gamma value that is applied as basic gamma curve OETF") + ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + + // Load preset group + ->ClassElement(AZ::Edit::ClassElements::Group, "Load Preset") + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->DataElement( + Edit::UIHandlers::ComboBox, &AcesParameterOverrides::m_preset, "Preset Selection", + "Allows specifying default preset for different ODT modes") + ->EnumAttribute(OutputDeviceTransformType::OutputDeviceTransformType_48Nits, "48 Nits") + ->EnumAttribute(OutputDeviceTransformType::OutputDeviceTransformType_1000Nits, "1000 Nits") + ->EnumAttribute(OutputDeviceTransformType::OutputDeviceTransformType_2000Nits, "2000 Nits") + ->EnumAttribute(OutputDeviceTransformType::OutputDeviceTransformType_4000Nits, "4000 Nits") + ->UIElement(AZ::Edit::UIHandlers::Button, "Load", "Load default preset") + ->Attribute(AZ::Edit::Attributes::ChangeNotify, &AcesParameterOverrides::LoadPreset) + ->Attribute(AZ::Edit::Attributes::ButtonText, "Load") + ; + editContext->Class("ToneMapperComponentConfig", "") ->ClassElement(Edit::ClassElements::EditorData, "") ->DataElement(Edit::UIHandlers::ComboBox, @@ -64,7 +136,10 @@ namespace AZ &DisplayMapperComponentConfig::m_ldrColorGradingLutEnabled, "Enable LDR color grading LUT", "Enable LDR color grading LUT.") - ->DataElement(AZ::Edit::UIHandlers::Default, &DisplayMapperComponentConfig::m_ldrColorGradingLut, "LDR color Grading LUT", "LDR color grading LUT"); + ->DataElement(AZ::Edit::UIHandlers::Default, &DisplayMapperComponentConfig::m_ldrColorGradingLut, "LDR color Grading LUT", "LDR color grading LUT") + ->DataElement(AZ::Edit::UIHandlers::Default, &DisplayMapperComponentConfig::m_acesParameterOverrides, "ACES Parameters", "Parameter overrides for ACES.") + ; + } } diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp index d0116452b7..9079f639ba 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -39,6 +40,8 @@ namespace AZ { namespace Render { + static constexpr uint32_t s_maxActiveWrinkleMasks = 16; + AZ_CLASS_ALLOCATOR_IMPL(AtomActorInstance, EMotionFX::Integration::EMotionFXAllocator, 0) AtomActorInstance::AtomActorInstance(AZ::EntityId entityId, @@ -413,6 +416,10 @@ namespace AZ EMotionFX::MorphSetup* morphSetup = m_actorInstance->GetActor()->GetMorphSetup(lodIndex); if (morphSetup) { + // Track all the masks/weights that are currently active + m_wrinkleMasks.clear(); + m_wrinkleMaskWeights.clear(); + uint32_t morphTargetCount = morphSetup->GetNumMorphTargets(); m_morphTargetWeights.clear(); for (uint32_t morphTargetIndex = 0; morphTargetIndex < morphTargetCount; ++morphTargetIndex) @@ -437,11 +444,28 @@ namespace AZ const EMotionFX::MorphTargetStandard::DeformData* deformData = morphTargetStandard->GetDeformData(deformDataIndex); if (deformData->mNumVerts > 0) { - m_morphTargetWeights.push_back(morphTargetSetupInstance->GetWeight()); + float weight = morphTargetSetupInstance->GetWeight(); + m_morphTargetWeights.push_back(weight); + + // If the morph target is active and it has a wrinkle mask + auto wrinkleMaskIter = m_morphTargetWrinkleMaskMapsByLod[lodIndex].find(morphTargetStandard); + if (weight > 0 && wrinkleMaskIter != m_morphTargetWrinkleMaskMapsByLod[lodIndex].end()) + { + // Add the wrinkle mask and weight, to be set on the material + m_wrinkleMasks.push_back(wrinkleMaskIter->second); + m_wrinkleMaskWeights.push_back(weight); + } } } } m_skinnedMeshRenderProxy->SetMorphTargetWeights(lodIndex, m_morphTargetWeights); + + // Until EMotionFX and Atom lods are synchronized [ATOM-13564] we don't know which EMotionFX lod to pull the weights from + // Until that is fixed, just use lod 0 [ATOM-15251] + if (lodIndex == 0) + { + UpdateWrinkleMasks(); + } } } } @@ -453,6 +477,8 @@ namespace AZ MaterialComponentRequestBus::EventResult(materials, m_entityId, &MaterialComponentRequests::GetMaterialOverrides); CreateRenderProxy(materials); + InitWrinkleMasks(); + TransformNotificationBus::Handler::BusConnect(m_entityId); MaterialComponentNotificationBus::Handler::BusConnect(m_entityId); MeshComponentRequestBus::Handler::BusConnect(m_entityId); @@ -573,5 +599,77 @@ namespace AZ { CreateSkinnedMeshInstance(); } + + void AtomActorInstance::InitWrinkleMasks() + { + EMotionFX::Actor* actor = m_actorAsset->GetActor(); + m_morphTargetWrinkleMaskMapsByLod.resize(m_skinnedMeshInputBuffers->GetLodCount()); + m_wrinkleMasks.reserve(s_maxActiveWrinkleMasks); + m_wrinkleMaskWeights.reserve(s_maxActiveWrinkleMasks); + + for (size_t lodIndex = 0; lodIndex < m_skinnedMeshInputBuffers->GetLodCount(); ++lodIndex) + { + EMotionFX::MorphSetup* morphSetup = actor->GetMorphSetup(lodIndex); + if (morphSetup) + { + const AZStd::vector& metaDatas = actor->GetMorphTargetMetaAsset()->GetMorphTargets(); + // Loop over all the EMotionFX morph targets + uint32_t numMorphTargets = morphSetup->GetNumMorphTargets(); + for (uint32_t morphTargetIndex = 0; morphTargetIndex < numMorphTargets; ++morphTargetIndex) + { + EMotionFX::MorphTargetStandard* morphTarget = static_cast(morphSetup->GetMorphTarget(morphTargetIndex)); + for (const RPI::MorphTargetMetaAsset::MorphTarget& metaData : metaDatas) + { + // Find the metaData associated with this morph target + if (metaData.m_morphTargetName == morphTarget->GetNameString() && metaData.m_wrinkleMask && metaData.m_numVertices > 0) + { + // If the metaData has a wrinkle mask, add it to the map + Data::Instance streamingImage = RPI::StreamingImage::FindOrCreate(metaData.m_wrinkleMask); + if (streamingImage) + { + m_morphTargetWrinkleMaskMapsByLod[lodIndex][morphTarget] = streamingImage; + } + } + } + } + } + } + } + + void AtomActorInstance::UpdateWrinkleMasks() + { + if (m_meshHandle) + { + Data::Instance wrinkleMaskObjectSrg = m_meshFeatureProcessor->GetObjectSrg(*m_meshHandle); + if (wrinkleMaskObjectSrg) + { + RHI::ShaderInputImageIndex wrinkleMasksIndex = wrinkleMaskObjectSrg->FindShaderInputImageIndex(Name{ "m_wrinkle_masks" }); + RHI::ShaderInputConstantIndex wrinkleMaskWeightsIndex = wrinkleMaskObjectSrg->FindShaderInputConstantIndex(Name{ "m_wrinkle_mask_weights" }); + RHI::ShaderInputConstantIndex wrinkleMaskCountIndex = wrinkleMaskObjectSrg->FindShaderInputConstantIndex(Name{ "m_wrinkle_mask_count" }); + if (wrinkleMasksIndex.IsValid() || wrinkleMaskWeightsIndex.IsValid() || wrinkleMaskCountIndex.IsValid()) + { + AZ_Error("AtomActorInstance", wrinkleMasksIndex.IsValid(), "m_wrinkle_masks not found on the ObjectSrg, but m_wrinkle_mask_weights and/or m_wrinkle_mask_count are being used."); + AZ_Error("AtomActorInstance", wrinkleMaskWeightsIndex.IsValid(), "m_wrinkle_mask_weights not found on the ObjectSrg, but m_wrinkle_masks and/or m_wrinkle_mask_count are being used."); + AZ_Error("AtomActorInstance", wrinkleMaskCountIndex.IsValid(), "m_wrinkle_mask_count not found on the ObjectSrg, but m_wrinkle_mask_weights and/or m_wrinkle_masks are being used."); + + if (m_wrinkleMasks.size()) + { + wrinkleMaskObjectSrg->SetImageArray(wrinkleMasksIndex, AZStd::array_view>(m_wrinkleMasks.data(), m_wrinkleMasks.size())); + + // Set the weights for any active masks + for (size_t i = 0; i < m_wrinkleMaskWeights.size(); ++i) + { + wrinkleMaskObjectSrg->SetConstant(wrinkleMaskWeightsIndex, m_wrinkleMaskWeights[i], i); + } + AZ_Error("AtomActorInstance", m_wrinkleMaskWeights.size() <= s_maxActiveWrinkleMasks, "The skinning shader supports no more than %d active morph targets with wrinkle masks.", s_maxActiveWrinkleMasks); + } + + wrinkleMaskObjectSrg->SetConstant(wrinkleMaskCountIndex, aznumeric_cast(m_wrinkleMasks.size())); + m_meshFeatureProcessor->QueueObjectSrgForCompile(*m_meshHandle); + } + } + } + } + } //namespace Render } // namespace AZ diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h index 1002fcbde1..e05280e896 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h @@ -17,6 +17,7 @@ #include #include +#include #include @@ -29,6 +30,8 @@ #include #include #include +#include + #include #include @@ -41,6 +44,7 @@ namespace AZ::RPI { class Model; class Buffer; + class StreamingImage; } namespace AZ @@ -168,6 +172,11 @@ namespace AZ // SkinnedMeshOutputStreamNotificationBus void OnSkinnedMeshOutputStreamMemoryAvailable() override; + // Check to see if the skin material is being used, + // and if there are blend shapes with wrinkle masks that should be applied to it + void InitWrinkleMasks(); + void UpdateWrinkleMasks(); + AZStd::intrusive_ptr m_skinnedMeshInputBuffers = nullptr; AZStd::intrusive_ptr m_skinnedMeshInstance; AZ::Data::Instance m_boneTransforms = nullptr; @@ -179,6 +188,12 @@ namespace AZ AZ::TransformInterface* m_transformInterface = nullptr; AZStd::set m_waitForMaterialLoadIds; AZStd::vector m_morphTargetWeights; + + typedef AZStd::unordered_map> MorphTargetWrinkleMaskMap; + AZStd::vector m_morphTargetWrinkleMaskMapsByLod; + + AZStd::vector> m_wrinkleMasks; + AZStd::vector m_wrinkleMaskWeights; }; } // namespace Render diff --git a/Gems/PhysX/Code/Source/System/PhysXSystem.cpp b/Gems/PhysX/Code/Source/System/PhysXSystem.cpp index 1622d04aae..8df9e9a86f 100644 --- a/Gems/PhysX/Code/Source/System/PhysXSystem.cpp +++ b/Gems/PhysX/Code/Source/System/PhysXSystem.cpp @@ -21,10 +21,25 @@ #include +// only enable physx timestep warning when not running debug or in Release +#if !defined(DEBUG) && !defined(RELEASE) +#define ENABLE_PHYSX_TIMESTEP_WARNING +#endif + namespace PhysX { AZ_CLASS_ALLOCATOR_IMPL(PhysXSystem, AZ::SystemAllocator, 0); +#ifdef ENABLE_PHYSX_TIMESTEP_WARNING + namespace FrameTimeWarning + { + static constexpr int MaxSamples = 1000; + static int NumSamples = 0; + static int NumSamplesOverLimit = 0; + static float LostTime = 0.0f; + } +#endif + PhysXSystem::MaterialLibraryAssetHelper::MaterialLibraryAssetHelper(PhysXSystem* physXSystem) : m_physXSystem(physXSystem) { @@ -140,9 +155,26 @@ namespace PhysX } }; - AZ_Warning("PhysXSystem", deltaTime <= m_systemConfig.m_maxTimestep, - "Frame delta time of [%.6f seconds] exceeds Physics max frame timestep, physics timestep will be clamped to [%.6f seconds].", - deltaTime, m_systemConfig.m_maxTimestep); +#ifdef ENABLE_PHYSX_TIMESTEP_WARNING + if (FrameTimeWarning::NumSamples < FrameTimeWarning::MaxSamples) + { + FrameTimeWarning::NumSamples++; + if (deltaTime > m_systemConfig.m_maxTimestep) + { + FrameTimeWarning::NumSamplesOverLimit++; + FrameTimeWarning::LostTime += deltaTime - m_systemConfig.m_maxTimestep; + } + } + else + { + AZ_Warning("PhysXSystem", FrameTimeWarning::NumSamplesOverLimit <= 0, + "[%d] of [%d] frames had a deltatime over the Max physics timestep[%.6f]. Physx timestep was clamped on those frames, losing [%.6f] seconds.", + FrameTimeWarning::NumSamplesOverLimit, FrameTimeWarning::NumSamples, m_systemConfig.m_maxTimestep, FrameTimeWarning::LostTime); + FrameTimeWarning::NumSamples = 0; + FrameTimeWarning::NumSamplesOverLimit = 0; + FrameTimeWarning::LostTime = 0.0f; + } +#endif deltaTime = AZ::GetClamp(deltaTime, 0.0f, m_systemConfig.m_maxTimestep); AZ_Assert(m_systemConfig.m_fixedTimestep >= 0.0f, "PhysXSystem - fixed timestep is negitive."); diff --git a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp index 592a8e2a75..45908eb2d5 100644 --- a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp +++ b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp @@ -135,7 +135,7 @@ namespace AZ::SceneGenerationComponents { for (size_t controlPointIndex = 0; controlPointIndex < skinData.get().GetVertexCount(); ++controlPointIndex) { - const int usedPointIndex = meshData->GetUsedPointIndexForControlPoint(aznumeric_caster(controlPointIndex)); + const int usedPointIndex = meshData->GetUsedPointIndexForControlPoint(meshData->GetControlPointIndex(aznumeric_caster(controlPointIndex))); const size_t linkCount = skinData.get().GetLinkCount(controlPointIndex); if (usedPointIndex < 0 || linkCount == 0) diff --git a/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.cpp b/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.cpp index e71a5207d0..25faca3667 100644 --- a/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.cpp +++ b/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.cpp @@ -72,6 +72,11 @@ namespace SceneBuilder m_sceneBuilder.BusDisconnect(); } + void BuilderPluginComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.emplace_back(AZ_CRC_CE("AssetImportRequestHandler")); + } + void BuilderPluginComponent::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serializeContext = azrtti_cast(context); @@ -81,5 +86,4 @@ namespace SceneBuilder ->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector({ AssetBuilderSDK::ComponentTags::AssetBuilder })); } } - } // namespace SceneBuilder diff --git a/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.h b/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.h index c1fc6ebb36..aed5e1b026 100644 --- a/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.h +++ b/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.h @@ -32,6 +32,8 @@ namespace SceneBuilder void Activate() override; void Deactivate() override; + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + private: SceneBuilderWorker m_sceneBuilder; }; diff --git a/Registry/sceneassetimporter.setreg b/Registry/sceneassetimporter.setreg new file mode 100644 index 0000000000..bd7c4d0705 --- /dev/null +++ b/Registry/sceneassetimporter.setreg @@ -0,0 +1,16 @@ +{ + "O3DE": + { + "SceneAPI": + { + "AssetImporter": + { + "SupportedFileTypeExtensions": + [ + ".fbx", + ".stl" + ] + } + } + } +} \ No newline at end of file diff --git a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_1000.dds b/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_1000.dds deleted file mode 100644 index 872d7b71d4..0000000000 --- a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_1000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f454e6505870d9159eaac1eb0c53751e45e803cf50bf94e5c5f51ba2232cebba -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_2000.dds b/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_2000.dds deleted file mode 100644 index 97ed5efe4d..0000000000 --- a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_2000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c1047fad9be53568fc471bdb5633445a030efaed6bf9b5e9d47abb09efb4d01e -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_3000.dds b/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_3000.dds deleted file mode 100644 index 337e63a40c..0000000000 --- a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_3000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11b5326877643b06a5687cf1470e388752296df64ce3abb69aa06f1933e0f3b8 -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_4000.dds b/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_4000.dds deleted file mode 100644 index 2e6b3a3eda..0000000000 --- a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_4000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:de54072f3eca1a1de6250b1585335a1aa6fa9e07e4e7ad00cd37ea5f809a303c -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_5000.dds b/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_5000.dds deleted file mode 100644 index 4a80ce04e1..0000000000 --- a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_5000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ac0f98198af41590052eff6550d34f05d0e3ca374bc59e7ece314be2380c210f -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_6000.dds b/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_6000.dds deleted file mode 100644 index d7e0e74fca..0000000000 --- a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_6000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:be47d7c0a2a64b17925e51b65abba72dd22735ef7f3913e9aa389f8c31124ede -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_7000.dds b/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_7000.dds deleted file mode 100644 index 391bd3ec9f..0000000000 --- a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_7000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:372359625eec486abbbe7f9ab438b51627939bfbd39002d136b6d6b9c61bbe1b -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_8000.dds b/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_8000.dds deleted file mode 100644 index 418a7ee3ed..0000000000 --- a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_8000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:91ba2bd2504a10a199964b35014df14a4405cf8fd47955a6c4ef9ca4f300637d -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_9000.dds b/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_9000.dds deleted file mode 100644 index 9c11c2fa43..0000000000 --- a/Tests/Atom/GoldenImages/Windows/amd/Baseviewer/BistroBenchmark/screenshot_bistro_9000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7d8442967964bab77d02a572e6e7f8fcd158e62308b7e6340d4d56be13f7f455 -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_1000.dds b/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_1000.dds deleted file mode 100644 index f9a46c53c8..0000000000 --- a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_1000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5bf373730d725a14b6833b7862c6268a93d8f9d847032a60cdc99ce14fea9dfe -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_2000.dds b/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_2000.dds deleted file mode 100644 index 25cce25c63..0000000000 --- a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_2000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:181c31eb7fa068027d3e42c415006fd70d1fbeb0bfe373b3290aea6e3002d762 -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_3000.dds b/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_3000.dds deleted file mode 100644 index a1c5d4e1b8..0000000000 --- a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_3000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:efc4fbc90bfd01a1ba09ebd925b8379e67263b7156f37473dd13a91f346ed1c4 -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_4000.dds b/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_4000.dds deleted file mode 100644 index 4cafe4e613..0000000000 --- a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_4000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b48137886ef2a38312eedbddb111367b3f3924ab5425b49f81a92ae7d4ea898e -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_5000.dds b/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_5000.dds deleted file mode 100644 index bb74f06212..0000000000 --- a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_5000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ea47fd8d68dab6ba54e45bfc50d84d380ec2f5b7e6915713dc9e9aa41423d4ea -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_6000.dds b/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_6000.dds deleted file mode 100644 index 8bc6e3de17..0000000000 --- a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_6000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9bd3fe0208cbee26cfcf00cba0a127c5938061b8bea2143d05e28fbc7f55d9ca -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_7000.dds b/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_7000.dds deleted file mode 100644 index 4c286062d0..0000000000 --- a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_7000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:30f6aac28e74da435ad6c018b6a7e9b7cee1ad74ec5452778e56036a7d438ec9 -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_8000.dds b/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_8000.dds deleted file mode 100644 index 31023d437e..0000000000 --- a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_8000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2628992a8c8774ff5e77152999009ba3cbb3b68ee79d53669dd16aebe978da27 -size 5946320 diff --git a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_9000.dds b/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_9000.dds deleted file mode 100644 index a5f85d6b22..0000000000 --- a/Tests/Atom/GoldenImages/Windows/nvidia/Baseviewer/BistroBenchmark/screenshot_bistro_9000.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:42faae0235f0c74bc0201dd8fc805a9c01f086b093026b654e207f83ea0f3f90 -size 5946320 diff --git a/Tests/Atom/__init__.py b/Tests/Atom/__init__.py deleted file mode 100755 index 36d43bea05..0000000000 --- a/Tests/Atom/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# """ -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. - -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/Tests/Atom/image_comparison_utils.py b/Tests/Atom/image_comparison_utils.py deleted file mode 100755 index 698901710a..0000000000 --- a/Tests/Atom/image_comparison_utils.py +++ /dev/null @@ -1,105 +0,0 @@ -# """ -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. - -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -# Utility functions for image comparison tests. -# """ -# import test_tools.shared.images.qssim as qssim -# import PythonMagick -# import logging -# import os -# import shared.s3_utils -# import platform - - -#################################### -# Commented out due to need to shift to new LyTestTools, Python3 and new screenshot workflow -# Don't merge to Mainline -#################################### - - -# def create_image_path(screenshot, path, extension, golden): - # """ - # Create image path from name, path and extension - # From a specified path, create the path for the diff image - # :param screenshot: path to the screenshot which needs to be saved - # :param path: path to where screenshot should be saved - # :param extension: extension of the diff image (.dds, .jpg) - # :param golden: True or False whether screenshot is golden image or not - # :return diff_full_path: path of the iamge to be saved - # """ - # screenshot_name = os.path.basename(screenshot) - # if golden: - # diff_name = "{}_golden{}".format(screenshot_name.split('.')[:-1][0], extension) - # else: - # diff_name = "{}{}".format(screenshot_name.split('.')[:-1][0], extension) - # diff_full_path = os.path.join(path, diff_name) - # return diff_full_path - - -# def convert_dds_to_jpg(image, path, golden): - # """ - # Convert DDS to JPEG - # :param image: DDS image to convert - # :param path: path to where iamge will be saved - # :return screenshotJPG_path: path to the newly JPEG-converted DDS image - # """ - # # Convert image as JPEG for quick review - # screenshotJPG_path = create_image_path(image, path, '.jpg', golden) - # screenshot = PythonMagick.Image(image) - # screenshot.quality(100) - # screenshot.magick('JPEG') - # screenshot.write(screenshotJPG_path) - # return screenshotJPG_path - - -# def compare_screenshot_to_golden_image(screenshot, golden_image, path, threshold=0.985): - # """ - # Compare Screenshots to Golden Images - # Function to compare a newly taken screenshot with the golden image - # :param screenshot: path of the screenshot - # :param golden_image: path of the golden image (in Perforce) - # :param path: path to where the screenshot diff image will be saved - # :param threshold: threshold for the image comparison test to fail/pass (optional) - # :return failure_not_found: True or False whether screenshots are similar (due to threshold) or not - # """ - # failure_not_found = True - # logging.info("Comparing screenshot {}".format(screenshot)) - # # Calculating screenshots similarity - # quaternion_similarity = qssim.qssim(screenshot, golden_image, diff_path = path) - # # Converting original screenshots to jpg - # convert_dds_to_jpg(screenshot, path, False) - # convert_dds_to_jpg(golden_image, path, True) - # # Checking if similarity index is bypassing the threshold - # if (quaternion_similarity < threshold): - # failure_not_found = False - # logging.error("%s failed the image comparison with %s", screenshot, golden_image) - # else: - # logging.info("Comparison successful, screenshots are similar.") - # return failure_not_found - - -# def upload_screenshots_to_s3(folder_path, folder_name): - # """ - # Uploading screenshots to certain s3 bucket - # Will require certain credentials (from the IAM that has access to l-qa@amazon acc) on the machine to work - # :param folder_path: full path to folder that needs to be uploaded to s3 - # :param folder_name: name of the folder to be uploaded to s3 - # :return: None - # """ - # host_name = platform.uname()[1] - # s3_folder_name = '_'.join([folder_name, host_name]) - - # logging.info("Trying to create a folder on S3; bucket: ly.screenshot.automation.artifacts, folder: {}".format(s3_folder_name)) - # shared.s3_utils.create_folder_in_bucket('ly.screenshot.automation.artifacts', s3_folder_name) - - # for file in os.listdir(folder_path): - # key = '{}/{}'.format(s3_folder_name, file) - # shared.s3_utils.upload_to_bucket('ly.screenshot.automation.artifacts', '{}/{}'.format(folder_path, file), key) - diff --git a/Tests/Atom/windows/__init__.py b/Tests/Atom/windows/__init__.py deleted file mode 100755 index 36d43bea05..0000000000 --- a/Tests/Atom/windows/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# """ -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. - -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/Tests/Atom/windows/atomsampleviewer_tests_stability.py b/Tests/Atom/windows/atomsampleviewer_tests_stability.py deleted file mode 100755 index b480a7bc1a..0000000000 --- a/Tests/Atom/windows/atomsampleviewer_tests_stability.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -This is a file to test basic functionality of the Base Viewer executable -""" - -import os -import pytest -import subprocess -import time -import re -from ly_test_tools.environment.process_utils import kill_processes_named as kill_processes_named - -dev_dir = os.path.abspath(os.path.join(os.path.abspath(__file__), '..', '..', '..', '..')) -bin_dir = 'Bin64vc141' - - -def gather_sample_names(): - """ - Gathers the currently eligible samples from the output of a single run of baseviewer.exe (with no sample argument). - For use in the fixture parameters. - """ - viewer_dir = os.path.join(dev_dir, bin_dir) - os.chdir(viewer_dir) - process = subprocess.Popen(['BaseViewer.exe', '-timeout', '5'], stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - out = process.communicate()[0] - log = out.splitlines() - samples = [] - for line in log: - line = str(line) - if "SampleComponentManager" in line and '-' not in line and 'Not Supported' not in line: - line_regexp = re.search('\[.*\]', line) - line = line_regexp.group(0) - sample = line.replace('[', '').replace(']', '') - samples.append(sample) - kill_processes_named('AssetProcessor', ignore_extensions=True) - if samples is not None: - return samples - - -@pytest.fixture -def kill_AP(request): - def teardown(): - kill_processes_named('AssetProcessor', ignore_extensions=True) - request.addfinalizer(teardown) - - -@pytest.mark.parametrize("samples", gather_sample_names()) -class TestBaseViewerExe(object): - - def test_OpenSampleLevel_CorrectFormat_ShouldPass(self, samples, kill_AP): - """ - Opens the specific BaseViewer samples individually and verifies they're stable for a few seconds and then exit - cleanly - """ - viewer_dir = os.path.join(dev_dir, bin_dir) - os.chdir(viewer_dir) - return_code = subprocess.check_call(['BaseViewer.exe', '-sample', samples, '-timeout', '20'], timeout=30) - assert return_code == 0, "Sample '{}' did not exit properly with code '{}'".format(samples, str(returncode)) - - def test_OpenSampleLevel_NoErrors_ShouldPass(self, samples, kill_AP): - """ - Opens the specific BaseViewer samples individually and verifies there are no errors in the output while running - """ - viewer_dir = os.path.join(dev_dir, bin_dir) - os.chdir(viewer_dir) - output = subprocess.check_output(['BaseViewer.exe', '-sample', samples, '-timeout', '20'], timeout=30) - log = output.splitlines() - errors = [] - assertions = [] - for i in range(len(log)): - line = str(log[i]) - surrounding_lines = str(log[i:i+3]) - if "Trace::Error" in line: - errors.append(surrounding_lines) - if "Trace::Assert" in line: - assertions.append(surrounding_lines) - - assert len(errors) == 0, "Sample '{}' had the following errors when run: {}".format(samples, "\n".join(errors)) - assert len(assertions) == 0, "Sample '{}' had the following assertions when run: {}".format(samples, "\n".join(assertions)) - diff --git a/Tests/Atom/windows/conftest.py b/Tests/Atom/windows/conftest.py deleted file mode 100755 index e0766a4015..0000000000 --- a/Tests/Atom/windows/conftest.py +++ /dev/null @@ -1,39 +0,0 @@ -# """ -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. - -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -# Conftest file for providing additional configuration for Screenshot Comparison tests -# """ - - - -#################################### -# Commented out due to need to shift to new LyTestTools, Python3 and new screenshot workflow -# Don't merge to Mainline -#################################### - - -# import pytest - -# def pytest_addoption(parser): - # parser.addoption( - # '--graphics_vendor', action='store', help='graphics vendor name: nvidia or amd', required=True - # ) - # parser.addoption( - # '--upload_results_to_s3', action='store_true', default=False, help='Specify if you need to upload screenshot artifacts to s3' - # ) - -# @pytest.fixture -# def graphics_vendor(request): - # return request.config.getoption('--graphics_vendor') - -# @pytest.fixture -# def upload_results_to_s3(request): - # return request.config.getoption('--upload_results_to_s3') - diff --git a/Tests/Atom/windows/screenshot_comparison_atomsampleviewer_windows.py b/Tests/Atom/windows/screenshot_comparison_atomsampleviewer_windows.py deleted file mode 100755 index fca50c9901..0000000000 --- a/Tests/Atom/windows/screenshot_comparison_atomsampleviewer_windows.py +++ /dev/null @@ -1,143 +0,0 @@ -# """ -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. - -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -# BaseViewer image comparison tests on windows -# """ - -# import Atom.image_comparison_utils as image_comparison_utils -# import test_tools.builtin.fixtures as fixtures -# import subprocess -# import pytest -# import os -# import logging -# import time -# import datetime -# from test_tools import WINDOWS -# from test_tools.shared.process_utils import kill_processes_named -# from test_tools.shared.waiter import wait_for - - - - -#################################### -# Commented out due to need to shift to new LyTestTools, Python3 and new screenshot workflow -# Don't merge to Mainline -#################################### - - - - -# workspace = fixtures.use_fixture(fixtures.builtin_workspace_fixture, scope='function') -# logger = logging.getLogger(__name__) - -# @pytest.fixture(scope='session', autouse=True) -# def closing_ap(request): - # """ - # Fixture to call once per all tests to teardown AP at the end - # :param request: pytest request - # """ - # def teardown(): - # kill_processes_named('AssetProcessor_tmp', ignore_extensions=True) - # kill_processes_named('AssetProcessor', ignore_extensions=True) - # kill_processes_named('AssetProcessorBatch', ignore_extensions=True) - # kill_processes_named('AssetBuilder', ignore_extensions=True) - # kill_processes_named('rc', ignore_extensions=True) - # request.addfinalizer(teardown) - -# @pytest.fixture() -# def screenshots_setup(request, workspace, sample, graphics_vendor, upload_results_to_s3): - # """ - # Fixture for setting up workspace needed for screenshot comparison test - # :param request: pytest request - # :param workspace: pythontesttools workspace object - # :param sample: name of BaseViwer sample - # :return final_path: path to folder where output screenshots will be stored - # """ - # # Creating output folder - # tests_path = os.path.dirname(os.path.realpath(__file__)) - # dir_name = "{}_screenshot_tests_{}_{}_{}_{}".format(datetime.datetime.now().strftime("%Y-%m-%d_%H_%M_%S_%f"), sample.replace('/', ""), workspace.release.platform, workspace.release.configuration, - # graphics_vendor) - # dir_name = dir_name.replace(":", '_') - # final_path = os.path.join(tests_path, dir_name) - # os.mkdir(final_path) - - # # Teardown to clean up Cache from .dds screenshots that are produced by BaseViewer.exe - # def teardown(): - # cache = os.path.join(workspace.release.paths.dev(), "Cache", "BaseViewer", "pc", "baseviewer") - # files = os.listdir(cache) - # for file in files: - # name, file_extension = os.path.splitext(file) - # if 'screenshot' in name and file_extension == '.dds': - # screen_to_remove = os.path.join(cache, file) - # logger.info('Deleting temp screenshot file {}.'.format(screen_to_remove)) - # os.remove(screen_to_remove) - # kill_processes_named('BaseViewer', ignore_extensions=True) - # # uploading screenshots to s3 - # if upload_results_to_s3: - # image_comparison_utils.upload_screenshots_to_s3(final_path, dir_name) - # request.addfinalizer(teardown) - # return final_path - - -# # Commenting out debug due to ATOM-1677 -# @pytest.mark.parametrize("platform,configuration,project,spec,sample", [ - # pytest.param("win_x64_vs2017", "profile", "BaseViewer", "all", "RPI/BistroBenchmark", - # marks=pytest.mark.skipif(not WINDOWS, reason="Only supported on Windows hosts")), - # #pytest.param("win_x64_vs2017", "debug", "BaseViewer", "all", "RPI/BistroBenchmark", - # # marks=pytest.mark.skipif(not WINDOWS, reason="Only supported on Windows hosts")), - # ]) -# class TestBaseViewerScreenshots(object): - # def test_BistroBenchmarkSample_CompareScreenshots(self, request, workspace, sample, screenshots_setup, graphics_vendor): - # """ - # Launches BaseViewer.exe RPI/BistoBenchmark, taking screenshot and comparing on certain frames. - # """ - # base_path = workspace.release.paths.dev() - # # Generating frames list parameter - # frames_list = range(1000,10000,1000) - # frames_param = "" - # screenshot_names = [] - # for parameter in frames_list: - # frames_param += '{},'.format(parameter) - # screenshot_names.append('screenshot_bistro_{}.dds'.format(parameter)) - # frames_param = frames_param[:-1] - # # Loading BaseViewer - # self.load_baseviewer_directly(workspace, sample, frames_param, timeout=100) - - # logger.info('Comparing screenshots to golden images') - # taken_screens_path = os.path.join(base_path, "Cache", "BaseViewer", "pc", "baseviewer") - # golden_screens_path = os.path.join(base_path, "Tests", "Atom", "GoldenImages", "Windows", graphics_vendor, "Baseviewer", "BistroBenchmark") - # failed_screenshots = [] - # for screen in screenshot_names: - # taken_image = os.path.join(taken_screens_path, screen) - # golden_image = os.path.join(golden_screens_path, screen) - # if not image_comparison_utils.compare_screenshot_to_golden_image(taken_image, golden_image, screenshots_setup): - # failed_screenshots.append(screen) - # if len(failed_screenshots) > 0: - # assert False, "A failure has been found during image comparison for the following images: {}".format(failed_screenshots) - - - # def load_baseviewer_directly(self, workspace, sample, frames, timeout): - # """ - # Launch directly Baseviewer without using the launcher (since Atom is not yet integrated in Lumberyard) - # :param workspace: pythontesttools workspace object - # :param sample: name of the sample from BaseViewer - # :param frames: list of frames to take screenshots at - # :param timeout: time in seconds to wait BaseViewer to take screenshots - # """ - # base_path = workspace.release.paths.dev() - # bin_dir = workspace.release.paths.bin_dir() - # cmd_path = os.path.join(base_path, bin_dir) - # os.chdir(cmd_path) - # p = subprocess.Popen(['BaseViewer.exe', '-sample', sample, '-screenshot', frames]) - # # Wait for BaseViewer to run and take screenshot - # last_screenshot = frames.split(',')[-1] - # screenshot_file = os.path.join(base_path, "Cache", "BaseViewer", "pc", "baseviewer", "screenshot_bistro_{}.dds".format(last_screenshot)) - # wait_for(lambda: os.path.exists(screenshot_file), timeout) - diff --git a/Tests/BuildSystems/test_BuildBAT.py b/Tests/BuildSystems/test_BuildBAT.py deleted file mode 100755 index 1521508550..0000000000 --- a/Tests/BuildSystems/test_BuildBAT.py +++ /dev/null @@ -1,212 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -BuildSystems BAT to automate building on packages for Windows -""" -import logging -import pytest -import os - -pytest.importorskip('ly_test_tools') - -import ly_test_tools.builtin.helpers as helpers -from .test_lib import build_helper - -logger = logging.getLogger(__name__) - - -@pytest.mark.BAT -@pytest.mark.parametrize('spec', ['all']) -@pytest.mark.parametrize('project', ['AutomatedTesting']) - -class TestWindowsBuildConfig(object): - """ - Automated tests for all the build configurations for Windows. - Test cases live in Repository/Build System/Lumberyard Builds/Configurations - """ - @pytest.mark.test_case_id('C15723869') - @pytest.mark.parametrize('platform', ['win_x64_vs2017']) - @pytest.mark.parametrize('configuration', ['profile']) - @pytest.mark.build - def test_build_win_x64_vs2017_profile(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15723870') - @pytest.mark.parametrize('platform', ['win_x64_vs2017']) - @pytest.mark.parametrize('configuration', ['profile_dedicated']) - @pytest.mark.build - def test_build_win_x64_vs2017_profile_dedicated(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15723871') - @pytest.mark.parametrize('platform', ['win_x64_vs2017']) - @pytest.mark.parametrize('configuration', ['profile_test']) - @pytest.mark.build - def test_build_win_x64_vs2017_profile_test(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15723872') - @pytest.mark.parametrize('platform', ['win_x64_vs2017']) - @pytest.mark.parametrize('configuration', ['profile_test_dedicated']) - @pytest.mark.build - def test_build_win_x64_vs2017_profile_test_dedicated(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15716369') - @pytest.mark.parametrize('platform', ['win_x64_vs2017']) - @pytest.mark.parametrize('configuration', ['debug']) - @pytest.mark.build - def test_build_win_x64_vs2017_debug(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15716370') - @pytest.mark.parametrize('platform', ['win_x64_vs2017']) - @pytest.mark.parametrize('configuration', ['debug_dedicated']) - @pytest.mark.build - def test_build_win_x64_vs2017_debug_dedicated(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15716371') - @pytest.mark.parametrize('platform', ['win_x64_vs2019']) - @pytest.mark.parametrize('configuration', ['debug_test']) - @pytest.mark.build - def test_build_win_x64_vs2019_debug_test(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15716372') - @pytest.mark.parametrize('platform', ['win_x64_vs2017']) - @pytest.mark.parametrize('configuration', ['debug_test_dedicated']) - @pytest.mark.build - def test_build_win_x64_vs2017_debug_test_dedicated(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15723889') - @pytest.mark.parametrize('platform', ['win_x64_vs2017']) - @pytest.mark.parametrize('configuration', ['release']) - @pytest.mark.build - def test_build_win_x64_vs2017_release(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15723890') - @pytest.mark.parametrize('platform', ['win_x64_vs2017']) - @pytest.mark.parametrize('configuration', ['release_dedicated']) - @pytest.mark.build - def test_build_win_x64_vs2017_release_dedicated(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15815180') - @pytest.mark.parametrize('platform', ['win_x64_vs2019']) - @pytest.mark.parametrize('configuration', ['profile']) - @pytest.mark.build - def test_build_win_x64_vs2019_profile(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15815181') - @pytest.mark.parametrize('platform', ['win_x64_vs2019']) - @pytest.mark.parametrize('configuration', ['profile_dedicated']) - @pytest.mark.build - def test_build_win_x64_vs2019_profile_dedicated(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15815182') - @pytest.mark.parametrize('platform', ['win_x64_vs2019']) - @pytest.mark.parametrize('configuration', ['profile_test']) - @pytest.mark.build - def test_build_win_x64_vs2019_profile_test(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15815183') - @pytest.mark.parametrize('platform', ['win_x64_vs2019']) - @pytest.mark.parametrize('configuration', ['profile_test_dedicated']) - @pytest.mark.build - def test_build_win_x64_vs2019_profile_test_dedicated(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15815174') - @pytest.mark.parametrize('platform', ['win_x64_vs2019']) - @pytest.mark.parametrize('configuration', ['debug']) - @pytest.mark.build - def test_build_win_x64_vs2019_debug(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15815175') - @pytest.mark.parametrize('platform', ['win_x64_vs2019']) - @pytest.mark.parametrize('configuration', ['debug_dedicated']) - @pytest.mark.build - def test_build_win_x64_vs2019_debug_dedicated(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15815176') - @pytest.mark.parametrize('platform', ['win_x64_vs2019']) - @pytest.mark.parametrize('configuration', ['debug_test']) - @pytest.mark.build - def test_build_win_x64_vs2019_debug_test(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15815177') - @pytest.mark.parametrize('platform', ['win_x64_vs2019']) - @pytest.mark.parametrize('configuration', ['debug_test_dedicated']) - @pytest.mark.build - def test_build_win_x64_vs2019_debug_test_dedicated(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15815190') - @pytest.mark.parametrize('platform', ['win_x64_vs2019']) - @pytest.mark.parametrize('configuration', ['release']) - @pytest.mark.build - def test_build_win_x64_vs2019_release(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) - - @pytest.mark.test_case_id('C15815191') - @pytest.mark.parametrize('platform', ['win_x64_vs2019']) - @pytest.mark.parametrize('configuration', ['release_dedicated']) - @pytest.mark.build - def test_build_win_x64_vs2019_release_dedicated(self, workspace, platform, configuration, project, spec): - workspace.build() - build_log = os.path.join(workspace.artifact_manager.dest_path, 'waf_build.log') - assert build_helper.verify_build_log(build_log, platform, configuration) \ No newline at end of file diff --git a/Tests/BuildSystems/test_lib/__init__.py b/Tests/BuildSystems/test_lib/__init__.py deleted file mode 100755 index 6ed3dc4bda..0000000000 --- a/Tests/BuildSystems/test_lib/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" \ No newline at end of file diff --git a/Tests/BuildSystems/test_lib/build_helper.py b/Tests/BuildSystems/test_lib/build_helper.py deleted file mode 100755 index c2213d139b..0000000000 --- a/Tests/BuildSystems/test_lib/build_helper.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Helper functions for build systems -""" -import os -import logging - -logger = logging.getLogger(__name__) - - -def verify_build_log(build_log_path, platform, configuration): - """ - This will search the log file for an expected success message for a specific platform configuration. - :param build_log_path: the full path to the log file. e.g. \\dev\TestResults\timestamp_folder\pytest_results\... - :param platform: the compiler to use, e.g. "win_x64_vs2017" - :param configuration: the flavor of the build, e.g. "profile" - :return: True, if success message is found within the build log. False, if success message is not found and raise an assertion error if the build log cannot be found. - """ - success_message = "[WAF] 'build_{0}_{1}' finished successfully".format(platform, configuration) - if os.path.exists(build_log_path): - with open(build_log_path) as build_file: - for line in build_file: - if success_message in line: - logger.info('Success message was found for {0}_{1}'.format(platform, configuration)) - return True - logger.info('Success message not found for {0}_{1}'.format(platform, configuration)) - return False - else: - logger.info('We cannot find the build log and this is the path we are looking for {0}'.format(build_log_path)) - raise AssertionError diff --git a/Tests/README.txt b/Tests/README.txt deleted file mode 100644 index 71598c7890..0000000000 --- a/Tests/README.txt +++ /dev/null @@ -1 +0,0 @@ -This folder contains integration tests which do not ship with Lumberyard. Tests that ship with the product can be found in folders adjacent to the code that they test. \ No newline at end of file diff --git a/Tests/__init__.py b/Tests/__init__.py deleted file mode 100755 index 6ed3dc4bda..0000000000 --- a/Tests/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" \ No newline at end of file diff --git a/Tests/ai/EditorScripts/LY_114727_NavigationComponent_MovementMethods.py b/Tests/ai/EditorScripts/LY_114727_NavigationComponent_MovementMethods.py deleted file mode 100755 index 75a13b8e17..0000000000 --- a/Tests/ai/EditorScripts/LY_114727_NavigationComponent_MovementMethods.py +++ /dev/null @@ -1,46 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# -''' -This script tests movement methods Transform, Physics and Custom -''' -import sys, os -import time -import azlmbr.legacy.general as general - -from tests_common import TestHelper - -class TestMovementMethods(TestHelper): - def __init__(self): - TestHelper.__init__(self, log_prefix = 'LY-114727', args=['level']) - - def run_test(self): - # Start by assuming we'll crash and fail - self.test_success = False - # Open the level non-interactively - level_opened = self.open_level(self.get_arg('level')) - if not level_opened: - return - - # Enter game mode, so that physics in the test level starts running. - general.enter_game_mode() - # Wait for game mode to start. (Not sure if this is necessary, just being extra-cautious) - while (general.is_in_game_mode() != True): - general.idle_wait(1.0) - # Wait for game mode to finish. Entities should be navigating - while (general.is_in_game_mode() == True): - general.idle_wait(2.0) - - # We finished and haven't crashed, so assume success and exit the Editor. - self.test_success = True - -test = TestMovementMethods() -test.run() - diff --git a/Tests/ai/EditorScripts/tests_common.py b/Tests/ai/EditorScripts/tests_common.py deleted file mode 100755 index 76ad183c60..0000000000 --- a/Tests/ai/EditorScripts/tests_common.py +++ /dev/null @@ -1,163 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -import sys, os -import azlmbr.legacy.general as general -import azlmbr.legacy.settings as settings - -class TestHelper: - def __init__(self, log_prefix, args = None): - self.log_prefix = log_prefix + ': ' - self.test_success = True - self.args = {} - if args: - # Get the level name and heightmap name from command-line args - if (len(sys.argv) == (len(args) + 1)): - for arg_index in range(len(args)): - self.args[args[arg_index]] = sys.argv[arg_index + 1] - else: - test_success = False - self.log('Expected command-line args: {}'.format(args)) - - - # Test Setup - # Set helpers - # Set viewport size - # Turn off display mode, antialiasing - # set log prefix, log test started - # TODO: Turn off user dialogs like Amazon login, surveys, etc... - def setup(self): - self.log('test started') - - def after_level_load(self): - # Enable the Editor to start running its idle loop. - # This is needed for Python scripts passed into the Editor startup. Since they're executed - # during the startup flow, they run before idle processing starts. Without this, the engine loop - # won't run during idle_wait, which will prevent our test level from working. - general.idle_enable(True) - - # Give everything a second to initialize - general.idle_wait(1.0) - - self.original_settings = settings.get_misc_editor_settings() - self.helpers_visible = general.is_helpers_shown() - self.viewport_size = general.get_viewport_size() - # Turn off the helper gizmos if visible - if (self.helpers_visible): - general.toggle_helpers() - general.idle_wait(1.0) - - # Set Editor viewport to a well-defined size - general.set_viewport_size(1280, 720) - general.idle_wait(1.0) - - # Turn off any display info like FPS, as that will mess up our image comparisons - # Turn off antialiasing as well - general.run_console("r_displayInfo=0") - general.run_console("r_antialiasingmode=0") - general.idle_wait(1.0) - - - - # Test Teardown - # Restore everything from above - # log test results, exit editor - def teardown(self): - # Restore the original Editor settings - settings.set_misc_editor_settings(self.original_settings) - # If the helper gizmos were on at the start, restore them - if (self.helpers_visible): - general.toggle_helpers() - # Set the viewport back to whatever size it was at the start - general.set_viewport_size(self.viewport_size.x, self.viewport_size.y) - general.idle_wait(1.0) - - self.log('test finished') - - if self.test_success == True: - self.log('result=SUCCESS') - general.set_result_to_success() - else: - self.log('result=FAILURE') - general.set_result_to_failure() - - general.exit_no_prompt() - - def run_test(self): - self.log('run') - - def run(self): - self.setup() - - # Only run the actual test if we didn't have setup issues - if self.test_success: - self.run_test() - - self.teardown() - - def get_arg(self, arg_name): - if arg_name in self.args: - return self.args[arg_name] - return '' - - - # general logger that adds prefix? - def log(self, log_line): - general.log(self.log_prefix + log_line) - - # isclose: Compares two floating-point values for "nearly-equal" - # From https://www.python.org/dev/peps/pep-0485/#proposed-implementation : - def isclose(self, a, b, rel_tol=1e-9, abs_tol=0.0): - return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) - - - # Create a new empty level - def create_level(self, level_name, heightmap_resolution, heightmap_meters_per_pixel, terrain_texture_resolution, use_terrain): - self.log('Creating level {}'.format(level_name)) - result = general.create_level_no_prompt(level_name, heightmap_resolution, heightmap_meters_per_pixel, terrain_texture_resolution, use_terrain) - - # Result codes are ECreateLevelResult defined in CryEdit.h - if (result == 1): - self.log('Temp level already exists') - elif (result == 2): - self.log('Failed to create directory') - elif (result == 3): - self.log('Directory length is too long') - elif (result != 0): - self.log('Unknown error, failed to create level') - else: - self.log('Level created successfully') - self.after_level_load() - - return (result == 0) - - def open_level(self, level_name): - # Open the level non-interactively - self.log('Opening level {}'.format(level_name)) - result = general.open_level_no_prompt(level_name) - self.after_level_load() - if result: - self.log('Level opened successfully') - else: - self.log('Unknown error, level failed to open') - - return result - - # Take Screenshot - def take_screenshot(self, posX, posY, posZ, rotX, rotY, rotZ): - # Set our camera position / rotation and wait for the Editor to acknowledge it - general.set_current_view_position(posX, posY, posZ) - general.set_current_view_rotation(rotX, rotY, rotZ) - general.idle_wait(1.0) - # Request a screenshot and wait for the Editor to process it - general.run_console("r_GetScreenShot=2") - general.idle_wait(1.0) - diff --git a/Tests/ai/LY_114727_NavigationComponent_test.py b/Tests/ai/LY_114727_NavigationComponent_test.py deleted file mode 100755 index 3eff39c787..0000000000 --- a/Tests/ai/LY_114727_NavigationComponent_test.py +++ /dev/null @@ -1,70 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - - -""" -ly102242: Runs the ly-102242 level in the Editor which reproduces the appropriate steps for -bug ly-102242 to crash when spawning an invalid touchbending asset. The touchbending asset -can be made invalid by including multiple meshes - one with skinning data and one without. -NOTE: In the bugfixed case, a crash will not occur, but touchbending will not occur and errors -will be printed to the console every time a new asset gets "touched" (spawned in the physics system). -""" -import pytest -pytest.importorskip('ly_test_tools') -import logging -import os - -from ..ly_shared import hydra_lytt_test_utils as hydra_utils - -logger = logging.getLogger(__name__) -test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts') -editor_timeout = 30 - -@pytest.mark.parametrize('platform', ['win_x64_vs2017']) -@pytest.mark.parametrize('configuration', ['profile']) -@pytest.mark.parametrize('project', ['AutomatedTesting']) -@pytest.mark.parametrize('spec', ['all']) -@pytest.mark.parametrize('level', ['AI/NavigationComponentTest']) -class TestNavigationComponent(object): - @pytest.fixture(autouse=True) - def setup_teardown(self, request): - def teardown(): - if hasattr(self, 'cfg_file_name'): - hydra_utils.cleanup_cfg_file(self.cfg_file_name) - - # Setup - add the teardown finalizer - request.addfinalizer(teardown) - - # entities with Transform, Physics and Custom movement methods navigate to the goal - def test_NavigationComponent(self, request, legacy_editor, level): - - cfg_args = [level] - - expected_lines = [ - "OnActivate NavigationAgentCustom", - "OnActivate NavigationAgentPhysics", - "OnActivate NavigationAgentTransform", - - "OnTraversalComplete NavigationAgentCustom", - "OnTraversalComplete NavigationAgentPhysics", - "OnTraversalComplete NavigationAgentTransform", - ] - - unexpected_lines = [ - "OnTraversalCanceled NavigationAgentCustom", - "OnTraversalCanceled NavigationAgentPhysics", - "OnTraversalCanceled NavigationAgentTransform", - ] - - hydra_utils.launch_and_validate_results(request, test_directory, legacy_editor, - 'LY_114727_NavigationComponent_MovementMethods.py', - expected_lines, unexpected_lines, timeout=editor_timeout, cfg_args=cfg_args) - diff --git a/Tests/ai/__init__.py b/Tests/ai/__init__.py deleted file mode 100755 index e912252f4e..0000000000 --- a/Tests/ai/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - - All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - its licensors. - - For complete copyright and license terms please see the LICENSE at the root of this - distribution (the "License"). All use of this software is governed by the License, - or, if provided, by the license below or the license accompanying this file. Do not - remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - diff --git a/Tests/demos/__init__.py b/Tests/demos/__init__.py deleted file mode 100755 index e912252f4e..0000000000 --- a/Tests/demos/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - - All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - its licensors. - - For complete copyright and license terms please see the LICENSE at the root of this - distribution (the "License"). All use of this software is governed by the License, - or, if provided, by the license below or the license accompanying this file. Do not - remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - diff --git a/Tests/demos/launcher_loading_tests.py b/Tests/demos/launcher_loading_tests.py deleted file mode 100755 index 6d23f56f1b..0000000000 --- a/Tests/demos/launcher_loading_tests.py +++ /dev/null @@ -1,183 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -""" -import pytest -import ly_test_tools - -import os -import shutil -import subprocess -from ly_test_tools.builtin.helpers import * -import tempfile -import time -# The following imports are used to detect capabilities in the current system - -from ly_test_tools.environment.process_utils import * -import Tests.shared.asset_processor_utils as aputil - -# Built-in fixture: provides a ready to use workspace. -from Tests.shared import substring -from ly_test_tools.environment.waiter import wait_for - -LAUNCHER_TIMEOUT = 120 - - -@pytest.mark.system -class TestProjectLauncher: - @pytest.fixture(autouse=True) - def setup_teardown(self, request, workspace, launcher): - path_to_cache = workspace.paths.asset_cache() - # Remove previous artifacts from cache - if os.path.isdir(os.path.join(path_to_cache, "user")): - shutil.rmtree(os.path.join(path_to_cache, "user")) - - user_cfg_path = os.path.join(workspace.paths.dev(), "user.cfg") - cfg_staging_path = None - - # Backup user.cfg if one exists - if os.path.exists(user_cfg_path): - cfg_staging_path = os.path.join(tempfile.gettempdir(), "user.cfg") - shutil.move(user_cfg_path, cfg_staging_path) - - # Configure the headless client - with open(user_cfg_path, "w") as user_cfg: - user_cfg.write("r_driver=NULL\n") - user_cfg.write("sys_audio_disable=1\n") - user_cfg.write("sys_skip_input=1\n") - - def teardown(): - launcher.kill() - - aputil.kill_asset_processor() - - # Restore previous user.cfg if one existed and unconfigure the headless client - if cfg_staging_path: - shutil.move(os.path.join(tempfile.gettempdir(), "user.cfg"), user_cfg_path) - elif os.path.exists(user_cfg_path): - os.remove(user_cfg_path) - - # save logs,screenshots - if os.path.exists(workspace.paths.project_log()): - workspace.artifact_manager.save_artifact(workspace.paths.project_log()) - if os.path.isdir(workspace.paths.project_screenshots()): - workspace.artifact_manager.save_artifact(workspace.paths.project_screenshots()) - - request.addfinalizer(teardown) - - @pytest.mark.parametrize('level', ['Samples/Fur_Technical_Sample', 'Samples/Advanced_RinLocomotion', 'UI/UiFeatures','Samples/Simple_JackLocomotion', - 'Samples/ScriptedEntityTweenerSample/SampleFullscreenAnimation', 'UI/UiMainMenuLuaSample', 'Samples/Metastream_Sample', - 'Samples/ScriptCanvas_Sample/ScriptCanvas_Basic_Sample', 'UI/UiIn3DWorld', 'Samples/Audio_Sample']) - @pytest.mark.test_case_id('C1698289,C1698287,C1698294,C1698280,C1698295,C1698293,C1698289,C1698290,C1698292,C1698288') - @pytest.mark.parametrize('platform', ['win_x64_vs2017', 'win_x64_vs2019']) - @pytest.mark.parametrize('configuration', ['profile']) - @pytest.mark.parametrize('project', ['SamplesProject']) - @pytest.mark.parametrize('spec', ['all']) - def test_LaunchAndWait_LoadsLevelAndQuit_NoCrash(self, workspace, level, launcher): - """ - Launch the Project Launcher and sets the specified level. - Performs the console steps by passing in args. - Loads the specified level and quit's the launcher. - """ - # Fast fail if the level doesn't exist - assert os.path.exists(os.path.join(workspace.paths.project(), "Levels", level)) and os.path.isdir( - os.path.join(workspace.paths.project(), "Levels", level)), "Level Doesn't Exist" - - launcher.args = ["+map", level] - launcher.launch() - - pattern_string = "Loading level " + level - test = os.path.join(workspace.paths.project_log(), "Game.log") - wait_for(lambda: os.path.exists(os.path.join(workspace.paths.project_log(), "Game.log"))) - wait_for(lambda: substring.in_file( - os.path.join(workspace.paths.project_log(), "Game.log"), pattern_string), LAUNCHER_TIMEOUT) - assert not os.path.exists( - os.path.join(workspace.paths.project_log(), "error.log")), "Launcher Crashed Unexpectedly" - - # This is a convenient place to also verify LY-90255 for free here - # (as well as any other text that must appear in the log). - # writing a separate test to launch the launcher and examine the log would just waste time as the - # above test already launches the launcher, and waits for it to finish anyway. - - assert substring.in_file(os.path.join(workspace.paths.project_log(), "Game.log"), "Initializing CryFont done, MemUsage") - - - - @pytest.mark.parametrize('level',['UI/UiFeatures','Samples/Metastream_Sample']) - @pytest.mark.parametrize('platform', ['win_x64_vs2017', 'win_x64_vs2019']) - @pytest.mark.parametrize('configuration', ['profile']) - @pytest.mark.parametrize('project', ['SamplesProject']) - @pytest.mark.parametrize('spec', ['all']) - def test_LaunchAndWait_LoadsLevelFromPakAndQuit_NoCrash(self, workspace, level, launcher): - """ - This test ensure that the Launcher can load levels from inside paks - """ - - if workspace.platform == 'win_x64_vs2017': - binDirPath = os.path.join(workspace.paths._dev_path, 'Bin64vc141') - if workspace.platform == 'win_x64_vs2019': - binDirPath = os.path.join(workspace.paths._dev_path, 'Bin64vc142') - - # Launch APBatch so that it can process all assets and quit - subprocess.check_call( - [os.path.join(binDirPath, 'AssetProcessorBatch'), "/gamefolder=SamplesProject"]) - - lowercaseProjectName = workspace.project.lower() - cacheLevelDir = os.path.join(workspace.paths.platform_cache(), lowercaseProjectName, "levels") - cacheTempLevelDir = os.path.join(workspace.paths.platform_cache(), lowercaseProjectName, "templevels") - - # Rename levels dir so that runtime cannot load levels from it - os.rename(cacheLevelDir, cacheTempLevelDir) - # Make an empty levels folder - os.mkdir(cacheLevelDir) - - # make an archive of all the levels - outputLevelsArchive = os.path.join(cacheLevelDir, "templevels") - shutil.make_archive(outputLevelsArchive, 'zip', cacheTempLevelDir) - # make archive will make a zip file - outputLevelsArchive = outputLevelsArchive + ".zip" - - # change extension from zip to pak - baseFileName = os.path.splitext(outputLevelsArchive)[0] - os.rename(outputLevelsArchive, baseFileName + ".pak") - - # Launching AP again for SamplesProject gameproject - subprocess.Popen([os.path.join(binDirPath, 'AssetProcessor'), "/gamefolder=SamplesProject", "--zeroAnalysisMode"]) - - # Waiting to give AP time some time to start the listening thread otherwise the launcher will try to launch another instance of AP - time.sleep(1) - - # ensure that in the cache level does not exist on disk - assert not os.path.exists(os.path.join(cacheLevelDir, level)) - - # load the levels - launcher.args = ["+map", level] - launcher.launch() - - pattern_string = "Loading level " + level - wait_for(lambda: os.path.exists(os.path.join(workspace.paths.project_log(), "Game.log"))) - wait_for(lambda: substring.in_file( - os.path.join(workspace.paths.project_log(), "Game.log"), pattern_string), LAUNCHER_TIMEOUT) - assert not os.path.exists( - os.path.join(workspace.paths.project_log(), "error.log")), "Launcher Crashed Unexpectedly" - - loaded_level_pattern = "Level " + level + " loaded" - wait_for(lambda: substring.in_file( - os.path.join(workspace.paths.project_log(), "Game.log"), loaded_level_pattern), LAUNCHER_TIMEOUT) - - launcher.stop() - - aputil.kill_asset_processor() - - shutil.rmtree(cacheLevelDir) - os.rename(cacheTempLevelDir, cacheLevelDir) - - - diff --git a/Tests/demos/mac/__init__.py b/Tests/demos/mac/__init__.py deleted file mode 100755 index e912252f4e..0000000000 --- a/Tests/demos/mac/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - - All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - its licensors. - - For complete copyright and license terms please see the LICENSE at the root of this - distribution (the "License"). All use of this software is governed by the License, - or, if provided, by the license below or the license accompanying this file. Do not - remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - diff --git a/Tests/demos/mac/demos_mac.py b/Tests/demos/mac/demos_mac.py deleted file mode 100755 index a2a44eab20..0000000000 --- a/Tests/demos/mac/demos_mac.py +++ /dev/null @@ -1,142 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -These tests will validate that StarterGame and SamplesProject can be setup and built, have no failing assets processed, -and will then have a screen shot taken to verify that it renders normally. All the logs and screenshots will be -transferred to the test results to be zipped up and added to the Flume result. These projects will be run in profile -and debug. -""" -import logging -import os -import pytest - -from demos.test_lib.demos_testlib import load_level, remote_console_take_screenshot, start_launcher, start_remote_console -import shared.shader_compile_server_utils as compile_server -import test_tools.shared.file_utils as file_utils -from test_tools.shared.launcher_testlib import configure_setup, assert_build_success, assert_process_assets - -import test_tools.builtin.fixtures as fixtures -from test_tools import MAC_LAUNCHER -import test_tools.launchers.phase -from test_tools.shared.remote_console_commands import RemoteConsole - -logger = logging.getLogger(__name__) - -workspace = fixtures.use_fixture(fixtures.builtin_workspace_fixture, scope='function') - - -@pytest.fixture -def launcher_instance(request, workspace, level): - """ - Creates a launcher fixture instance with an extra teardown for error log grabbing. - """ - def teardown(): - """ - Tries to grab any error logs before moving on to the next test. - """ - compile_server.stop_shader_compile_server() - - if os.path.exists(launcher.workspace.release.paths.project_log()): - for file_name in os.listdir(launcher.workspace.release.paths.project_log()): - file_utils.move_file(launcher.workspace.release.paths.project_log(), - launcher.workspace.artifact_manager.get_save_artifact_path(), - file_name) - - logs_exist = lambda: file_utils.gather_error_logs( - launcher.workspace.release.paths.dev(), - launcher.workspace.artifact_manager.get_save_artifact_path()) - try: - test_tools.shared.waiter.wait_for(logs_exist) - except AssertionError: - print("No error logs found. Completing test...") - - request.addfinalizer(teardown) - - launcher = fixtures.launcher(request, workspace, level) - return launcher - - -@pytest.fixture -def remote_console_instance(request): - """ - Creates a remote console instance to send console commands. - """ - console = RemoteConsole() - - def teardown(): - try: - console.stop() - except: - pass - - request.addfinalizer(teardown) - - return console - - -@pytest.mark.parametrize("platform,configuration,project,spec,level", [ - pytest.param("darwin_x64", "profile", "StarterGame", "all", "StarterGame", - marks=pytest.mark.skipif(not MAC_LAUNCHER, reason="Only supported on Mac hosts")), - pytest.param("darwin_x64", "debug", "StarterGame", "all", "StarterGame", - marks=pytest.mark.skipif(not MAC_LAUNCHER, reason="Only supported on Mac hosts")), - ]) -class TestSingleLevel(object): - def test_single_level(self, launcher_instance, configuration, level, remote_console_instance): - """ - Verifies projects with a given demo-level can compile and successfully launch. - """ - configure_setup(launcher_instance) - - assert_build_success(launcher_instance) - assert_process_assets(launcher_instance) - - compile_server.start_mac_shader_compile_server(os.path.join(launcher_instance.workspace.release.paths.dev(), - "Tools"), configuration) - start_launcher(launcher_instance) - start_remote_console(launcher_instance, remote_console_instance) - - load_level(launcher_instance, remote_console_instance, level) - remote_console_take_screenshot(launcher_instance, remote_console_instance, level) - - -@pytest.mark.parametrize("platform,configuration,project,spec,level,levels", [ - pytest.param("darwin_x64", "profile", "SamplesProject", "all", "Advanced_RinLocomotion", - ["Advanced_RinLocomotion", "Audio_Sample", "Fur_Technical_Sample", - "Gems_InAppPurchases_Sample", "Metastream_Sample", "ScriptCanvas_Basic_Sample", - "Simple_JackLocomotion", "SampleFullscreenAnimation", "UiFeatures", "UiIn3DWorld", - "UiMainMenuLuaSample", "UiMainMenuScriptCanvasSample"], - marks=pytest.mark.skipif(not MAC_LAUNCHER, reason="Only supported on Mac hosts")), - pytest.param("darwin_x64", "debug", "SamplesProject", "all", "Advanced_RinLocomotion", - ["Advanced_RinLocomotion", "Audio_Sample", "Fur_Technical_Sample", - "Gems_InAppPurchases_Sample", "Metastream_Sample", "ScriptCanvas_Basic_Sample", - "Simple_JackLocomotion", "SampleFullscreenAnimation", "UiFeatures", "UiIn3DWorld", - "UiMainMenuLuaSample", "UiMainMenuScriptCanvasSample"], - marks=pytest.mark.skipif(not MAC_LAUNCHER, reason="Only supported on Mac hosts")), - ]) -# Testing for projects with multiple demo levels -class TestMultipleLevels(object): - """ - Verifies projects with multiple demo-levels can compile and successfully launch. - """ - def test_multiple_levels(self, launcher_instance, configuration, levels, remote_console_instance): - configure_setup(launcher_instance) - - assert_build_success(launcher_instance) - assert_process_assets(launcher_instance) - - compile_server.start_mac_shader_compile_server(os.path.join(launcher_instance.workspace.release.paths.dev(), - "Tools"), configuration) - start_launcher(launcher_instance) - start_remote_console(launcher_instance, remote_console_instance) - - # Switch to each level, check if it loads and takes a screen shot, then move to test folder for Flume - for level in levels: - load_level(launcher_instance, remote_console_instance, level) - remote_console_take_screenshot(launcher_instance, remote_console_instance, level) diff --git a/Tests/demos/test_lib/__init__.py b/Tests/demos/test_lib/__init__.py deleted file mode 100755 index e912252f4e..0000000000 --- a/Tests/demos/test_lib/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - - All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - its licensors. - - For complete copyright and license terms please see the LICENSE at the root of this - distribution (the "License"). All use of this software is governed by the License, - or, if provided, by the license below or the license accompanying this file. Do not - remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - diff --git a/Tests/demos/test_lib/demos_testlib.py b/Tests/demos/test_lib/demos_testlib.py deleted file mode 100755 index 46ed627cab..0000000000 --- a/Tests/demos/test_lib/demos_testlib.py +++ /dev/null @@ -1,70 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -This demos_testlib file is used for a collection of reusable functionality that QA will use in their scripts specific -to the setup of demo level tests. -""" -import sys - -import shared.network_utils as network_utils -from shared.screenshot_utils import move_screenshots, take_screenshot_with_retries - -from test_tools.shared.launcher_testlib import * - -import test_tools.shared.waiter - - -def start_launcher(launcher): - """ - For PC: Used to start launcher and give time to load. - """ - launcher.launch() - launcher.run(test_tools.launchers.phase.TimePhase(120, 120)) - - -def load_level(launcher, remote_console, level): - """ - Uses the remote console to use the map command to load a level and checks the console output for a successful load. - """ - command = 'map {}'.format(level) - load = remote_console.expect_log_line('LEVEL_LOAD_COMPLETE', 300) - retry_console_command(remote_console, command, "Executing console command '{}'".format(command)) - assert load(), "{} level failed to load.".format(level) - - # Allow one minute to let level fully render and to test for stability - launcher.run(test_tools.launchers.phase.TimePhase(60, 60)) - - -def start_remote_console(launcher, remote_console, on_devkit=False): - """ - Starts the remote console. Used in QA scripts that require the use of remote console. - """ - if on_devkit: - test_tools.shared.waiter.wait_for(lambda: network_utils.check_for_remote_listening_port(4600, launcher.ip), - timeout=600, exc=AssertionError('Port 4600 not listening.')) - else: - test_tools.shared.waiter.wait_for(lambda: network_utils.check_for_listening_port(4600), timeout=300, - exc=AssertionError('Port 4600 not listening.')) - - remote_console.start() - - # Allows remote console time to connect to launcher. - launcher.run(test_tools.launchers.phase.TimePhase(60, 60)) - - -def remote_console_take_screenshot(launcher, remote_console, level): - """ - Uses the remote console to run the r_GetScreenshot command to take a screenshot of the current launcher and move - the screenshot to the test results location. - """ - screenshot_path = os.path.join(launcher.workspace.release.paths.platform_cache(), "user", "screenshots") - take_screenshot_with_retries(remote_console, launcher, level) - if os.path.exists(screenshot_path): - move_screenshots(screenshot_path, '.jpg', launcher.workspace.artifact_manager.get_save_artifact_path()) diff --git a/Tests/demos/win/__init__.py b/Tests/demos/win/__init__.py deleted file mode 100755 index 4d5680a30d..0000000000 --- a/Tests/demos/win/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# diff --git a/Tests/demos/win/demos_pc.py b/Tests/demos/win/demos_pc.py deleted file mode 100755 index cb8b9a0e4b..0000000000 --- a/Tests/demos/win/demos_pc.py +++ /dev/null @@ -1,151 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -These tests will validate that each project can be setup and built, have no failing assets processed, and will then -have a screenshot taken to verify that it renders normally. All the logs and screenshots will be transferred to the -test results to be zipped up and added to the Flume result. These projects will be run in profile and debug. -Currently SearchForEden and Bistro are failing and are temporarily commented out of these tests. -""" -import logging -import os -import pytest - -from demos.test_lib.demos_testlib import load_level, remote_console_take_screenshot, start_launcher, start_remote_console -from test_tools.shared.file_utils import gather_error_logs, move_file -from test_tools.shared.launcher_testlib import configure_setup, assert_build_success, assert_process_assets - -from test_tools import WINDOWS_LAUNCHER -import test_tools.builtin.fixtures as fixtures -import test_tools.launchers.phase -from test_tools.shared.remote_console_commands import RemoteConsole - -logger = logging.getLogger(__name__) - -# use_fixture registers the imported fixture in pytest at the specified scope. The test should provide all the -# parameters in the fixture's signature -workspace = fixtures.use_fixture(fixtures.builtin_workspace_fixture, scope='function') - - -@pytest.fixture -def launcher_instance(request, workspace, level): - """ - Creates a launcher fixture instance with an extra teardown for error log grabbing. - """ - def teardown(): - """ - Tries to grab any error logs before moving on to the next test. - """ - if os.path.exists(launcher.workspace.release.paths.project_log()): - for file_name in os.listdir(launcher.workspace.release.paths.project_log()): - move_file(launcher.workspace.release.paths.project_log(), - launcher.workspace.artifact_manager.get_save_artifact_path(), - file_name) - - logs_exist = lambda: gather_error_logs( - launcher.workspace.release.paths.dev(), - launcher.workspace.artifact_manager.get_save_artifact_path()) - try: - test_tools.shared.waiter.wait_for(logs_exist) - except AssertionError: - print("No error logs found. Completing test...") - - request.addfinalizer(teardown) - - launcher = fixtures.launcher(request, workspace, level) - return launcher - - -@pytest.fixture -def remote_console_instance(request): - """ - Creates a remote console instance to send console commands. - """ - console = RemoteConsole() - - def teardown(): - try: - console.stop() - except: - pass - - request.addfinalizer(teardown) - - return console - - -@pytest.mark.parametrize("platform,configuration,project,spec,level", [ - pytest.param("win_x64_vs2017", "profile", "StarterGame", "all", "StarterGame", - marks=pytest.mark.skipif(not WINDOWS_LAUNCHER, reason="Only supported on Windows hosts")), - pytest.param("win_x64_vs2019", "profile", "StarterGame", "all", "StarterGame", - marks=pytest.mark.skipif(not WINDOWS_LAUNCHER, reason="Only supported on Windows hosts")), - pytest.param("win_x64_vs2017", "debug", "StarterGame", "all", "StarterGame", - marks=pytest.mark.skipif(not WINDOWS_LAUNCHER, reason="Only supported on Windows hosts")), - pytest.param("win_x64_vs2019", "debug", "StarterGame", "all", "StarterGame", - marks=pytest.mark.skipif(not WINDOWS_LAUNCHER, reason="Only supported on Windows hosts")), - ]) -class TestSingleLevel(object): - def test_single_level(self, launcher_instance, level, remote_console_instance): - """ - Verifies projects with a given demo-level can compile and successfully launch. - """ - configure_setup(launcher_instance) - - assert_build_success(launcher_instance) - assert_process_assets(launcher_instance) - - start_launcher(launcher_instance) - start_remote_console(launcher_instance, remote_console_instance) - - load_level(launcher_instance, remote_console_instance, level) - remote_console_take_screenshot(launcher_instance, remote_console_instance, level) - - -@pytest.mark.parametrize("platform,configuration,project,spec,level,levels", [ - pytest.param("win_x64_vs2017", "profile", "SamplesProject", "all", "Advanced_RinLocomotion", - ["Advanced_RinLocomotion", "Audio_Sample", "Fur_Technical_Sample", "Gems_InAppPurchases_Sample", - "Metastream_Sample", "ScriptCanvas_Basic_Sample", "Simple_JackLocomotion", - "SampleFullscreenAnimation", "UiDrawCallsSample", "UiFeatures", "UiIn3DWorld", "UiMainMenuLuaSample", - "UiMainMenuScriptCanvasSample", "UiTextureAtlasSample"], - marks=pytest.mark.skipif(not WINDOWS_LAUNCHER, reason="Only supported on Windows hosts")), - pytest.param("win_x64_vs2019", "profile", "SamplesProject", "all", "Advanced_RinLocomotion", - ["Advanced_RinLocomotion", "Audio_Sample", "Fur_Technical_Sample", "Gems_InAppPurchases_Sample", - "Metastream_Sample", "ScriptCanvas_Basic_Sample", "Simple_JackLocomotion", - "SampleFullscreenAnimation", "UiDrawCallsSample", "UiFeatures", "UiIn3DWorld", "UiMainMenuLuaSample", - "UiMainMenuScriptCanvasSample", "UiTextureAtlasSample"], - marks=pytest.mark.skipif(not WINDOWS_LAUNCHER, reason="Only supported on Windows hosts")), - pytest.param("win_x64_vs2017", "debug", "SamplesProject", "all", "Advanced_RinLocomotion", - ["Advanced_RinLocomotion", "Audio_Sample", "Fur_Technical_Sample", "Gems_InAppPurchases_Sample", - "Metastream_Sample", "ScriptCanvas_Basic_Sample", "Simple_JackLocomotion", - "SampleFullscreenAnimation", "UiDrawCallsSample", "UiFeatures", "UiIn3DWorld", "UiMainMenuLuaSample", - "UiMainMenuScriptCanvasSample", "UiTextureAtlasSample"], - marks=pytest.mark.skipif(not WINDOWS_LAUNCHER, reason="Only supported on Windows hosts")), - pytest.param("win_x64_vs2019", "debug", "SamplesProject", "all", "Advanced_RinLocomotion", - ["Advanced_RinLocomotion", "Audio_Sample", "Fur_Technical_Sample", "Gems_InAppPurchases_Sample", - "Metastream_Sample", "ScriptCanvas_Basic_Sample", "Simple_JackLocomotion", - "SampleFullscreenAnimation", "UiDrawCallsSample", "UiFeatures", "UiIn3DWorld", "UiMainMenuLuaSample", - "UiMainMenuScriptCanvasSample", "UiTextureAtlasSample"], - marks=pytest.mark.skipif(not WINDOWS_LAUNCHER, reason="Only supported on Windows hosts")) - ]) -class TestMultipleLevels(object): - def test_multiple_levels(self, launcher_instance, levels, remote_console_instance): - """ - Verifies projects with multiple demo-levels can compile and successfully launch. - """ - configure_setup(launcher_instance) - - assert_build_success(launcher_instance) - assert_process_assets(launcher_instance) - - start_launcher(launcher_instance) - start_remote_console(launcher_instance, remote_console_instance) - - for level in levels: - load_level(launcher_instance, remote_console_instance, level) - remote_console_take_screenshot(launcher_instance, remote_console_instance, level) diff --git a/Tests/graphics/ly107748_LightningArcProperties.cfg b/Tests/graphics/ly107748_LightningArcProperties.cfg deleted file mode 100644 index fe05f5f782..0000000000 --- a/Tests/graphics/ly107748_LightningArcProperties.cfg +++ /dev/null @@ -1,2 +0,0 @@ -# this file is copied to $/dev/editor_autoexec.cfg so the the Editor automation runs for this Hydra test -pyRunFile @devroot@/Tests/graphics/ly107748_LightningArcProperties_test_case.py \ No newline at end of file diff --git a/Tests/graphics/ly107748_LightningArcProperties_test.py b/Tests/graphics/ly107748_LightningArcProperties_test.py deleted file mode 100755 index 59cc3d8d1b..0000000000 --- a/Tests/graphics/ly107748_LightningArcProperties_test.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -# -# This is a pytest module to test the in-Editor Python API from PythonEditorFuncs -# -import pytest -pytest.importorskip('test_tools') -import time -import logging -import os -import shutil - -from test_tools import WINDOWS_LAUNCHER -import test_tools.shared.log_monitor -import test_tools.launchers.phase -import test_tools.builtin.fixtures as fixtures - -# Use the built-in workspace and editor fixtures. -# These will configure the requested project and run the editor. -workspace = fixtures.use_fixture(fixtures.builtin_workspace_fixture, scope='function') -editor = fixtures.use_fixture(fixtures.editor, scope='function') - -logger = logging.getLogger(__name__) - - -@pytest.mark.parametrize("platform,configuration,project,spec", [ - pytest.param("win_x64_vs2017", "profile", "AutomatedTesting", "all", marks=pytest.mark.skipif(not WINDOWS_LAUNCHER, reason="Only supported on Windows hosts")), -]) -class TestLightningArcPropertyRanges(object): - - @pytest.fixture(autouse=True) - def setup_teardown(self, request, workspace, editor): - def teardown(): - editor.ensure_stopped() - - file_utils.delete_level(editor, "LightningArcTestLevel") - - request.addfinalizer(teardown) - - def test_change_properties(self, request, editor, project): - logger.debug("Running automated test") - - request.addfinalizer(editor.ensure_stopped) - - editor.deploy() - editor.launch(["--exec", "@engroot@/Tests/graphics/ly107748_LightningArcProperties.cfg"]) - - editorlog_file = os.path.join(editor.workspace.release.paths.project_log(), 'Editor.log') - - # LY-107861 LY-108088 - # expected failure cases are commented out pending implementation of property validation by hydra - expected_lines = [ - "Created new entity.", - "Lightning Arc component added to entity.", - #"ChangeProperty m_config|Arc Parameters|Segment Count to 0 failed.", - "ChangeProperty m_config|Arc Parameters|Segment Count to 1 succeeded.", - "ChangeProperty m_config|Arc Parameters|Segment Count to 25 succeeded.", - "ChangeProperty m_config|Arc Parameters|Segment Count to 50 succeeded.", - "ChangeProperty m_config|Arc Parameters|Segment Count to 70 succeeded.", - #"ChangeProperty m_config|Arc Parameters|Segment Count to 75 failed.", - #"ChangeProperty m_config|Arc Parameters|Segment Count to 100 failed.", - #"ChangeProperty m_config|Arc Parameters|Point Count to 0 failed.", - "ChangeProperty m_config|Arc Parameters|Point Count to 1 succeeded.", - "ChangeProperty m_config|Arc Parameters|Point Count to 25 succeeded.", - "ChangeProperty m_config|Arc Parameters|Point Count to 50 succeeded.", - "ChangeProperty m_config|Arc Parameters|Point Count to 70 succeeded.", - #"ChangeProperty m_config|Arc Parameters|Point Count to 75 failed.", - #"ChangeProperty m_config|Arc Parameters|Point Count to 100 failed.", - ] - - test_tools.shared.log_monitor.monitor_for_expected_lines(editor, editorlog_file, expected_lines) - - # Rely on the test script to quit after running - editor.run(test_tools.launchers.phase.WaitForLauncherToQuit(editor, 10)) diff --git a/Tests/graphics/ly107748_LightningArcProperties_test_case.py b/Tests/graphics/ly107748_LightningArcProperties_test_case.py deleted file mode 100755 index dd0c72e893..0000000000 --- a/Tests/graphics/ly107748_LightningArcProperties_test_case.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -# Tests a portion of the Component Property Get/Set Python API while the Editor is running - -import azlmbr.legacy.general as general -import azlmbr.bus as bus -import azlmbr.entity as entity -import azlmbr.editor as editor -import azlmbr.math as math - - -# Create a test level -general.create_level_no_prompt("LightningArcTestLevel", 1024, 1, 1024, True) - -def ChangeProperty(component, path, value): - getPropertyOutcome = editor.EditorComponentAPIBus(bus.Broadcast, 'GetComponentProperty', component, path) - if not(getPropertyOutcome.IsSuccess()): - print("GetComponentProperty " + path + " failed.") - else: - oldValue = getPropertyOutcome.GetValue() - - setPropertyOutcome = editor.EditorComponentAPIBus(bus.Broadcast, 'SetComponentProperty', component, path, value) - if not(setPropertyOutcome.IsSuccess()): - print("SetComponentProperty " + path + " to " + str(value) + " failed.") - - getPropertyOutcome = editor.EditorComponentAPIBus(bus.Broadcast, 'GetComponentProperty', component, path) - if not(getPropertyOutcome.IsSuccess()): - print("GetComponentProperty " + path + " failed.") - else: - newValue = getPropertyOutcome.GetValue() - - if not(newValue == oldValue): - print("ChangeProperty " + path + " to " + str(value) + " succeeded.") - else: - print("ChangeProperty " + path + " to " + str(value) + " failed.") - -# Create new Entity -entityId = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', entity.EntityId()) - -if (entityId.IsValid()): - print("Created new entity.") - -# Get Component Type for Lightning Arc -typeIdsList = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Lightning Arc"], entity.EntityType().Game) - -componentOutcome = editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', entityId, typeIdsList) - -if (componentOutcome.IsSuccess()): - print("Lightning Arc component added to entity.") - -components = componentOutcome.GetValue() -component = components[0] - -# Tests for GetComponentProperty/SetComponentProperty -ChangeProperty(component, "m_config|Arc Parameters|Segment Count", 0) -ChangeProperty(component, "m_config|Arc Parameters|Segment Count", 1) -ChangeProperty(component, "m_config|Arc Parameters|Segment Count", 25) -ChangeProperty(component, "m_config|Arc Parameters|Segment Count", 50) -ChangeProperty(component, "m_config|Arc Parameters|Segment Count", 70) -ChangeProperty(component, "m_config|Arc Parameters|Segment Count", 75) -ChangeProperty(component, "m_config|Arc Parameters|Segment Count", 100) - -ChangeProperty(component, "m_config|Arc Parameters|Point Count", 0) -ChangeProperty(component, "m_config|Arc Parameters|Point Count", 1) -ChangeProperty(component, "m_config|Arc Parameters|Point Count", 25) -ChangeProperty(component, "m_config|Arc Parameters|Point Count", 50) -ChangeProperty(component, "m_config|Arc Parameters|Point Count", 70) -ChangeProperty(component, "m_config|Arc Parameters|Point Count", 75) -ChangeProperty(component, "m_config|Arc Parameters|Point Count", 100) - -general.exit_no_prompt() diff --git a/Tests/hydra/ctests/open_level_tweak_and_exit.py b/Tests/hydra/ctests/open_level_tweak_and_exit.py deleted file mode 100755 index de81327852..0000000000 --- a/Tests/hydra/ctests/open_level_tweak_and_exit.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -# --runpythontest @devroot@\tests\hydra\ctests\open_level_tweak_and_exit.py -# An example of how a create a level, make an entity, and terminate successfully - -import time -import azlmbr.editor -import azlmbr.entity -import azlmbr.framework -import azlmbr.legacy.general as general -from azlmbr.bus import Broadcast - -handler = None - -def open_level(level): - print('opening level {}'.format(level)) - azlmbr.editor.EditorToolsApplicationRequestBus(Broadcast, 'OpenLevelNoPrompt', level) - general.idle_wait(1.0) - -def on_entity_registered(args): - print('on_entity_registered') - azlmbr.framework.Terminate(0) - -def main(): - print ('open_level_tweak_and_exit - starting') - open_level('auto_test') - - azlmbr.editor.ToolsApplicationRequestBus(Broadcast, 'CreateNewEntity', azlmbr.entity.EntityId()) - general.idle_wait(1.0) - - handler = azlmbr.editor.ToolsApplicationNotificationBusHandler() - handler.connect() - handler.add_callback('EntityRegistered', on_entity_registered) - azlmbr.editor.ToolsApplicationRequestBus(Broadcast, 'CreateNewEntity', azlmbr.entity.EntityId()) - -if __name__ == "__main__": - main() diff --git a/Tests/hydra/ctests/start_stop.py b/Tests/hydra/ctests/start_stop.py deleted file mode 100755 index c8fd670a97..0000000000 --- a/Tests/hydra/ctests/start_stop.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -# --runpythontest @devroot@\tests\hydra\ctests\start_stop.py -# an example of a test script that loads a level, listens for the first entity, and terminates the Editor with a 0 - -import azlmbr.framework -import azlmbr.editor -import azlmbr.bus - -handler = None - -def on_entity_registered(args): - print('on_entity_registered') - azlmbr.framework.Terminate(0) - -def main(): - print ('hello, start_stop') - handler = azlmbr.editor.ToolsApplicationNotificationBusHandler() - handler.connect() - handler.add_callback('EntityRegistered', on_entity_registered) - azlmbr.editor.EditorToolsApplicationRequestBus(azlmbr.bus.Broadcast, 'OpenLevelNoPrompt', 'auto_test') - print ('start_stop started') - -if __name__ == "__main__": - main() diff --git a/Tests/hydra/ctests/start_with_args.py b/Tests/hydra/ctests/start_with_args.py deleted file mode 100755 index e8b99b54ac..0000000000 --- a/Tests/hydra/ctests/start_with_args.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -# --runpythontest @devroot@\tests\hydra\ctests\start_with_args.py --runpythonargs foo bar baz -# An example of how to use runpythontest with a main() + args - -import azlmbr.framework - -def main(): - print("hello, start_with_args") - - # print command line arguments - for arg in sys.argv: - print (arg) - - azlmbr.framework.Terminate(0) - -if __name__ == "__main__": - main() - diff --git a/Tests/hydra/ctests/stop_with_error_one.py b/Tests/hydra/ctests/stop_with_error_one.py deleted file mode 100755 index b17b5247aa..0000000000 --- a/Tests/hydra/ctests/stop_with_error_one.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -# --runpythontest @devroot@\tests\hydra\ctests\stop_with_error_one.py -# an example terminating with a non-zero return code from Editor.exe - -import azlmbr.framework -print ('hello, stop_with_error_one') -azlmbr.framework.Terminate(1) diff --git a/Tests/hydra/ctests/stop_with_zero.py b/Tests/hydra/ctests/stop_with_zero.py deleted file mode 100755 index d1d3f4f58d..0000000000 --- a/Tests/hydra/ctests/stop_with_zero.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -# --runpythontest @devroot@\tests\hydra\ctests\stop_with_zero.py -# An example of how a test script stops the Editor.exe with a succuessful zero return code - -import azlmbr.framework -print ('hello, stop_with_zero') -azlmbr.framework.Terminate(0) diff --git a/Tests/hydra/ctests/throws_exception.py b/Tests/hydra/ctests/throws_exception.py deleted file mode 100755 index e8b4507fb1..0000000000 --- a/Tests/hydra/ctests/throws_exception.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -# --runpythontest @devroot@\tests\hydra\ctests\throws_exception.py -# An example of how a test script to fatal from Editor.exe when a Python exception happens - -print ('hello, throws_exception') -foo = 1.0 -bar = 0.0 -baz = foo / bar - diff --git a/Tests/ly_shared/PlatformSetting.py b/Tests/ly_shared/PlatformSetting.py deleted file mode 100755 index b215275c16..0000000000 --- a/Tests/ly_shared/PlatformSetting.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Class for querying and setting a system setting/preference. - -""" - -import pytest -import logging -from typing import Optional, Any - -import ly_test_tools.o3de.pipeline_utils as utils - -logger = logging.getLogger(__name__) - - -class PlatformSetting: - """ - Interface for managing different platforms' system variables. - """ - - class DATA_TYPE: - """Platform-agnostic data type enums""" - - INT = 1 - STR = 2 - STR_LIST = 3 - - def __init__(self, workspace: pytest.fixture, subkey: str, key: str) -> None: - self._workspace = workspace - self._key = key - self._subkey = subkey - - def get_value(self, get_type: bool = False) -> object: - """Gets the current setting's value (and optionally type as tuple) from the system. Returns None if entry DNE""" - raise NotImplementedError("Virtual PlatformSetting not implemented. Instantiate a specific platform") - - def set_value(self, value: any) -> bool: - """Sets the current setting's value. Creates the entry if it DNE. Returns True for success.""" - raise NotImplementedError("Virtual PlatformSetting not implemented. Instantiate a specific platform") - - def delete_entry(self) -> bool: - """Deletes the settings entry. Returns boolean for success.""" - raise NotImplementedError("Virtual PlatformSetting not implemented. Instantiate a specific platform") - - def entry_exists(self) -> bool: - """Checks if the settings entry exists.""" - raise NotImplementedError("Virtual PlatformSetting not implemented. Instantiate a specific platform") - - @staticmethod - def get_system_setting(workspace: pytest.fixture, subkey: str, key: str, hive: Optional[str] = None) -> Any: - """Factory method creates a platform-specific system setting accessor""" - if workspace.asset_processor_platform is 'windows': - # import WindowsSetting and return an instance - from Tests.ly_shared.WindowsRegistrySetting import WindowsRegistrySetting - - return WindowsRegistrySetting(workspace, subkey, key, hive) - # ######################################################## - # Insert Mac (and Linux?) Setting implementations - # ######################################################## - else: - raise NotImplementedError(f"Platform: {workspace.platform} not supported yet") diff --git a/Tests/ly_shared/PlatformSettingTest.py b/Tests/ly_shared/PlatformSettingTest.py deleted file mode 100755 index f0ef8b6d1a..0000000000 --- a/Tests/ly_shared/PlatformSettingTest.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Tests the functionality of the PlatformSetting class -""" - -import pytest - -import ly_test_tools.builtin.helpers as helpers -from Tests.ly_shared.PlatformSetting import PlatformSetting - -all_platforms = helpers.all_host_platforms_params() -automatic_platform_skipping = helpers.automatic_platform_skipping -targetProjects = ["Helios"] - - -@pytest.mark.usefixtures("automatic_platform_skipping") -@pytest.mark.parametrize("platform", all_platforms) -@pytest.mark.parametrize("configuration", ["profile"]) -@pytest.mark.parametrize("spec", ["all"]) -@pytest.mark.parametrize("project", targetProjects) -class TestsPlatformSetting(object): - """ - Tests for the PlatformSetting class - """ - - def test_PlatformSetting(self, workspace): - - key = "Software" - subkey = "TemporarySystemSetting" - - # Create setting reference - setting = PlatformSetting.get_system_setting(workspace, subkey, key) - - # Test storing integer - value = 74 - setting.set_value(value) - - # Test creation of subkey - assert setting.entry_exists(), f"Failed creating key:subkey, {key}:{subkey}" - - # Test data retrieval (without type) - retrieved = setting.get_value() - # fmt:off - assert retrieved == value, \ - f"Unexpected value retrieved from system settings. Expected: {value}, Actual: {retrieved}" - # fmt:on - - # Test data retrieval (with type) - retrieved = setting.get_value(get_type=True) - assert type(retrieved) == tuple, "Getting value with type DID NOT return a tuple" - assert len(retrieved) == 2, f"Getting value with type returned a tuple of size {len(retrieved)}: expected 2" - assert retrieved[1] == PlatformSetting.DATA_TYPE.INT, "Value stored was int, but type retrieved was NOT int" - assert type(retrieved[0]) == int, "Value stored was int, but value retrieved was NOT int" - - # fmt:off - assert retrieved[0] == value, \ - f"Unexpected value retrieved from system settings. Expected: {value}, Actual: {retrieved[0]}" - # fmt:on - - # Test storing string - value = "Some Text" - setting.set_value(value) - retrieved = setting.get_value(get_type=True) - assert ( - retrieved[1] == PlatformSetting.DATA_TYPE.STR - ), "Value stored was string, but type retrieved was NOT string" - assert type(retrieved[0]) == str, "Value stored was string, but value retrieved was NOT string" - assert value == retrieved[0], f"Value retrieved not expected. Expected: {value}, Actual: {retrieved[0]}" - - # Test storing list of strings - value = ["Some", "List", "Of", "Text"] - setting.set_value(value) - retrieved = setting.get_value(get_type=True) - assert ( - retrieved[1] == PlatformSetting.DATA_TYPE.STR_LIST - ), "Value stored was string list, but type retrieved was NOT string list" - assert type(retrieved[0]) == list, "Value stored was string, but value retrieved was NOT string" - # fmt:off - assert sorted(value) == sorted(retrieved[0]), f"Value retrieved not expected. " \ - f"Expected: {value}, Actual: {retrieved[0]}" - # fmt:on - - setting.delete_entry() - assert not setting.entry_exists(), f"Failed to delete key:subkey, {key}:{subkey}" diff --git a/Tests/ly_shared/WindowsRegistrySetting.py b/Tests/ly_shared/WindowsRegistrySetting.py deleted file mode 100755 index c1aa4e4a98..0000000000 --- a/Tests/ly_shared/WindowsRegistrySetting.py +++ /dev/null @@ -1,165 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Class for querying and setting a windows registry setting. - -""" -import pytest -import logging -from typing import List, Optional, Tuple, Any - -from winreg import ( - CreateKey, - OpenKey, - QueryValueEx, - DeleteValue, - SetValueEx, - KEY_ALL_ACCESS, - KEY_WRITE, - REG_SZ, - REG_MULTI_SZ, - REG_DWORD, - HKEY_CURRENT_USER, -) - - -from Tests.ly_shared.PlatformSetting import PlatformSetting - -logger = logging.getLogger(__name__) - - -class WindowsRegistrySetting(PlatformSetting): - def __init__(self, workspace: pytest.fixture, subkey: str, key: str, hive: Optional[str] = None) -> None: - super().__init__(workspace, subkey, key) - self._hive = None - try: - if hive is not None: - self._hive = self._str_to_hive(hive) - except ValueError: - logger.warning(f"Windows Registry Hive {hive} not recognized, using default: HKEY_CURRENT_USER") - finally: - if self._hive is None: - self._hive = HKEY_CURRENT_USER - - def get_value(self, get_type: Optional[bool] = False) -> Any: - """Retrieves the fast scan value in Windows registry (and optionally the type). If entry DNE, returns None.""" - if self.entry_exists(): - registryKey = OpenKey(self._hive, self._key) - value = QueryValueEx(registryKey, self._subkey) - registryKey.Close() - # Convert windows data type to universal data type flag: PlatformSettings.DATA_TYPE - # And handles unicode conversion for strings - value = self._convert_value(value) - return value if get_type else value[0] - - else: - logger.warning(f"Could not retrieve Registry entry; key: {self._key}, subkey: {self._subkey}.") - return None - - def set_value(self, value: Any) -> bool: - """Sets the Windows registry value.""" - value, win_type = self._format_data(value) - registryKey = None - result = False - try: - CreateKey(self._hive, self._subkey) - registryKey = OpenKey(self._hive, self._key, 0, KEY_WRITE) - SetValueEx(registryKey, self._subkey, 0, win_type, value) - result = True - except WindowsError as e: - logger.warning(f"Windows error caught while setting fast scan registry: {e}") - finally: - if registryKey is not None: - # Close key if it's been opened successfully - registryKey.Close() - return result - - def delete_entry(self) -> bool: - """Deletes the Windows registry entry for fast scan enabled""" - try: - if self.entry_exists(): - registryKey = OpenKey(self._hive, self._key, 0, KEY_ALL_ACCESS) - DeleteValue(registryKey, self._subkey) - registryKey.Close() - return True - except WindowsError: - logger.error(f"Could not delete registry entry; key: {self._key}, subkey: {self._subkey}") - finally: - return False - - def entry_exists(self) -> bool: - """Checks for existence of the setting in Windows registry.""" - try: - # Attempt to open and query key. If fails then the entry DNE - registryKey = OpenKey(self._hive, self._key) - QueryValueEx(registryKey, self._subkey) - registryKey.Close() - return True - - except WindowsError: - return False - - @staticmethod - def _format_data(value: bool or int or str or List[str]) -> Tuple[int or str or List[str], int]: - """Formats the type of the value provided. Returns the formatted value and the windows registry type (int).""" - if type(value) == str: - return value, REG_SZ - elif type(value) == bool: - value = "true" if value else "false" - return value, REG_SZ - elif type(value) == int or type(value) == float: - if type(value) == float: - logger.warning(f"Windows registry does not support floats. Truncating {value} to integer") - value = int(value) - return value, REG_DWORD - elif type(value) == list: - for single_value in value: - if type(single_value) != str: - # fmt:off - raise ValueError( - f"Windows Registry lists only support strings, got a {type(single_value)} in the list") - # fmt:on - return value, REG_MULTI_SZ - else: - raise ValueError(f"Windows registry expected types: int, str and [str], found {type(value)}") - - @staticmethod - def _convert_value(value_tuple: Tuple[Any, int]) -> Tuple[Any, PlatformSetting.DATA_TYPE]: - """Converts the Windows registry data and type (tuple) to a (standardized) data and PlatformSetting.DATA_TYPE""" - value, windows_type = value_tuple - if windows_type == REG_SZ: - # Convert from unicode to string - return value, PlatformSetting.DATA_TYPE.STR - elif windows_type == REG_MULTI_SZ: - # Convert from unicode to string - return [string for string in value], PlatformSetting.DATA_TYPE.STR_LIST - elif windows_type == REG_DWORD: - return value, PlatformSetting.DATA_TYPE.INT - else: - raise ValueError(f"Type flag not recognized: {windows_type}") - - @staticmethod - def _str_to_hive(hive_str: str) -> int: - """Converts a string to a Windows Registry Hive enum (int)""" - from winreg import HKEY_CLASSES_ROOT, HKEY_CURRENT_CONFIG, HKEY_LOCAL_MACHINE, HKEY_USERS - - lower = hive_str.lower() - if lower == "hkey_current_user" or lower == "current_user": - return HKEY_CURRENT_USER - elif lower == "hkey_classes_root" or lower == "classes_root": - return HKEY_CLASSES_ROOT - elif lower == "hkey_current_config" or lower == "current_config": - return HKEY_CURRENT_CONFIG - elif lower == "hkey_local_machine" or lower == "local_machine": - return HKEY_LOCAL_MACHINE - elif lower == "hkey_users" or lower == "users": - return HKEY_USERS - else: - raise ValueError(f"Hive: {hive_str} not recognized") diff --git a/Tests/ly_shared/__init__.py b/Tests/ly_shared/__init__.py deleted file mode 100755 index 6ed3dc4bda..0000000000 --- a/Tests/ly_shared/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" \ No newline at end of file diff --git a/Tests/ly_shared/asset_database_utils.py b/Tests/ly_shared/asset_database_utils.py deleted file mode 100755 index da8ad3651e..0000000000 --- a/Tests/ly_shared/asset_database_utils.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -import sqlite3 -import os -from typing import List - -# Index for ProductID in Products table in DB -PRODUCT_ID_INDEX = 0 - - -def do_select(asset_db_path, cmd): - try: - connection = sqlite3.connect(asset_db_path) - # Get ProductID from database - db_rows = connection.execute(cmd) - return_result = db_rows.fetchall() - connection.close() - return return_result - except sqlite3.Error as sqlite_error: - print(f'select on db {asset_db_path} failed with exception {sqlite_error}') - return [] - - -def get_active_platforms_from_db(asset_db_path) -> List[str]: - """Returns a list of platforms that are active in the database, based on what jobs were run""" - platform_rows = do_select(asset_db_path, f"select distinct Platform from Jobs") - # Condense this into a single list of platforms. - platforms = [platform[0] for platform in platform_rows] - return platforms - - -# Convert a source product path into a db product path -# cache_platform/projectname/product_path -def get_db_product_path(workspace, source_path, cache_platform): - product_path = os.path.join(cache_platform, workspace.project, source_path) - product_path = product_path.replace('\\', '/') - return product_path - - -def get_product_id(asset_db_path, product_name) -> str: - # Get ProductID from database - product_id = list(do_select(asset_db_path, f"SELECT ProductID FROM Products where ProductName='{product_name}'")) - if len(product_id) == 0: - return product_id # return empty list - return product_id[0][PRODUCT_ID_INDEX] # Get product id from 'first' row - - -# Retrieve a product_id given a source_path assuming the source is copied into the cache with the same -# name or a product name without cache_platform or projectname prepended -def get_product_id_from_relative(workspace, source_path, asset_platform): - return get_product_id(workspace.paths.asset_db(), get_db_product_path(workspace, source_path, asset_platform)) - - -def get_missing_dependencies(asset_db_path, product_id) -> List[str]: - return list(do_select(asset_db_path, f"SELECT * FROM MissingProductDependencies where ProductPK={product_id}")) - - -def do_single_transaction(asset_db_path, cmd): - try: - connection = sqlite3.connect(asset_db_path) - cursor = connection.cursor() # SQL cursor used for issuing commands - cursor.execute(cmd) - connection.commit() # Save changes - connection.close() - except sqlite3.Error as sqlite_error: - print(f'transaction on db {asset_db_path} cmd {cmd} failed with exception {sqlite_error}') - - -def clear_missing_dependencies(asset_db_path, product_id) -> None: - do_single_transaction(asset_db_path, f"DELETE FROM MissingProductDependencies where ProductPK={product_id}") - - -def clear_all_missing_dependencies(asset_db_path) -> None: - do_single_transaction(asset_db_path, "DELETE FROM MissingProductDependencies;") diff --git a/Tests/ly_shared/asset_processor_utils.py b/Tests/ly_shared/asset_processor_utils.py deleted file mode 100755 index 13dc50ea83..0000000000 --- a/Tests/ly_shared/asset_processor_utils.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - - -import logging -import os -import subprocess - -from ly_test_tools.environment.process_utils import kill_processes_named as kill_processes_named -logger = logging.getLogger(__name__) - - -def start_asset_processor(bin_dir): - """ - Starts the AssetProcessor from the given bin directory. Raises a RuntimeError if the process fails. - :param bin_dir: The bin directory from which to launch the AssetProcessor executable. - :return: A subprocess.Popen object for the AssetProcessor process. - """ - os.chdir(bin_dir) - asset_processor = subprocess.Popen(['AssetProcessor.exe']) - return_code = asset_processor.poll() - - if return_code is not None and return_code != 0: - logger.error("Failed to start AssetProcessor") - raise RuntimeError("AssetProcessor exited with code {}".format(return_code)) - else: - logger.info("AssetProcessor is running") - return asset_processor - - -def kill_asset_processor(): - """ - Kill the AssetProcessor and all its related processes . - """ - - kill_processes_named('AssetProcessor_tmp', ignore_extensions=True) - kill_processes_named('AssetProcessor', ignore_extensions=True) - kill_processes_named('AssetProcessorBatch', ignore_extensions=True) - kill_processes_named('AssetBuilder', ignore_extensions=True) - kill_processes_named('rc', ignore_extensions=True) - - - diff --git a/Tests/ly_shared/file_utils.py b/Tests/ly_shared/file_utils.py deleted file mode 100755 index 2fc93d7538..0000000000 --- a/Tests/ly_shared/file_utils.py +++ /dev/null @@ -1,169 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -import os -import shutil -import logging -import stat - -import ly_test_tools.environment.file_system as file_system -import ly_test_tools.environment.waiter as waiter - -logger = logging.getLogger(__name__) - - -def clear_out_file(file_path): - """ - Clears out the specified config file to be empty. - :param file_path: The full path to the file. - """ - if os.path.exists(file_path): - file_system.unlock_file(file_path) - with open(file_path, 'w') as file_to_write: - file_to_write.write('') - else: - logger.debug(f'{file_path} not found while attempting to clear out file.') - - -def add_commands_to_config_file(config_file_dir, config_file_name, command_list): - """ - From the command list, appends each command to the specified config file. - :param config_file_dir: The directory the config file is contained in. - :param config_file_name: The config file name. - :param command_list: The commands to add to the file. - :return: - """ - config_file_path = os.path.join(config_file_dir, config_file_name) - os.chmod(config_file_path, stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC) - with open(config_file_path, 'w') as launch_config_file: - for command in command_list: - launch_config_file.write("{}\n".format(command)) - - -def gather_error_logs(workspace): - """ - Grabs all error logs (if there are any) and puts them into the specified logs path. - :param workspace: The AbstractWorkspaceManager object that contains all of the paths - """ - error_log_path = os.path.join(workspace.paths.project_log(), 'error.log') - error_dump_path = os.path.join(workspace.paths.project_log(), 'error.dmp') - if os.path.exists(error_dump_path): - workspace.artifact_manager.save_artifact(error_dump_path) - if os.path.exists(error_log_path): - workspace.artifact_manager.save_artifact(error_log_path) - - -def delete_screenshot_folder(workspace): - """ - Deletes screenshot folder from platform path - :param workspace: The AbstractWorkspaceManager object that contains all of the paths - """ - shutil.rmtree(workspace.paths.project_screenshots(), ignore_errors=True) - - -def move_file(src_dir, dest_dir, file_name, timeout=120): - """ - Attempts to move a file from the source directory to the destination directory. Raises an IOError if - the file is in use. - :param src_dir: Directory of the file to be moved. - :param dest_dir: Directory where the file will be moved to. - :param file_name: Name of the file to be moved. - :param timeout: Number of seconds to wait for the file to be released. - """ - file_path = os.path.join(src_dir, file_name) - if os.path.exists(file_path): - waiter.wait_for(lambda: move_file_check(src_dir, dest_dir, file_name), timeout=timeout, - exc=IOError('Cannot move file {} while in use'.format(file_path))) - - -def move_file_check(src_dir, dest_dir, file_name): - """ - Moves file and checks if the file has been moved from the source to the destination directory. - :param src_dir: Source directory of the file to be moved - :param dest_dir: Destination directory where the file should move to - :param file_name: The name of the file to be moved - :return: - """ - try: - shutil.move(os.path.join(src_dir, file_name), os.path.join(dest_dir, file_name)) - except OSError as e: - logger.info(e) - return False - - return True - - -def rename_file(file_path, dest_path, timeout=10): - # type: (str, str, int) -> None - """ - Renames a file by moving it. Waits for file to become available and raises and exception if timeout occurs. - :param file_path: absolute path to the source file - :param dest_path: absolute path to the new file - :param timeout: timeout to wait for function to complete - :return: None - """ - def _rename_file_check(): - try: - shutil.move(file_path, dest_path) - except OSError as e: - logger.debug(f'Attempted to rename file: {file_path} but an error occurred, retrying.' - f'\nError: {e}', - stackinfo=True) - return False - return True - - if os.path.exists(file_path): - waiter.wait_for(lambda: _rename_file_check(), timeout=timeout, - exc=OSError('Cannot rename file {} while in use'.format(file_path))) - - -def delete_level(workspace, level_dir, timeout=120): - """ - Attempts to delete an entire level folder from the project. - :param workspace: The workspace instance to delete the level from. - :param level_dir: The level folder to delete - """ - - if not level_dir: - logger.warning("level_dir is empty, nothing to delete.") - return - - full_level_dir = os.path.join(workspace.paths.project(), 'Levels', level_dir) - if not os.path.isdir(full_level_dir): - if os.path.exists(full_level_dir): - logger.error("level '{}' isn't a directory, it won't be deleted.".format(full_level_dir)) - else: - logger.info("level '{}' doesn't exist, nothing to delete.".format(full_level_dir)) - return - - waiter.wait_for(lambda: delete_check(full_level_dir), - timeout=timeout, - exc=IOError('Cannot delete directory {} while in use'.format(full_level_dir))) - -def delete_check(src_dir): - """ - Deletes directory and verifies that it's been deleted. - :param src_dir: The directory to delete - """ - try: - def handle_delete_error(action, path, exception_info): - logger.info("Error deleting '{}' ({}), changing permissions to writeable.".format(path, exception_info)) - os.chmod(path, stat.S_IWRITE) - # Try the passed-in action (delete) again - action(path) - - shutil.rmtree(src_dir, onerror=handle_delete_error) - except OSError as e: - logger.debug("Delete for '{}' failed: {}".format(src_dir, e)) - return False - - return not os.path.exists(src_dir) - diff --git a/Tests/ly_shared/hydra_editor_utils.py b/Tests/ly_shared/hydra_editor_utils.py deleted file mode 100755 index b75e680cf8..0000000000 --- a/Tests/ly_shared/hydra_editor_utils.py +++ /dev/null @@ -1,340 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -import azlmbr.bus as bus -import azlmbr.editor as editor -import azlmbr.entity as entity -import azlmbr.object - -from typing import List -from math import isclose -import collections.abc - - -def find_entity_by_name(entity_name): - """ - Gets an entity ID from the entity with the given entity_name - :param entity_name: String of entity name to search for - :return entity ID - """ - search_filter = entity.SearchFilter() - search_filter.names = [entity_name] - matching_entity_list = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter) - if matching_entity_list: - matching_entity = matching_entity_list[0] - if matching_entity.IsValid(): - print(f'{entity_name} entity found with ID {matching_entity.ToString()}') - return matching_entity - else: - return matching_entity_list - - -def get_component_type_id(component_name): - """ - Gets the component_type_id from a given component name - :param component_name: String of component name to search for - :return component type ID - """ - type_ids_list = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', [component_name], entity.EntityType().Game) - component_type_id = type_ids_list[0] - return component_type_id - - -def add_component(componentName, entityId): - """ - Given a component name, finds component TypeId, adds to given entity, and verifies successful add/active state. - :param componentName: String of component name to add. - :param entityId: Entity to add component to. - :return: Component object. - """ - typeIdsList = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', [componentName], entity.EntityType().Game) - typeNamesList = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeNames', typeIdsList) - componentOutcome = editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', entityId, typeIdsList) - isActive = editor.EditorComponentAPIBus(bus.Broadcast, 'IsComponentEnabled', componentOutcome.GetValue()[0]) - hasComponent = editor.EditorComponentAPIBus(bus.Broadcast, 'HasComponentOfType', entityId, typeIdsList[0]) - if componentOutcome.IsSuccess() and isActive: - print('{} component was added to entity'.format(typeNamesList[0])) - elif componentOutcome.IsSuccess() and not isActive: - print('{} component was added to entity, but the component is disabled'.format(typeNamesList[0])) - elif not componentOutcome.IsSuccess(): - print('Failed to add {} component to entity'.format(typeNamesList[0])) - if hasComponent: - print('Entity has a {} component'.format(typeNamesList[0])) - return componentOutcome.GetValue()[0] - - -def get_component_property_value(component, component_propertyPath): - """ - Given a component name and component property path, outputs the property's value. - :param component: Component object to act on. - :param componentPropertyPath: String of component property. (e.g. 'Settings|Visible') - :return: Value set in given componentPropertyPath - """ - componentPropertyObj = editor.EditorComponentAPIBus(bus.Broadcast, 'GetComponentProperty', component, - component_propertyPath) - if componentPropertyObj.IsSuccess(): - componentProperty = componentPropertyObj.GetValue() - print(f'{component_propertyPath} set to {componentProperty}') - return componentProperty - else: - print(f'FAILURE: Could not get value from {component_propertyPath}') - return None - - -def get_property_tree(component): - """ - Given a configured component object, prints the property tree info from that component - :param component: Component object to act on. - """ - pteObj = editor.EditorComponentAPIBus(bus.Broadcast, 'BuildComponentPropertyTreeEditor', component) - pte = pteObj.GetValue() - print(pte.build_paths_list()) - return pte - - -def compare_values(first_object: object, second_object: object, name: str) -> bool: - # Quick case - can we just directly compare the two objects successfully? - if (first_object == second_object): - result = True - # No, so get a lot more specific - elif isinstance(first_object, collections.abc.Container): - # If they aren't both containers, they're different - if not isinstance(second_object, collections.abc.Container): - result = False - # If they have different lengths, they're different - elif len(first_object) != len (second_object): - result = False - # If they're different strings, they're containers but they failed the == check so - # we know they're different - elif isinstance(first_object, str): - result = False - else: - # It's a collection of values, so iterate through them all... - collection_idx = 0 - result = True - for val1, val2 in zip(first_object, second_object): - result = result and compare_values(val1, val2, f"{name} (index [{collection_idx}])") - collection_idx = collection_idx + 1 - - else: - # Do approximate comparisons for floats - if isinstance(first_object, float) and isclose(first_object, second_object, rel_tol=0.001): - result = True - # We currently don't have a generic way to compare PythonProxyObject contents, so return a - # false positive result for now. - elif isinstance(first_object, azlmbr.object.PythonProxyObject): - print(f"{name}: validation inconclusive, the two objects cannot be directly compared.") - result = True - else: - result = False - - if not result: - print(f"compare_values failed: {first_object} ({type(first_object)}) vs {second_object} ({type(second_object)})") - - print(f"{name}: {'SUCCESS' if result else 'FAILURE'}") - return result - - -class Entity: - """ - Entity class used to create entity objects - :param name: String for the name of the Entity - :param id: The ID of the entity - """ - - def __init__(self, name: str, id: object = entity.EntityId()): - self.name: str = name - self.id: object = id - self.components: List[object] = None - self.parent_id = None - self.parent_name = None - - def create_entity(self, entity_position, components, parent_id=entity.EntityId()): - self.id = editor.ToolsApplicationRequestBus( - bus.Broadcast, "CreateNewEntityAtPosition", entity_position, entity.EntityId() - ) - if self.id.IsValid(): - print(f"{self.name} Entity successfully created") - editor.EditorEntityAPIBus(bus.Event, 'SetName', self.id, self.name) - self.components = [] - for component in components: - new_component = add_component(component, self.id) - self.components.append(new_component) - - def get_parent_info(self): - """ - Sets the value for parent_id and parent_name on the entity (self) - Prints the string for papertrail - :return: None - """ - self.parent_id = editor.EditorEntityInfoRequestBus(bus.Event, "GetParent", self.id) - self.parent_name = editor.EditorEntityInfoRequestBus(bus.Event, "GetName", self.parent_id) - print(f"The parent entity of {self.name} is {self.parent_name}") - - def set_test_parent_entity(self, parent_entity_obj): - editor.EditorEntityAPIBus(bus.Event, "SetParent", self.id, parent_entity_obj.id) - self.get_parent_info() - - def get_set_test(self, component_index: int, path: str, value: object, expected_result: object = None) -> bool: - """ - Used to set and validate changes in component values - :param component_index: Index location in the self.components list - :param path: asset path in the component - :param value: new value for the variable being changed in the component - :param expected_result: (optional) check the result against a specific expected value - """ - - if expected_result is None: - expected_result = value - - # Test Get/Set (get old value, set new value, check that new value was set correctly) - print(f"Entity {self.name} Path {path} Component Index {component_index} ") - - component = self.components[component_index] - old_value = get_component_property_value(component, path) - - if old_value is not None: - print(f"SUCCESS: Retrieved property Value for {self.name}") - else: - print(f"FAILURE: Failed to find value in {self.name} {path}") - return False - - if old_value == expected_result: - print((f"WARNING: get_set_test on {self.name} is setting the same value that already exists ({old_value})." - "The set results will be inconclusive.")) - - editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", component, path, value) - - new_value = get_component_property_value(self.components[component_index], path) - - if new_value is not None: - print(f"SUCCESS: Retrieved new property Value for {self.name}") - else: - print(f"FAILURE: Failed to find new value in {self.name}") - return False - - return compare_values(new_value, expected_result, f"{self.name} {path}") - - -def get_set_test(entity: object, component_index: int, path: str, value: object) -> bool: - """ - Used to set and validate changes in component values - :param component_index: Index location in the entity.components list - :param path: asset path in the component - :param value: new value for the variable being changed in the component - """ - return entity.get_set_test(component_index, path, value) - - -def get_set_property_test(ly_object: object, attribute_name: str, value: object, expected_result: object = None) -> bool: - """ - Used to set and validate BehaviorContext property changes in Lumberyard objects - :param ly_object: The lumberyard object to test - :param attribute_name: property (attribute) name in the BehaviorContext - :param value: new value for the variable being changed in the component - :param expected_result: (optional) check the result against a specific expected value other than the one set - """ - - if expected_result is None: - expected_result = value - - # Test Get/Set (get old value, set new value, check that new value was set correctly) - print(f"Attempting to set {ly_object.typename}.{attribute_name} = {value} (expected result is {expected_result})") - - if hasattr(ly_object, attribute_name): - print(f"SUCCESS: Located attribute {attribute_name} for {ly_object.typename}") - else: - print(f"FAILURE: Failed to find attribute {attribute_name} in {ly_object.typename}") - return False - - old_value = getattr(ly_object, attribute_name) - - if old_value is not None: - print(f"SUCCESS: Retrieved existing value {old_value} for {attribute_name} in {ly_object.typename}") - else: - print(f"FAILURE: Failed to retrieve value for {attribute_name} in {ly_object.typename}") - return False - - if old_value == expected_result: - print((f"WARNING: get_set_test on {attribute_name} is setting the same value that already exists ({old_value})." - "The 'set' result for the test will be inconclusive.")) - - setattr(ly_object, attribute_name, expected_result) - - new_value = getattr(ly_object, attribute_name) - - if new_value is not None: - print(f"SUCCESS: Retrieved new value {new_value} for {attribute_name} in {ly_object.typename}") - else: - print(f"FAILURE: Failed to retrieve value for {attribute_name} in {ly_object.typename}") - return False - - return compare_values(new_value, expected_result, f"{ly_object.typename}.{attribute_name}") -def has_components(entity_id: object, component_list: list) -> bool: - """ - Used to verify if a given entity has all the components of components_list. Returns True if all the - components are present, else False - :param entity_id: entity id of the entity - :param component_list: list of component names to be verified - """ - typeIdsList = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', component_list , entity.EntityType().Game) - for type_id in typeIdsList: - if not editor.EditorComponentAPIBus(bus.Broadcast, 'HasComponentOfType', entity_id, type_id): - return False - return True - -class PathNotFoundError(Exception): - def __init__(self, path): - self.path = path - - def __str__(self): - return f"Path \"{self.path}\" not found in Editor Settings" - -def get_editor_settings_path_list(): - """ - Get the list of Editor Settings paths - """ - paths = editor.EditorSettingsAPIBus(bus.Broadcast, 'BuildSettingsList') - return paths - -def get_editor_settings_by_path(path): - """ - Get the value of Editor Settings based on the path. - :param path: path to the Editor Settings to get the value - """ - if path not in get_editor_settings_path_list(): - raise PathNotFoundError(path) - outcome = editor.EditorSettingsAPIBus(bus.Broadcast, 'GetValue', path) - if outcome.isSuccess(): - return outcome.GetValue() - raise RuntimeError(f"GetValue for path '{path}' failed") - -def set_editor_settings_by_path(path, value, is_bool = False): - """ - Set the value of Editor Settings based on the path. - # NOTE: Some Editor Settings may need an Editor restart to apply. - # Ex: Enabling or disabling New Viewport Interaction Model - :param path: path to the Editor Settings to get the value - :param value: value to be set - :param is_bool: True for Boolean settings (enable/disable), False for other settings - """ - if path not in get_editor_settings_path_list(): - raise PathNotFoundError(path) - if is_bool and not isinstance(value, bool): - def ParseBoolValue(value): - if(value == "0"): - return False - return True - value = ParseBoolValue(value) - outcome = editor.EditorSettingsAPIBus(bus.Broadcast, 'SetValue', path, value) - if not outcome.isSuccess(): - raise RuntimeError(f"SetValue for path '{path}' failed") - print(f"Value for path '{path}' is set to {value}") diff --git a/Tests/ly_shared/hydra_lytt_test_utils.py b/Tests/ly_shared/hydra_lytt_test_utils.py deleted file mode 100755 index bbdcbc6cb6..0000000000 --- a/Tests/ly_shared/hydra_lytt_test_utils.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -import logging -import os -import tempfile -import ly_test_tools.log.log_monitor -import ly_test_tools.environment.process_utils as process_utils -import ly_test_tools.environment.waiter as waiter - -logger = logging.getLogger(__name__) - - -def teardown_editor(editor): - """ - :param editor: Configured editor object - :return: - """ - process_utils.kill_processes_named('AssetProcessor.exe') - logger.debug('Ensuring Editor is stopped') - editor.ensure_stopped() - - -def launch_and_validate_results(request, test_directory, editor, editor_script, expected_lines, unexpected_lines=[], - halt_on_unexpected=False, auto_test_mode=True, run_python="--runpythontest", cfg_args=[], timeout=60, log_creation_max_wait=60): - """ - Creates a temporary config file for Hydra execution, runs the Editor with the specified script, and monitors for - expected log lines. - :param request: Special fixture providing information of the requesting test function. - :param test_directory: Path to test directory that editor_script lives in. - :param editor: Configured editor object to run test against. - :param editor_script: Name of script that will execute in the Editor. - :param expected_lines: Expected lines to search log for. - :param unexpected_lines: Unexpected lines to search log for. Defaults to none. - :param halt_on_unexpected: Halts test if unexpected lines are found. Defaults to False. - :param auto_test_mode: Defaults to True. Runs the test in auto_test_mode. - :param run_python: Defaults to "--runpythontest", other option is "--runpython". - :param cfg_args: Additional arguments for CFG, such as LevelName. - :param timeout: Length of time for test to run. Default is 60. - :param log_creation_max_wait: Length of time for waiting to find the log file. Default is 60. - """ - test_case = os.path.join(test_directory, editor_script) - request.addfinalizer(lambda: teardown_editor(editor)) - logger.debug("Running automated test: {}".format(editor_script)) - - editor.args.extend(["--skipWelcomeScreenDialog"]) - if auto_test_mode: editor.args.extend(["--autotest_mode"]) - editor.args.extend([run_python, test_case, "--runpythonargs", cfg_args]) - - with editor.start(): - - editorlog_file = os.path.join(editor.workspace.paths.project_log(), 'Editor.log') - log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=editor, log_file_path=editorlog_file, log_creation_max_wait_time=log_creation_max_wait) - log_monitor.monitor_log_for_lines(expected_lines=expected_lines, unexpected_lines=unexpected_lines, - halt_on_unexpected=halt_on_unexpected, timeout=timeout) - - -def remove_files(artifact_path, suffix): - """ - Removes files with the specified suffix from the specified path - :param artifact_path: Path to search for files - :param suffix: File extension to remove - """ - if not os.path.isdir(artifact_path): - return - - for file_name in os.listdir(artifact_path): - if file_name.endswith(suffix): - os.remove(os.path.join(artifact_path, file_name)) diff --git a/Tests/ly_shared/network_utils.py b/Tests/ly_shared/network_utils.py deleted file mode 100755 index 8b772303f1..0000000000 --- a/Tests/ly_shared/network_utils.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -import logging -import psutil -import socket - - -logger = logging.getLogger(__name__) - - -def check_for_listening_port(port): - """ - Checks to see if the connection to the designated port was established. - :param port: Port to listen to. - :return: True if port is listening. - """ - port_listening = False - for conn in psutil.net_connections(): - if 'port={}'.format(port) in str(conn): - port_listening = True - return port_listening - - -def check_for_remote_listening_port(port, ip_addr='127.0.0.1'): - """ - Tries to connect to a port to see if port is listening. - :param port: Port being tested. - :param ip_addr: IP address of the host being connected to. - :return: True if connection to the port is established. - """ - port_listening = True - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - sock.connect((ip_addr, port)) - except socket.error as err: - # Socket error: Connection refused, error code 10061 - if err.errno == 10061: - port_listening = False - finally: - sock.close() - return port_listening - - -def get_local_ip_address(): - """ - Finds the IP address for the primary ethernet adapter by opening a connection and grabbing its IP address. - :return: The IP address for the adapter used to make the connection. - """ - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - try: - # Connecting to Google's public DNS so there is an open connection - # and then getting the address used for that connection - sock.connect(('8.8.8.8', 80)) - host_ip = sock.getsockname()[0] - finally: - sock.close() - return host_ip diff --git a/Tests/ly_shared/phase.py b/Tests/ly_shared/phase.py deleted file mode 100755 index 4bb827b3ad..0000000000 --- a/Tests/ly_shared/phase.py +++ /dev/null @@ -1,198 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -This represents one "phase" of a test. The test runner communicates with the launcher by running phases and waiting -for results, however they appear. -""" - -import logging -import os -import time -import xml.etree.ElementTree - -import ly_test_tools.launchers.exceptions as exceptions -from ly_test_tools.launchers.platforms.base import Launcher - -_POLL_INTERVAL_SEC = 1 -_CRASH_TIMEOUT = 5 - -logger = logging.getLogger(__name__) - - -class Phase(object): - """ - A generic test phase for running the launcher. With the launcher running elsewhere (at a minimum in a different - process, possibly on a different device altogether) the following kind of phases might occur: - - - Wait for a specific file to show up / complete processing. - - Send commands to the launcher over the network and wait for response. - - Wait for multiple launchers to coordinate networking testing. - - Wait for a specific amount of time. - - Each phase can then compile the artifacts for the next phase. - """ - def __init__(self, timeout): - """ - :param timeout: Maximum time allocated for phase. - """ - self.timeout = timeout - - def _start(self, previous_phase=None): - """ - Start the phase. - - :return: None - """ - logger.debug("start: {}".format(self.__class__.__name__)) - - def _is_complete(self): - """ - Check if the phase is complete. This is the only required function. - - :return: None - """ - raise NotImplementedError - - def _compile_artifacts(self): - """ - Compile artifacts after a completed phase. - """ - logger.debug("compile_artifacts: {}".format(self.__class__.__name__)) - - def _update(self, elapsed_time): - """ - Update the test phase if necessary. - - :param elapsed_time: Time since the last update. - :return: None - """ - logger.debug("update: {}".format(self.__class__.__name__)) - - def _wait(self, launcher): - """ - Wait for the phase to complete. - - :return: None. - :raises: TimeoutError, CrashError - """ - dead_time = -1 - logger.debug("wait begin: {}".format(self.__class__.__name__)) - start = time.time() - - while not self._is_complete(): - - if time.time() - start > self.timeout: - message = "Timeout exceeded {}s in {}".format(self.timeout, self.__class__.__name__) - logger.error(message) - raise exceptions.TimeoutError(message) - elif not launcher.is_alive(): - # The final result may arrive after the app closes. - if dead_time == -1: - dead_time = time.time() - if time.time() - dead_time > _CRASH_TIMEOUT: - message = "Unexpected termination in {} after {:0.2f}s".format( - self.__class__.__name__, time.time() - start) - logger.error(message) - raise exceptions.CrashError(message) - - sleep_start = time.time() - time.sleep(_POLL_INTERVAL_SEC) - - self._update(time.time() - sleep_start) - - logger.debug("wait end: {}, duration: {:.2f}".format(self.__class__.__name__, time.time() - start)) - - -class FileExistsPhase(Phase): - """ - Test phase that completes when a specific file is created. - """ - def __init__(self, path, timeout=60, non_empty=False): - super(FileExistsPhase, self).__init__(timeout) - self.path = path - self.non_empty = non_empty - - def _is_complete(self): - if self.path is not None and os.path.exists(self.path): - if self.non_empty: - return os.path.getsize(self.path) > 0 - else: - return True - - return False - - -class XMLValidPhase(FileExistsPhase): - """ - Test phase that completes when a valid XML file is found. - """ - def __init__(self, path, timeout=60): - super(XMLValidPhase, self).__init__(path, timeout, non_empty=True) - self.path = path - self.xml = None - - def _is_complete(self): - if not super(XMLValidPhase, self)._is_complete(): - return False - - try: - self.xml = xml.etree.ElementTree.parse(self.path) - except xml.etree.ElementTree.ParseError: - return False - - return True - - -class TimePhase(Phase): - """ - Simple class to complete in a specified time. Can be used to test timeout. - """ - def __init__(self, timeout, complete_time): - super(TimePhase, self).__init__(timeout) - self.complete_time = complete_time - - def _start(self, previous_phase=None): - super(TimePhase, self)._start(previous_phase) - self.start_time = time.time() - - def _is_complete(self): - return time.time() - self.start_time > self.complete_time - - -class ElapsedTimePhase(Phase): - """ - Simple class to complete in a specified time using elapsed time. Can be used to test timeout and elapsed time. - """ - def __init__(self, timeout, complete_time): - super(ElapsedTimePhase, self).__init__(timeout) - self.complete_time = complete_time - self.total_time = None - - def _start(self, previous_phase=None): - super(ElapsedTimePhase, self)._start(previous_phase) - self.total_time = 0 - - def _update(self, elapsed_time): - super(ElapsedTimePhase, self)._update(elapsed_time) - self.total_time += elapsed_time - - def _is_complete(self): - return self.total_time > self.complete_time - - -class WaitForLauncherToQuit(Phase): - def __init__(self, launcher, timeout=60): - # type: (Launcher, int) -> None - super(WaitForLauncherToQuit, self).__init__(timeout) - self.launcher = launcher - - def _is_complete(self): - # type: () -> bool - return not self.launcher.is_alive() diff --git a/Tests/ly_shared/pyside_utils.py b/Tests/ly_shared/pyside_utils.py deleted file mode 100755 index 1dfdcdb5c0..0000000000 --- a/Tests/ly_shared/pyside_utils.py +++ /dev/null @@ -1,939 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -import azlmbr.qt -import azlmbr.qt_helpers -import asyncio -import re -from shiboken2 import wrapInstance, getCppPointer -from PySide2 import QtCore, QtWidgets, QtGui, QtTest -from PySide2.QtWidgets import QAction, QWidget -from PySide2.QtCore import Qt -from PySide2.QtTest import QTest -import azlmbr.legacy.general as general -import traceback -import threading -import types - - -qApp = QtWidgets.QApplication.instance() -# Monkey patch static method calls -QtWidgets.QApplication.activeModalWidget = qApp.activeModalWidget - - -class LmbrQtEventLoop(asyncio.AbstractEventLoop): - def __init__(self): - self.running = False - self.shutdown = threading.Event() - self.blocked_events = set() - self.finished_events = set() - self.queue = [] - self._wait_future = None - self._event_loop_nesting = 0 - - def get_debug(self): - return False - - def time(self): - return azlmbr.qt_helpers.time() - - def wait_for_condition(self, condition, action, on_timeout=None, timeout=1.0): - timeout = self.time() + timeout if timeout is not None else None - def callback(time): - # Run our action and remove us from the queue if our condition is satisfied - if condition(): - action() - return True - # Give up if timeout has elapsed - if time > timeout: - if on_timeout is not None: - on_timeout() - return True - return False - self.queue.append((callback)) - - def event_loop(self): - time = self.time() - def run_event(event): - if event in self.blocked_events or event in self.finished_events: - return False - self.blocked_events.add(event) - try: - if event(time): - self.finished_events.add(event) - except Exception: - traceback.print_exc() - self.finished_events.add(event) - finally: - self.blocked_events.remove(event) - - self._event_loop_nesting += 1 - try: - for event in self.queue: - run_event(event) - finally: - self._event_loop_nesting -= 1 - - # Clear out any finished events if the queue is safe to mutate - if self._event_loop_nesting == 0: - self.queue = [event for event in self.queue if event not in self.finished_events] - self.finished_events = set() - - if not self.running or self._wait_future is not None and self._wait_future.done(): - self.close() - - def run_until_shutdown(self): - # Run our event loop callback (via azlmbr.qt_helpers) by pumping the Qt event loop - # azlmbr.qt_helpers will attempt to ensure our event loop is always run, even when a - # new event loop is started and run from the main event loop - self.running = True - self.shutdown.clear() - azlmbr.qt_helpers.set_loop_callback(self.event_loop) - while not self.shutdown.is_set(): - qApp.processEvents(QtCore.QEventLoop.AllEvents, 0) - - def run_forever(self): - self._wait_future = None - self.run_until_shutdown() - - def run_until_complete(self, future): - # Wrap coroutines into Tasks (future-like analogs) - if isinstance(future, types.CoroutineType): - future = self.create_task(future) - self._wait_future = future - self.run_until_shutdown() - - def _timer_handle_cancelled(self, handle): - pass - - def is_running(self): - return self.running - - def is_closed(self): - return not azlmbr.qt_helpers.loop_is_running() - - def stop(self): - self.running = False - - def close(self): - self.running = False - self.shutdown.set() - azlmbr.qt_helpers.clear_loop_callback() - - def shutdown_asyncgens(self): - pass - - def call_exception_handler(self, context): - try: - raise context.get('exception', None) - except: - traceback.print_exc() - - def call_soon(self, callback, *args, **kw): - h = asyncio.Handle(callback, args, self) - def callback_wrapper(time): - if not h.cancelled(): - h._run() - return True - self.queue.append(callback_wrapper) - return h - - def call_later(self, delay, callback, *args, **kw): - if delay < 0: - raise Exception("Can't schedule in the past") - return self.call_at(self.time() + delay, callback, *args) - - def call_at(self, when, callback, *args, **kw): - h = asyncio.TimerHandle(when, callback, args, self) - h._scheduled = True - def callback_wrapper(time): - if time > when: - if not h.cancelled(): - h._run() - return True - return False - self.queue.append(callback_wrapper) - return h - - def create_task(self, coro): - return asyncio.Task(coro, loop=self) - - def create_future(self): - return asyncio.Future(loop=self) - - -class EventLoopTimeoutException(Exception): - pass - - -event_loop = LmbrQtEventLoop() -def wait_for_condition(condition, timeout=1.0): - """ - Asynchronously waits for `condition` to evaluate to True. - condition: A function with the signature def condition() -> bool - This condition will be evaluated until it evaluates to True or the timeout elapses - timeout: The time in seconds to wait - if 0, this will wait forever - Throws pyside_utils.EventLoopTimeoutException on timeout. - """ - future = event_loop.create_future() - def on_complete(): - future.set_result(True) - def on_timeout(): - future.set_exception(EventLoopTimeoutException()) - event_loop.wait_for_condition(condition, on_complete, on_timeout=on_timeout, timeout=timeout) - return future - - -async def wait_for(expression, timeout=1.0): - """ - Asynchronously waits for "expression" to evaluate to a non-None value, - then returns that value. - - expression: A function with the signature def expression() -> Generic[Any,None] - The result of expression will be returned as soon as it returns a non-None value. - timeout: The time in seconds to wait - if 0, this will wait forever - Throws pyside_utils.EventLoopTimeoutException on timeout. - """ - result = None - def condition(): - nonlocal result - result = expression() - return result is not None - await wait_for_condition(condition, timeout) - return result - - -def run_soon(fn): - """ - Runs a function on the event loop to enable asynchronous execution. - - fn: The function to run, should be a function that takes no arguments - Returns a future that will be popualted with the result of fn or the exception it threw. - """ - future = event_loop.create_future() - def coroutine(): - try: - fn() - future.set_result(True) - except Exception as e: - future.set_exception(e) - event_loop.call_soon(coroutine) - return future - - -def run_async(awaitable): - """ - Synchronously runs a coroutine or a future on the event loop. - This can be used in lieu of "await" in non-async functions. - - awaitable: The coroutine or future to await. - Returns the result of operation specified. - """ - if isinstance(awaitable, types.CoroutineType): - awaitable = event_loop.create_task(awaitable) - event_loop.run_until_complete(awaitable) - return awaitable.result() - - -def wrap_async(fn): - """ - This decorator enables an async function's execution from a synchronous one. - - For example: - @pyside_utils.wrap_async - async def foo(): - result = await long_operation() - return result - - def non_async_fn(): - x = foo() # this will return the correct result by executing the event loop - - fn: The function to wrap - Returns the decorated function. - """ - def wrapper(*args, **kw): - result = fn(*args, **kw) - return run_async(result) - return wrapper - - -def get_editor_main_window(): - """ - Fetches the main Editor instance of QMainWindow for use with PySide tests - :return Instance of QMainWindow for the Editor - """ - params = azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, "GetQtBootstrapParameters") - editor_id = QtWidgets.QWidget.find(params.mainWindowId) - main_window = wrapInstance(int(getCppPointer(editor_id)[0]), QtWidgets.QMainWindow) - return main_window - - -def get_action_for_menu_path(editor_window: QtWidgets.QMainWindow, main_menu_item: str, *menu_item_path: str): - """ - main_menu_item: Main menu item among the MenuBar actions. Ex: "File" - menu_item_path: Path to any nested menu item. Ex: "Viewport", "Goto Coordinates" - returns: QAction object for the corresponding path. - """ - # Check if path is valid - menu_bar = editor_window.menuBar() - menu_bar_actions = [index.iconText() for index in menu_bar.actions()] - - # Verify if the given Menu exists in the Menubar - if main_menu_item not in menu_bar_actions: - print(f"QAction not found for main menu item '{main_menu_item}'") - return None - curr_action = menu_bar.actions()[menu_bar_actions.index(main_menu_item)] - curr_menu = curr_action.menu() - for index, element in enumerate(menu_item_path): - curr_menu_actions = [index.iconText() for index in curr_menu.actions()] - if element not in curr_menu_actions: - print(f"QAction not found for menu item '{element}'") - return None - if index == len(menu_item_path) - 1: - return curr_menu.actions()[curr_menu_actions.index(element)] - curr_action = curr_menu.actions()[curr_menu_actions.index(element)] - curr_menu = curr_action.menu() - return None - - -def _pattern_to_dict(pattern, **kw): - """ - Helper function, turns a pattern match parameter into a normalized dictionary - """ - - def is_string_or_regex(x): - return isinstance(x, str) or isinstance(x, re.Pattern) - - # If it's None, just make an empty dict - if pattern is None: - pattern = {} - # If our pattern is a string or regex, turn it into a text match - elif is_string_or_regex(pattern): - pattern = dict(text=pattern) - # If our pattern is an (int, int) tuple, turn it into a row/column match - elif isinstance(pattern, tuple) and isinstance(pattern[0], int) and isinstance(pattern[1], int): - pattern = dict(row=pattern[0], column=pattern[1]) - # If our pattern is a QObject type, turn it into a type match - elif isinstance(pattern, type(QtCore.QObject)): - pattern = dict(type=pattern) - # Otherwise assume it's a dict and make a copy - else: - pattern = dict(pattern) - - # Merge with any kw arguments - for key, value in kw.items(): - pattern[key] = value - return pattern - - -def _match_pattern(obj, pattern): - """ - Helper function, determines whether obj matches the pattern specified by pattern. - - It is required that pattern is normalized into a dict before calling this. - """ - - def compare(value1, value2): - # Do a regex search if it's a regex, otherwise do a normal compare - if isinstance(value2, re.Pattern): - return re.search(value2, value1) - return value1 == value2 - - item_roles = Qt.ItemDataRole.values.values() - for key, value in pattern.items(): - if key == "type": # Class type - if not isinstance(obj, value): - return False - elif key == "text": # Default 'text' path, depends on type - text_values = [] - - def get_from_attrs(*args): - for attr in args: - try: - text_values.append(getattr(obj, attr)()) - except Exception: - pass - - # Use any of the following fields for default matching, if they're defined - get_from_attrs("text", "objectName", "windowTitle") - # Additionally, use the DisplayRole for QModelIndexes - if isinstance(obj, QtCore.QModelIndex): - text_values.append(obj.data(Qt.DisplayRole)) - - if not any(compare(text, value) for text in text_values): - return False - elif key in item_roles: # QAbstractItemModel display role - if not isinstance(obj, QtCore.QModelIndex): - raise RuntimeError(f"Attempted to match data role on unsupported object {obj}") - if not compare(obj.data(key), value): - return False - elif hasattr(obj, key): - # Look up our key on the object itself - objectValue = getattr(obj, key) - # Invoke it if it's a getter - if callable(objectValue): - objectValue = objectValue() - if not compare(objectValue, value): - return False - else: - return False - - return True - - -def get_child_indexes(model, parent_index=QtCore.QModelIndex()): - indexes = [parent_index] - while len(indexes) > 0: - parent_index = indexes.pop(0) - for row in range(model.rowCount(parent_index)): - # FIXME - # PySide appears to have a bug where-in it thinks columnCount is private - # Bail gracefully for now, we can add a C++ wrapper to work around if needed - try: - column_count = model.columnCount(parent_index) - except Exception: - column_count = 1 - for col in range(column_count): - cur_index = model.index(row, col, parent_index) - yield cur_index - - -def _get_children(obj): - """ - Helper function. Get the direct descendants from a given PySide object. - This includes all: QObject children, QActions owned by the object, and QModelIndexes if applicable - """ - if isinstance(obj, QtCore.QObject): - yield from obj.children() - if isinstance(obj, QtWidgets.QWidget): - yield from obj.actions() - if isinstance(obj, (QtWidgets.QAbstractItemView, QtCore.QModelIndex)): - model = obj.model() - if model is None: - return - - # For a QAbstractItemView (e.g. QTreeView, QListView), the parent index - # will be an invalid QModelIndex(), which will use find all indexes on the root. - # For a QModelIndex, we use the actual QModelIndex as the parent_index so that - # it will find any child indexes under it - parent_index = QtCore.QModelIndex() - if isinstance(obj, QtCore.QModelIndex): - parent_index = obj - - yield from get_child_indexes(model, parent_index) - - -def _get_parents_to_search(obj_entry_or_list): - """ - Helper function, turns obj_entry_or_list into a list of parents to search - - If obj_entry_or_list is None, returns all visible top level widgets - If obj_entry_or_list is iterable, return it as a list - Otherwise, return a list containing obj_entry_or_list - """ - if obj_entry_or_list is None: - return [widget for widget in QtWidgets.QApplication.topLevelWidgets() if widget.isVisible()] - try: - return list(obj_entry_or_list) - except TypeError: - return [obj_entry_or_list] - - -def find_children_by_pattern(obj=None, pattern=None, recursive=True, **kw): - """ - Finds the children of an object that match a given pattern. - See find_child_by_pattern for more information on usage. - """ - pattern = _pattern_to_dict(pattern, **kw) - parents_to_search = _get_parents_to_search(obj) - - while len(parents_to_search) > 0: - parent = parents_to_search.pop(0) - for child in _get_children(parent): - if _match_pattern(child, pattern): - yield child - if recursive: - parents_to_search.append(child) - - -def find_child_by_pattern(obj=None, pattern=None, recursive=True, **kw): - """ - Finds the child of an object that matches a given pattern. - A "child" in this context is not necessarily a QObject child. - QActions are also considered children, as are the QModelIndex children of QAbstractItemViews. - obj: The object to search - should be either a QObject or a QModelIndex, or a list of them - If None this will search all top level windows. - pattern: The pattern to match, the first child that matches all of the criteria specified will - be returned. This is a dictionary with any combination of the following: - - - "text": generic text to match, will search object names for QObjects, display role text - for QModelIndexes, or action text() for QActions - - "type": a class type, e.g. QtWidgets.QMenu, a child will only match if it's of this type - - "row" / "column": integer row and column indices of a QModelIndex - - "type": type class (e.g. PySide.QtWidgets.QComboBox) that the object must inherit from - - A Qt.ItemDataRole: matches for QModelIndexes with data of a given value - - Any other fields will fall back on being looked up on the object itself by name, e.g. - {"windowTitle": "Foo"} would match a windowTitle named "Foo" - - Any instances where a field is specified as text can also be specified as a regular expression: - find_child_by_pattern(obj, {text: re.compile("Foo_.*")}) would find a child with text starting - with "Foo_" - - For convenience, these parameter types may also be specified as keyword arguments: - find_child_by_pattern(obj, text="foo", type=QtWidgets.QAction) - is equivalent to - find_child_by_pattern(obj, {"text": "foo", "type": QtWidgets.QAction}) - - If pattern is specified as a string, it will turn into a pattern matching "text": - find_child_by_pattern(obj, "foo") - is equivalent to - find_child_by_pattern(obj, {"text": "foo"}) - - If a pattern is specified as an (int, int) tuple, it will turn into a row/column match: - find_child_by_pattern(obj, (0, 2)) - is equivalent to - find_child_by_pattern(obj, {"row": 0, "column": 2}) - - If a pattern is specified as a type, like PySide.QtWidgets.QLabel, it will turn into a type match: - find_child_by_pattern(obj, PySide.QtWidgets.QLabel) - is equivalent to - find_child_by_pattern(obj, {"type": PySide.QtWidgets.QLabel}) - """ - # Return the first match result, if found - for match in find_children_by_pattern(obj, pattern=pattern, recursive=recursive, **kw): - return match - return None - - -def find_child_by_hierarchy(parent, *patterns): - """ - Searches for a hierarchy of children descending from parent. - parent: The Qt object (or list of Qt obejcts) to search within - If none, this will search all top level windows. - patterns: A list of patterns to match to find a hierarchy of descendants. - These patterns will be tested in order. - - For example, to look for the QComboBox in a hierarchy like the following: - QWidget (window) - -QTabWidget - -QWidget named "m_exampleTab" - -QComboBox - One might invoke: - find_child_by_hierarchy(window, QtWidgets.QTabWidget, "m_exampleTab", QtWidgets.QComboBox) - - Alternatively, "..." may be specified in place of a parent, where the hierarchy will match any - ancestors along the path, so the above might be shortened to: - find_child_by_hierarchy(window, ..., "m_exampleTab", QtWidgets.QComboBox) - """ - search_recursively = False - current_objects = _get_parents_to_search(parent) - for pattern in patterns: - # If it's an ellipsis, do the next search recursively as we're looking for any number of intermediate ancestors - if pattern is ...: - search_recursively = True - continue - - candidates = [] - for parent_candidate in current_objects: - candidates += find_children_by_pattern(parent_candidate, pattern=pattern, recursive=search_recursively) - if len(candidates) == 0: - return None - current_objects = candidates - - search_recursively = False - return current_objects[0] - -async def wait_for_child_by_hierarchy(parent, *patterns, timeout=1.0): - """ - Searches for a hierarchy of children descending from parent until timeout occurs. - Returns a future that will result in either the found child or an EventLoopTimeoutException. - - See find_child_by_hierarchy for usage information. - """ - match = None - def condition(): - nonlocal match - match = find_child_by_hierarchy(parent, *patterns) - return match is not None - await wait_for_condition(condition, timeout) - return match - - -async def wait_for_child_by_pattern(obj=None, pattern=None, recursive=True, timeout=1.0, **kw): - """ - Finds the child of an object that matches a given pattern. - Returns a future that will result in either the found child or an EventLoopTimeoutException. - - See find_child_by_hierarchy for usage information. - """ - match = None - def condition(): - nonlocal match - match = find_child_by_pattern(obj, pattern, recursive, **kw) - return match is not None - await wait_for_condition(condition, timeout) - return match - - -def find_child_by_property(obj, obj_type, property_name, property_value, reg_exp_search=False): - """ - Finds the child of an object which has the property name matching the property value - of type obj_type - obj: The property value is searched through obj children - obj_type: Type of object to be matched - property_name: Property of the child which should be verified for the required value. - property_value: Property value that needs to be matched - reg_exp_search: If True searches for the property_value based on re search. Defaults to False. - """ - for child in obj.children(): - if reg_exp_search and re.search(property_value, getattr(child, property_name)()): - return child - if not reg_exp_search and isinstance(child, obj_type) and getattr(child, property_name)() == property_value: - return child - return None - -def get_item_view_index(item_view, row, column=0, parent=QtCore.QModelIndex()): - """ - Retrieve the index for a specified row/column, with optional parent - This is necessary when needing to reference into nested hierarchies in a QTreeView - item_view: The QAbstractItemView instance - row: The requested row index - column: The requested column index (defaults to 0 in case of single column) - parent: Parent index (defaults to invalid) - """ - item_model = item_view.model() - model_index = item_model.index(row, column, parent) - return model_index - - -def get_item_view_index_rect(item_view, index): - """ - Gets the QRect for a given index in a QAbstractItemView (e.g. QTreeView, QTableView, QListView). - This is helpful because for sending mouse events to a QAbstractItemView, you have to send them to - the viewport() widget of the QAbstractItemView. - item_view: The QAbstractItemView instance - index: A QModelIndex for the item index - """ - return item_view.visualRect(index) - - -def item_view_index_mouse_click(item_view, index, button=QtCore.Qt.LeftButton, modifier=QtCore.Qt.NoModifier): - """ - Helper method version of QTest.mouseClick for injecting mouse clicks on a QAbstractItemView - item_view: The QAbstractItemView instance - index: A QModelIndex for the item index to be clicked - """ - item_index_rect = get_item_view_index_rect(item_view, index) - item_index_center = item_index_rect.center() - - # For QAbstractItemView widgets, the events need to be forwarded to the actual viewport() widget - QTest.mouseClick(item_view.viewport(), button, modifier, item_index_center) - - -def item_view_mouse_click(item_view, row, column=0, button=QtCore.Qt.LeftButton, modifier=QtCore.Qt.NoModifier): - """ - Helper method version of 'item_view_index_mouse_click' using a row, column instead of a QModelIndex - item_view: The QAbstractItemView instance - row: The requested row index - column: The requested column index (defaults to 0 in case of single column) - """ - index = get_item_view_index(item_view, row, column) - item_view_index_mouse_click(item_view, index, button, modifier) - - -async def wait_for_action_in_menu(menu, pattern, timeout=1.0): - """ - Finds a QAction inside a menu, based on the specified pattern. - - menu: The QMenu to search - pattern: The action text or pattern to match (see find_child_by_pattern) - If pattern specifies a QWidget, this will search for the associated QWidgetAction - """ - action = await wait_for_child_by_pattern(menu, pattern, timeout=timeout) - if action is None: - raise TimeoutError(f"Failed to find context menu action for {pattern}") - - # If we've found a valid QAction, we're good to go - if hasattr(action, 'trigger'): - return action - - # If pattern matches a widget and not a QAction, look for an associated QWidgetAction - widget_actions = find_children_by_pattern(menu, type=QtWidgets.QWidgetAction) - underlying_widget_action = None - for widget_action in widget_actions: - widgets_to_check = [widget_action.defaultWidget()] + widget_action.createdWidgets() - for check_widget in widgets_to_check: - if action in _get_children(check_widget): - underlying_widget_action = widget_action - break - if underlying_widget_action is not None: - action = underlying_widget_action - break - - if not hasattr(action, 'trigger'): - raise RuntimeError(f"Failed to find action associated with widget {action}") - return action - - -def queue_hide_event(widget): - """ - Explicitly post a hide event for the next frame, this can be used to ensure modal dialogs exit correctly. - - widget: The widget to hide - """ - qApp.postEvent(widget, QtGui.QHideEvent()) - - -async def wait_for_destroyed(obj, timeout=1.0): - """ - Waits for a QObject (including a widget) to be fully destroyed - - This can be used to wait for a modal dialog to shut down properly - - obj: The object to wait on destruction - timeout: The time, in seconds to wait. 0 for an indefinite wait. - """ - was_destroyed = False - def on_destroyed(): - nonlocal was_destroyed - was_destroyed = True - obj.destroyed.connect(on_destroyed) - return await wait_for_condition(lambda: was_destroyed, timeout=timeout) - - -async def close_modal(modal_widget, timeout=1.0): - """ - Closes a modal dialog and waits for it to be cleaned up. - - This attempts to ensure the modal event loop gets properly exited. - - modal_widget: The widget to close - timeout: The time, in seconds, to wait. 0 for an indefinite wait. - """ - queue_hide_event(modal_widget) - return await wait_for_destroyed(modal_widget, timeout=timeout) - - -def trigger_context_menu_entry(widget, pattern, pos=None, index=None): - """ - Trigger a context menu event on a widget and activate an entry - widget: The widget to trigger the event on - pattern: The action text or pattern to match (see find_child_by_pattern) - pos: Optional, the QPoint to set as the event origin - index: Optional, the QModelIndex to click in widget - widget must be a QAbstractItemView - """ - async def async_wrapper(): - menu = await open_context_menu(widget, pos=pos, index=index) - action = await wait_for_action_in_menu(menu, pattern) - action.trigger() - queue_hide_event(menu) - - result = async_wrapper() - # If we have an event loop, go ahead and just return the coroutine - # Otherwise, do a synchronous wait - if event_loop.is_running(): - return result - else: - return run_async(result) - - -async def open_context_menu(widget, pos=None, index=None, timeout=1.0): - """ - Trigger a context menu event on a widget - widget: The widget to trigger the event on - pos: Optional, the QPoint to set as the event origin - index: Optional, the QModelIndex to click in widget - widget must be a QAbstractItemView - - Returns the menu that was created. - """ - if index is not None: - if pos is not None: - raise RuntimeError("Error: 'index' and 'pos' are mutually exclusive") - pos = widget.visualRect(index).center() - parent = widget - widget = widget.viewport() - pos = widget.mapFrom(parent, pos) - if pos is None: - pos = widget.rect().center() - - # Post both a mouse event and a context menu to let the widget handle whichever is appropriate - qApp.postEvent(widget, QtGui.QContextMenuEvent(QtGui.QContextMenuEvent.Mouse, pos)) - QtTest.QTest.mouseClick(widget, Qt.RightButton, Qt.NoModifier, pos) - - menu = None - # Wait for a menu popup - def menu_has_focus(): - nonlocal menu - for fw in [qApp.activePopupWidget(), qApp.activeModalWidget(), qApp.focusWidget(), qApp.activeWindow()]: - if fw and isinstance(fw, QtWidgets.QMenu) and fw.isVisible(): - menu = fw - return True - return False - await wait_for_condition(menu_has_focus, timeout) - return menu - - -def move_mouse(widget, position): - """ - Helper method to move the mouse to a specified position on a widget - widget: The widget to trigger the event on - position: The QPoint (local to widget) to move the mouse to - """ - # For some reason, Qt wouldn't register the mouse movement correctly unless both of these ways are invoked. - # The QTest.mouseMove seems to update the global cursor position, but doesn't always result in the MouseMove event being - # triggered, which prevents drag/drop being able to be simulated. - # Similarly, if only the MouseMove event is sent by itself to the core application, the global cursor position wasn't - # updated properly, so drag/drop logic that depends on grabbing the globalPos didn't work. - QtTest.QTest.mouseMove(widget, position) - event = QtGui.QMouseEvent(QtCore.QEvent.MouseMove, position, widget.mapToGlobal(position), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier) - QtCore.QCoreApplication.sendEvent(widget, event) - - -def drag_and_drop(source, target, source_point = QtCore.QPoint(), target_point = QtCore.QPoint()): - """ - Simulate a drag/drop event from a source object to a specified target - This has special case handling if the source is a QDockWidget (for docking) vs normal drag/drop - source: The source object to initiate the drag from - This is either a QWidget, or a tuple of (QAbstractItemView, QModelIndex) for dragging an item view item - target: The target object to drop on after dragging - This is either a QWidget, or a tuple of (QAbstractItemView, QModelIndex) for dropping on an item view item - source_point: Optional, The QPoint to initiate the drag from. If none is specified, the center of the source will be used. - target_point: Optional, The QPoint to drop on. If none is specified, the center of the target will be used. - """ - # Flag if this drag/drop is for docking, which has some special cases - docking = False - - # If the source is a tuple of (QAbstractItemView, QModelIndex), we need to use the - # viewport() as the source, and find the location of the index - if isinstance(source, tuple) and len(source) == 2: - source_item_view = source[0] - source_widget = source_item_view.viewport() - source_model_index = source[1] - source_rect = source_item_view.visualRect(source_model_index) - else: - # There are some special case actions if we are doing this drag for docking, - # so figure this out by checking if the source is a QDockWidget - if isinstance(source, QtWidgets.QDockWidget): - docking = True - - source_widget = source - source_rect = source.rect() - - # If the target is a tuple of (QAbstractItemView, QModelIndex), we need to use the - # viewport() as the target, and find the location of the index - if isinstance(target, tuple) and len(target) == 2: - target_item_view = target[0] - target_widget = target_item_view.viewport() - target_model_index = target[1] - target_rect = target_item_view.visualRect(target_model_index) - else: - # If we are doing a drag for docking, we actually want all the mouse events - # to still be directed through the source widget - if docking: - target_widget = source_widget - else: - target_widget = target - target_rect = target.rect() - - # If no source_point is specified, we need to find the center point of - # the source widget - if source_point.isNull(): - # If we are dragging for docking, initiate the drag from the center of the - # dock widget title bar - if docking: - title_bar_widget = source.titleBarWidget() - if title_bar_widget: - source_point = title_bar_widget.geometry().center() - else: - raise RuntimeError("No titleBarWidget found for QDockWidget") - # Otherwise, can just find the center of the rect - else: - source_point = source_rect.center() - - # If no target_point was specified, we need to find the center point of the target widget - if target_point.isNull(): - target_point = target_rect.center() - - # If we are dragging for docking and we aren't dragging within the same source/target, - # the mouse movements need to be directed to the source_widget, so we need to use the - # difference in global positions of our source and target widgets to adjust the target_point - # to be relative to the source - if docking and source != target: - source_top_left = source.mapToGlobal(QtCore.QPoint(0, 0)) - target_top_left = target.mapToGlobal(QtCore.QPoint(0, 0)) - offset = target_top_left - source_top_left - target_point += offset - - # Move the mouse to the source spot where we will start the drag - move_mouse(source_widget, source_point) - - # Press the left-mouse button to begin the drag - QtTest.QTest.mousePress(source_widget, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier, source_point) - - # If we are dragging for docking, we first need to drag the mouse past the minimum distance to - # trigger the docking system properly - if docking: - drag_distance = QtWidgets.QApplication.startDragDistance() + 1 - docking_trigger_point = source_point + QtCore.QPoint(drag_distance, drag_distance) - move_mouse(source_widget, docking_trigger_point) - - # Drag the mouse to the target widget over the desired point - move_mouse(target_widget, target_point) - - # Release the left-mouse button to complete the drop. - # If we are docking, we need to delay the actual mouse button release because the docking system has - # a delay before the drop zone becomes active after it has been hovered, which can be found here: - # FancyDockingDropZoneConstants::dockingTargetDelayMS = 110 ms - # So we need to delay greater than dockingTargetDelayMS after the final mouse move - # over the intended target. - delay = -1 - if docking: - delay = 200 - QtTest.QTest.mouseRelease(target_widget, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier, target_point, delay) - - # Some drag/drop events have extra processing on the following event tick, so let those processEvents - # first before we complete the drag/drop operation - QtWidgets.QApplication.processEvents() - - -def trigger_action_async(action): - """ - Convenience function. Triggers an action asynchronously. - This can be used if calling action.trigger might block (e.g. if it opens a modal dialog) - - action: The action to trigger - """ - return run_soon(lambda: action.trigger()) - - -def click_button_async(button): - """ - Convenience function. Clicks a button asynchronously. - This can be used if calling button.click might block (e.g. if it opens a modal dialog) - - button: The button to click - """ - return run_soon(lambda: button.click()) - - -async def wait_for_modal_widget(timeout=1.0): - """ - Waits for an active modal widget and returns it. - """ - return await wait_for(lambda: qApp.activeModalWidget(), timeout=timeout) - -async def wait_for_popup_widget(timeout=1.0): - """ - Waits for an active popup widget and returns it. - """ - return await wait_for(lambda: qApp.activePopupWidget(), timeout=timeout) \ No newline at end of file diff --git a/Tests/ly_shared/s3_utils.py b/Tests/ly_shared/s3_utils.py deleted file mode 100755 index 5c516094f3..0000000000 --- a/Tests/ly_shared/s3_utils.py +++ /dev/null @@ -1,131 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" -import pytest -pytest.importorskip("boto3") -import boto3 -import botocore.exceptions -import logging -import os - -import ly_test_tools.environment.file_system as file_system - -logger = logging.getLogger(__name__) - - -class KeyExistsError(Exception): - pass - - -class KeyDoesNotExistError(Exception): - pass - - -class S3Utils(object): - """ - Stores a boto3 S3 client to use for AWS S3 functionalities. - """ - DEFAULT_REGION = 'us-west-2' - - def __init__(self, boto3_session=None): - # type: (boto3.Session) -> None - """ - The boto3 session can be set during init, or a default one will be created. - :param boto3_session: A boto3 session - """ - if boto3_session: - self._session = boto3_session - else: - logger.info("No session provided, using default profile for s3 resource") - self._session = boto3.session.Session() - self._s3_resource = self._session.resource('s3') - - def upload_to_bucket(self, bucket_name, file_path, overwrite=False): - """ - Uploads a given file to the given S3 bucket. - :param bucket_name: Name of the S3 bucket where the file should be uploaded. - :param file_path: Path to the target file. - :param overwrite: Overwrite the key if it exists. - """ - if not self.bucket_exists_in_s3(bucket_name): - self._s3_resource.create_bucket(Bucket=bucket_name) - - s3_bucket = self._s3_resource.Bucket(bucket_name) - - file_key = os.path.basename(file_path) - if not overwrite and self.key_exists_in_bucket(bucket_name, file_key): - raise KeyExistsError("Key '{}' already exists in S3 bucket {}".format(file_key, bucket_name)) - - s3_bucket.upload_file(file_path, file_key) - logger.info("Uploading {} to S3 bucket {}".format(file_key, bucket_name)) - - def download_from_bucket(self, bucket_name, file_key, destination_dir, file_name=None): - """ - Download the given key from the given S3 bucket to the given destination. Logs an error if there is not enough \ - space available for the download. - :param bucket_name: Name of the S3 bucket containing the desired file. - :param file_key: Name of the file stored in S3. - :param destination_dir: Directory where the file should be downloaded to. - :param file_name: The name of the file you want to save it as. Defaults to the file_key. - """ - self.bucket_exists_in_s3(bucket_name) - - if not self.key_exists_in_bucket(bucket_name, file_key): - raise KeyDoesNotExistError("Key '{}' does not exist in S3 bucket {}".format(file_key, bucket_name)) - - obj_summary = self._s3_resource.ObjectSummary(bucket_name, file_key) - required_space = obj_summary.size - - file_system.check_free_space(destination_dir, required_space, "Insufficient space available for download:") - - if not os.path.exists(destination_dir): - os.makedirs(destination_dir) - - if file_name is None: - file_name = file_key - destination_path = os.path.join(destination_dir, file_name) - self._s3_resource.Object(bucket_name, file_key).download_file(destination_path) - logger.info("Downloading {} to {}".format(file_key, destination_path)) - - def bucket_exists_in_s3(self, bucket_name): - """ - Verifies that the S3 bucket exists. - :param bucket_name: Name of the S3 bucket that may or may not exist. - :return: True if the bucket exists. False otherwise. - """ - bucket_exists = True - - try: - self._s3_resource.meta.client.head_bucket(Bucket=bucket_name) - except botocore.exceptions.ClientError as err: - if err.response['Error']['Code'] == '404': - bucket_exists = False - - return bucket_exists - - def key_exists_in_bucket(self, bucket_name, file_key): - """ - Verifies that the given key does not already exist in the given S3 bucket. - :param bucket_name: Name of the S3 bucket that may or may not contain the file key. - :param file_key: Name of the file key in question. - :return: True if the key exists. False otherwise. - """ - key_exists = True - obj_summary = self._s3_resource.ObjectSummary(bucket_name, file_key) - - # Attempting to access any member of ObjectSummary for a nonexistent key will throw an exception - # There is no built-in way to check key existence otherwise - try: - obj_summary.size - except botocore.exceptions.ClientError as err: - if err.response['Error']['Code'] == '404': - key_exists = False - - return key_exists diff --git a/Tests/ly_shared/screenshot_utils.py b/Tests/ly_shared/screenshot_utils.py deleted file mode 100755 index c000cd8258..0000000000 --- a/Tests/ly_shared/screenshot_utils.py +++ /dev/null @@ -1,195 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -import os -import string - -from .file_utils import move_file -from . import phase as phase -from ly_test_tools.environment.waiter import wait_for -from ly_test_tools.image.screenshot_compare_qssim import qssim as compare_screenshots - -from ly_remote_console.remote_console_commands import capture_screenshot_command as capture_screenshot_command -from ly_remote_console.remote_console_commands import send_command_and_expect_response as send_command_and_expect_response - - -def get_next_screenshot_at_path(screenshot_path, prefix='screenshot', num_digits=4): - """ - :param screenshot_path: Root folder where the screenshots are being generated by the Launcher pr Editor. - :param prefix: Generated screenshot files are named sequentially using the prefix. - e.g: screenshot0000.jpg, screenshot0001.jpg and so on. - :param num_digits: How many digits are used for file name formation. - :return: A string with the file name (relative to screenshot_path). - """ - max_counter = 10**num_digits - counter = 0 - while counter < max_counter: - numberstr = "{}".format(counter) - formattednumber = numberstr.zfill(num_digits) - filename = "{}{}.jpg".format(prefix, formattednumber) - filepath = os.path.join(screenshot_path, filename) - if not os.path.exists(filepath): - #This filename is available. - return filename - raise AssertionError("All possible screenshot names at directory {} are taken".format(screenshot_path)) - - -def take_screenshot(remote_console_instance, workspace, screenshot_name): - """ - Takes an in game screenshot using the remote console instance passed in, validates that the screenshot exists - and then renames that screenshot to something defined by the user of this function. - :param remote_console_instance: Remote console instance that is attached to a specific launcher instance - :param workspace: workspace instance so we can get the platform cache folder. - :param screenshot_name: Name of the screenshot - :return: None - """ - screenshot_path = os.path.join(workspace.paths.platform_cache(), 'user', 'screenshots') - expected_screenshot_name = get_next_screenshot_at_path(screenshot_path) - capture_screenshot_command(remote_console_instance) - wait_for(lambda: os.path.exists(os.path.join(screenshot_path, expected_screenshot_name)), - timeout=10, - exc=AssertionError('Screenshot at path:{} and with name:{} not found.'.format(screenshot_path, expected_screenshot_name)) ) - wait_for(lambda: rename_screenshot(screenshot_path, screenshot_name), - timeout=10, - exc=AssertionError('Screenshot at path:{} and with name:{} is still in use.'.format(screenshot_path, screenshot_name))) - - -def rename_screenshot(screenshot_path, screenshot_name): - """ - Tries to rename the screenshot when the file is done being written to - :param screenshot_path: Path to the Screenshot folder - :param screenshot_name: Name we wish to change the screenshot to - :return: True when operation is completed, False if the file is still in use - """ - try: - src_img = os.path.join(screenshot_path, 'screenshot0000.jpg') - dst_img = os.path.join(screenshot_path, '{}.jpg'.format(screenshot_name)) - print('Trying to rename {} to {}'.format(src_img, dst_img)) - os.rename(src_img, dst_img) - return True - except Exception as e: - print('Found error {0} when trying to rename screenshot.'.format(str(e))) - return False - - -def move_screenshots(screenshot_path, file_type, logs_path): - """ - Moves screenshots of a specific file type to the flume location so we can gather all of the screenshots we took. - :param screenshot_path: Path to the screenshot folder - :param file_type: Types of Files to look for. IE .jpg, .tif, etc - :param logs_path: Path where flume gathers logs to be upload - """ - for file_name in os.listdir(screenshot_path): - if file_name.endswith(file_type): - move_file(screenshot_path, logs_path, file_name) - -def move_screenshots_to_artifacts(screenshot_path, file_type, artifact_manager): - """ - Saves screenshots of a specific file type to the artifact manager then removes the original files - :param screenshot_path: Path to the screenshot folder - :param file_type: Types of Files to look for. IE .jpg, .tif, etc - :param artifact_manager: The artifact manager to save the artifacts to - """ - for file_name in os.listdir(screenshot_path): - if file_name.endswith(file_type): - full_path_name = os.path.join(screenshot_path, file_name) - artifact_manager.save_artifact(full_path_name) - os.remove(full_path_name) - - - -def compare_golden_image(similarity_threshold, screenshot, screenshot_path, golden_image_name, - golden_image_path=None): - """ - This function assumes that your golden image filename contains the same base screenshot name and the word "golden" - ex. pc_gamelobby_golden - - :param similarity_threshold: A float from 0.0 - 1.0 that determines how similar images must be or an asserts - :param screenshot: A string that is the full name of the screenshot (ex. 'gamelobby_host.jpg') - :param screenshot_path: A string that contains the path to the screenshots - :param golden_image_path: A string that contains the path to the golden images, defaults to the screenshot_path - :return: - """ - if golden_image_path is None: - golden_image_path = screenshot_path - - mean_similarity = compare_screenshots((os.path.join(screenshot_path, screenshot)), - (os.path.join(golden_image_path, golden_image_name))) - assert mean_similarity > similarity_threshold, \ - '{} screenshot comparison failed! Mean similarity value is: {}'\ - .format(screenshot, mean_similarity) - -def download_qa_golden_images(project_name, destination_dir, platform): - """ - Downloads the golden images for a specified project from s3. The project_name, platform, and filetype are used to - filter which images will be downloaded as the golden images. - - https://s3.console.aws.amazon.com/s3/buckets/ly-qae-jenkins-configs/golden-images/?region=us-west-1&tab=overview - - :param project_name: a string of the project name of the folder in s3. ex: 'MultiplayerSample' - :param destination_dir: a string of where the images will be downloaded to - :param platform: a string for the platform type ('pc', 'android', 'ios', 'darwin') - :param filetype: a string for the file type. ex: '.jpg', '.png' - :return: - """ - - # Currently we import s3_utils here instead of at the top because this is the only method that needs it, - # and s3_utils has an unmet dependency on boto3 that hasn't been resolved. Once s3_utils is functional again, - # this can move back to the top of the file. - try: - from . import s3_utils as s3_utils - except ImportError: - raise Exception("Failed to import s3_utils") - # end s3_utils import - - bucket_name = 'ly-qae-jenkins-configs' - path = 'golden-images/{}/{}/'.format(project_name, platform) - - if not s3_utils.key_exists_in_bucket(bucket_name, path): - raise s3_utils.KeyDoesNotExistError("Key '{}' does not exist in S3 bucket {}".format(path, bucket_name)) - for image in s3_utils.s3.Bucket(bucket_name).objects.filter(Prefix=path): - file_name = string.replace(image.key, path, '') - if file_name != '': - s3_utils.download_from_bucket(bucket_name, image.key, destination_dir, file_name) - - -def _retry_command(remote_console_instance, command, output, tries=10, timeout=10): - """ - Retries specified console command multiple times and asserts if it still can not send. - :param remote_console: the remote console connected to the launcher. - :param command: the command to send to the console. - :param output: The expected output to check if the command was sent successfully. - :param tries: The amount of times to try before asserting. - :param timeout: The amount of time in seconds to wait for each retry send. - :return: True if succeeded, will assert otherwise. - """ - while tries > 0: - tries -= 1 - try: - send_command_and_expect_response(remote_console_instance, command, output) - return True - except: - pass #Do nothing. Let the number of tries get to 0 if necessary. - assert False, "Command \"{}\" failed to run in remote console.".format(command) - - -def prepare_for_screenshot_compare(remote_console_instance): - """ - Prepares launcher for screenshot comparison. Removes any debug text and antialiasing that may result in interference - with the comparison. - - :param remote_console_instance: Remote console instance that is attached to a specific launcher instance - :return: - """ - wait_for(lambda: _retry_command(remote_console_instance, 'r_displayinfo 0', - '$3r_DisplayInfo = $60 $5[DUMPTODISK, RESTRICTEDMODE]$4')) - wait_for(lambda: _retry_command(remote_console_instance, 'r_antialiasingmode 0', - '$3r_AntialiasingMode = $60 $5[]$4')) diff --git a/Tests/performance/Scripts/apbatch_perf_summary.py b/Tests/performance/Scripts/apbatch_perf_summary.py deleted file mode 100755 index ed34cbf490..0000000000 --- a/Tests/performance/Scripts/apbatch_perf_summary.py +++ /dev/null @@ -1,211 +0,0 @@ -""" - - All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - its licensors. - - For complete copyright and license terms please see the LICENSE at the root of this - distribution (the "License"). All use of this software is governed by the License, - or, if provided, by the license below or the license accompanying this file. Do not - remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -""" -Script is designed to help with asset processor performance testing. - -This script is capable of running AssetProcessorBatch.exe and -calculating how much time did it take to execute it. Also it is -processing ap batch output and grabbing actual asset processing time. - -Apart from that script is capable of logging folder size -(files, folders, actual size in bytes). - -Usage: -python apbatch_perf_summary.py [-h] {folder_size,run_apbatch} - -python apbatch_perf_summary.py folder_size path project -cache - -Will show folder size: files, folders and actual size in bytes. - -build_path: Full path to the build dev directory, e.g. F:\builds\lumberyard-0.0-639162-pc-1985\dev -project: Full project name (e.g. StarterGame or SamplesProject). --cache: specify if you need to check cache folder instead of source assets. - -python apbatch_perf_summary.py run_apbatch build_path platform num_launches -delete_cache - -Will launch AssetProcessorBatch.exe num_launches times. Will return average -running time and average asset processing time. - -build_path: Full path to the build dev directory, e.g. F:\builds\lumberyard-0.0-639162-pc-1985\dev -platform: Which platform to launch - one of following: vc141, vc142, mac -num_launches: How many times do you want to launch ap batch. --delete_cache: specify if you want to delete Cache before each run -""" - - -import subprocess -import time -import os -import argparse -import test_tools.shared.file_system as fs -import errno - - -def run_ap_batch(build_path, platform): - """ - Given a path to build will run ap batch and return total running and processing times. - :param build_path: Full path to build dev, e.g. F:\builds\lumberyard-0.0-639162-pc-1985\dev - :param platform: Specify platform where to run apbatch: vc141, vc142 or mac. - :return: (processing_time, total_running_time) tuple. - """ - assert os.path.exists(build_path) - - now = time.time() - process = subprocess.Popen(['AssetProcessorBatch'], cwd=os.path.join(build_path, platform), - shell=True, stdout=subprocess.PIPE) - for line in iter(process.stdout.readline, ''): - if 'Total Assets Processing Time' in line: - processing_time = line.split(':')[2] - process.wait() - end = time.time() - - print 'Processing time: {}'.format(processing_time.strip()) - print 'Total time: {}s'.format(end - now) - - return float(processing_time.split('s')[0]), float(end - now) - - -def run_several_times(build_path, platform, num_launches, erase_cache): - """ - Given path to build, project name and boolean parameter (whether there is need to delete a cache) - will run AssetProcessorBatch.exe num_launches and will return average running and processing times. - :param build_path: Full path to build dev, e.g. F:\builds\lumberyard-0.0-639162-pc-1985\dev - :param platform: Specify platform where to run apbatch: vc141, vc142 or mac. - :param num_launches: How many times user needs to launch ap batch. - :param erase_cache: yes/no or True/False in case user needs Cache folder to be deleted prior to apbatch launch. - :return: (avg_processing_time, avg_running_time) tuple. - """ - if not os.path.exists(build_path): - raise IOError(errno.ENOENT, os.strerror(errno.ENOENT), build_path) - - average_processing_time = 0 - average_total_time = 0 - - # running apbatch num_launches times and getting times - for i in range(num_launches): - if erase_cache and os.path.exists(os.path.join(build_path, 'Cache')): - fs.delete([os.path.join(build_path, 'Cache')], False, True) - print 'Iteration # {}'.format(i) - processing_time, total_time = run_ap_batch(build_path, platform) - average_processing_time += processing_time - average_total_time += total_time - - # calculating average times - average_processing_time /= num_launches - average_total_time /= num_launches - - return average_processing_time, average_total_time - - -def folder_size(path): - """ - Given path to a build will calculate folder size. - :param path: Full path to the folder for which you need folder size info. - :return: (total_files_count, total_folders_count, total_size_in_bytes). - """ - total_size = 0 - total_files_count = 0 - total_folder_count = 0 - - if not os.path.exists(path): - raise IOError(errno.ENOENT, os.strerror(errno.ENOENT), path) - - # walking over the folder and calculating files, folder; total files size - for dirpath, dirnames, filenames in os.walk(path): - total_folder_count += len(dirnames) - total_files_count += len(filenames) - for f in filenames: - fp = os.path.join(dirpath, f) - total_size += os.path.getsize(fp) - - return total_files_count, total_folder_count, total_size - - -def run_apbatch(args): - """ - Function for argparse command run_apbatch: - running run_several_times function and printing its results. - :param args: args.build_path: see run_several_times build_path. - args.num_launches: see run_several_times num_launches. - args.platform: see run_several_times platform. - args.delete: see run_several_times erase_cache. - :return: None - """ - platform_bin = { - 'vc141': 'Bin64vc141', - 'vc142': 'Bin64vc142', - 'mac': 'BinMac64' - } - running_times = run_several_times(args.build_path, platform_bin[args.platform], args.num_launches, args.delete_cache) - print '\nAssets processing time: {}s'.format(running_times[0]) - print 'Total running time: {}s'.format(running_times[1]) - - -def print_folder_size(args): - """ - Function for argparse command folder_size: - running folder_size function and printing its results. - :param args: args.build_path: see folder_size path. - args.project: specified project which folder will be analyzed. - args.cache: yes/no or True/False - whether user need to check Cache folder or not. - :return: None - """ - print '{} (cache: {}) folder size:'.format(args.project, args.cache) - if args.cache: - path = os.path.join(args.build_path, 'Cache', args.project) - else: - path = os.path.join(args.build_path, args.project) - folder_size_data = folder_size(path) - print 'Files: {}'.format(folder_size_data[0]) - print 'Folders: {}'.format(folder_size_data[1]) - print 'Size: {}'.format(folder_size_data[2]) - - -def main(): - """Main function with set-up and commands execution""" - # creating command line arguments parser - parser = argparse.ArgumentParser(prog = 'apbatch_perf_summary') - subparsers = parser.add_subparsers(help = 'sub-command help', dest='command') - - parser_folder_size = subparsers.add_parser('folder_size', - help='Will show folder size: files, folders and actual size in bytes. ') - parser_run_apbatch = subparsers.add_parser('run_apbatch', help='run_apbatch help') - - parser_run_apbatch.add_argument('build_path', - help='Full path to the build dev directory, e.g. ' - 'F:\\builds\\lumberyard-0.0-639162-pc-1985\\dev') - parser_run_apbatch.add_argument('platform', choices=['vc141', 'vc142', 'mac'], help='vc141, vc142 or mac') - parser_run_apbatch.add_argument('num_launches', type=int, help='How many times do you want to launch ap batch.') - parser_run_apbatch.add_argument('-delete_cache', default=False, action='store_true', - help='Specify if you want to delete Cache before and between runs') - - parser_run_apbatch.set_defaults(func=run_apbatch) - - parser_folder_size.add_argument('build_path', - help='Full path to the build dev directory, e.g. ' - 'F:\\builds\\lumberyard-0.0-639162-pc-1985\dev') - parser_folder_size.add_argument('project', help='Full project name (e.g. StarterGame or SamplesProject).') - parser_folder_size.add_argument('-cache', default=False, action='store_true', - help='Specify if you want to check cache folder', required=False) - parser_folder_size.set_defaults(func=print_folder_size) - - args = parser.parse_args() - - # executing passed commands - args.func(args) - - -# calling main function if script is launched as standalone module -if __name__ == '__main__': - main() - diff --git a/Tests/pipeline/__init__.py b/Tests/pipeline/__init__.py deleted file mode 100755 index e912252f4e..0000000000 --- a/Tests/pipeline/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - - All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - its licensors. - - For complete copyright and license terms please see the LICENSE at the root of this - distribution (the "License"). All use of this software is governed by the License, - or, if provided, by the license below or the license accompanying this file. Do not - remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - diff --git a/Tests/pipeline/product_dependency_tests/AssetDependencyTests.py b/Tests/pipeline/product_dependency_tests/AssetDependencyTests.py deleted file mode 100755 index 25e94b150c..0000000000 --- a/Tests/pipeline/product_dependency_tests/AssetDependencyTests.py +++ /dev/null @@ -1,88 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Automated scripts for tests calling AssetProcessorBatch validating basic features. - -""" - -from TestFixtures import HeliosProjectFixture - -import os -import pytest -import sqlite3 -import time -import codecs - -def AssertProductHasDependencies(engineRoot, projectName, buildInfo, dbCheckWaitTime, product, pathDependencies, assetIdDependencies): - productNameInDB = os.path.join(buildInfo.cacheSubfolder, projectName, product) - productNameInDB = productNameInDB.replace("\\", "/") - - sqlDatabasePath = os.path.join(engineRoot, "Cache", projectName, "assetdb.sqlite") - print (" * Connecting to database {}".format(sqlDatabasePath)) - sqlConnection = sqlite3.connect(sqlDatabasePath) - productRowsList = list() - productDbWait = dbCheckWaitTime - while len(productRowsList) == 0 and productDbWait > 0: - productRows = sqlConnection.execute( - "SELECT ProductID FROM Products where ProductName='{}'".format(productNameInDB)) - productRowsList = list(productRows.fetchall()) - time.sleep(1) - productDbWait = productDbWait - 1 - assert len(productRowsList) == 1, str.format("productRowsList= {}", productRowsList) - productId = int(productRowsList[0][0]) - - foundDependencies = list() - dependencyDbTimeout = dbCheckWaitTime - while len(foundDependencies) == 0 and dependencyDbTimeout > 0: - dependencyRows = sqlConnection.execute( - "SELECT * FROM ProductDependencies where ProductPK={}".format(productId)) - foundDependencies = list(dependencyRows.fetchall()) - time.sleep(1) - dependencyDbTimeout = dependencyDbTimeout - 1 - - foundAssetIds = list() - foundUnresolvedPaths = list() - - uuidIndex = 2 - subIdIndex = 3 - unresolvedPathIndex = 6 - for foundDependency in foundDependencies: - if foundDependency[unresolvedPathIndex] != "": - # If there's a path, there won't be an asset ID - foundUnresolvedPaths.append(foundDependency[unresolvedPathIndex]) - else: - dependencyUUIDAsHex = codecs.encode(foundDependency[uuidIndex], 'hex_codec') - subId = str(foundDependency[subIdIndex]) - assetId = "{}:{}".format(dependencyUUIDAsHex.decode('utf8'), subId) - foundAssetIds.append(assetId) - - assert sorted(pathDependencies) == sorted(foundUnresolvedPaths) - assert sorted(assetIdDependencies) == sorted(foundAssetIds) - - -def test_VegdescriptorlistValidDependencies_DependenciesInDb(HeliosProjectFixture): - engineRoot, projectName, buildInfo, dbCheckWaitTime = HeliosProjectFixture - pathDependencies = {} - assetIdDependencies = { - # MeshAsset reference to "objects/default/primative_wedge_30.cgf" - "e8b39f901f905e3998aa6f8ec4e91507:0", - # MaterialAsset reference to "materials/am_grass1.mtl" - "1151f14d38a65579888abe3139882e68:0" - } - AssertProductHasDependencies(engineRoot, projectName, buildInfo, dbCheckWaitTime, "heliosvegetation.vegdescriptorlist", pathDependencies, assetIdDependencies) - -def test_CloudLibrarytValidDependencies_DependenciesInDb(HeliosProjectFixture): - engineRoot, projectName, buildInfo, dbCheckWaitTime = HeliosProjectFixture - pathDependencies = {} - assetIdDependencies = { - # MaterialAsset reference to "materials/clouds/baseclouds.mtl" - "f249f13854055cfba3b6d95bdc1a3db0:0" - } - AssertProductHasDependencies(engineRoot, projectName, buildInfo, dbCheckWaitTime, "libs/clouds/default.xml", pathDependencies, assetIdDependencies) diff --git a/Tests/pipeline/product_dependency_tests/LvlDepTestDynamicSlice.py b/Tests/pipeline/product_dependency_tests/LvlDepTestDynamicSlice.py deleted file mode 100755 index de7609c92c..0000000000 --- a/Tests/pipeline/product_dependency_tests/LvlDepTestDynamicSlice.py +++ /dev/null @@ -1,228 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Automated scripts for tests calling AssetProcessorBatch validating basic features. - -""" - -from TestFixtures import EmptyProjectFixture - -import fileinput -import os -import pytest -import shutil -import sqlite3 -import time - -import SubprocessUtils - -def MakeEditorPythonFile(testLevelName, assetGuid, tempFolder, templatePythonFile): - outputFileName = templatePythonFile.replace(".template", ".py") - outputFilePath = os.path.join(tempFolder, outputFileName) - shutil.copy(os.path.join(os.path.dirname(os.path.realpath( - __file__)), templatePythonFile), outputFilePath) - for line in fileinput.FileInput(outputFilePath, inplace=1): - line = line.replace("${LevelName}", str.format('"{}"', testLevelName)) - line = line.replace("${MeshGuid}", str.format('"{}"', assetGuid)) - print (line) - return outputFilePath - -@pytest.mark.skip(reason="This test takes too long on Jenkins, and bundler tests catch everything we want from here") -def test_productDependencies_EntityInLevelWithAssetReference_ReferencedAssetIsLevelProductDependency( - EmptyProjectFixture, tmpdir): - print ("RunLvlDynamicSliceTest") - engineRoot, projectName, buildInfo, dbCheckWaitTime = EmptyProjectFixture - - - tempFolder = tmpdir.mkdir("EditorPyScripts") - print (" * Launch editor with dynamic slice test creation script") - - testLevelName = "SimpleLevel" - expectedDependencyGuid = "81C4A6AF-C57D-5734-81B7-822074358C4D" - exportLevelScriptPath = MakeEditorPythonFile(testLevelName, expectedDependencyGuid, str(tempFolder), "export_test_level.template") - - lyCommand = [buildInfo.editorExe, '/BatchMode', '/runpython', exportLevelScriptPath] - SubprocessUtils.SubprocessWithTimeout(lyCommand, engineRoot, 60) - - # Logic that the export_test_level.py script will run in the editor: - # * Create new level - # * Place an entity in the level - # * Add the mesh component to the entity - # * Assign a test asset to that component [dev\Engine\Objects\default\primitive_sphere.cgf] - # * Export the level - - print (" * Wait for the Asset Processor to copy the asset to the cache and update the asset database") - - projectCacheRoot = os.path.join(engineRoot, "Cache", projectName) - levelRelativeSubFolder = os.path.join("Levels", testLevelName) - cachePath = os.path.join(projectCacheRoot, buildInfo.cacheSubfolder, projectName, levelRelativeSubFolder, "level.pak") - # On an i7 running Lumberyard on an SSD, it normally takes about 1-2 minutes to complete this step. - # Add a few minutes onto that because the Jenkins machines may not be as fast. - pakTimeoutSeconds = 10 * 60 - pakTimeoutWaitTimeSeconds = 1 - # Wait for the level.pak file to exist in the cache - while not os.path.exists(cachePath) and pakTimeoutSeconds > 0: - time.sleep(pakTimeoutWaitTimeSeconds) - pakTimeoutSeconds -= pakTimeoutWaitTimeSeconds - - assert(os.path.exists(cachePath)) - - print (" * Open the asset database, check that the correct product dependency is set for the exported level.pak") - - # A newly created level will have all of these dependencies by default. - # These are tracked by the relative source path instead of the UUID because it's more readable. - expectedDependencyPaths = { - "materials/material_terrain_default.mtl", # from leveldata.xml - "EngineAssets/Materials/sky/sky.mtl", # from mission_mission0.xml - "EngineAssets/Materials/Water/ocean_default.mtl", # from mission_mission0.xml - "textures/skys/night/half_moon.tif" - } - - # Nothing is currently expected to unresolved, but this is left here in case that changes. - expectedUnresolvedPaths = { - # Hardcoded levelbuilder relative path output - os.path.join(levelRelativeSubFolder, "auto_resourcelist.txt"), - os.path.join(levelRelativeSubFolder, "level.cfg"), - os.path.join(levelRelativeSubFolder, "levelparticles.xml"), - os.path.join(levelRelativeSubFolder, "occluder.ocm"), - os.path.join(levelRelativeSubFolder, "preloadlibs.txt"), - os.path.join(levelRelativeSubFolder, "terrain", "cover.ctc"), - os.path.join(levelRelativeSubFolder, "terrain", "merged_meshes_sectors", "mmrm_used_meshes.lst"), - os.path.join(levelRelativeSubFolder, str.format("{}.xml",testLevelName)) - } - - # All expected GUIDs should match the format in the database: Lowercase, with no separators. - expectedDependencyGuids = { - expectedDependencyGuid.lower().replace('-','') - } - - CheckDatabaseForDependency(projectCacheRoot, projectName, testLevelName, expectedDependencyGuids, - expectedDependencyPaths, expectedUnresolvedPaths, buildInfo, dbCheckWaitTime) - - print ("/RunLvlDynamicSliceTest") - - -def CheckDatabaseForDependency(projectCacheRoot, projectName, testLevelName, expectedDependencyGuids, expectedDependencyPaths, expectedUnresolvedPaths, buildInfo, dbCheckWaitTime): - print ("CheckDatabaseForDependency") - - print (str.format(" * Checking expected dependencies for level.pak for level {}", testLevelName)) - print (str.format(" * Searching for these GUIDs as dependencies: {}", str(expectedDependencyGuids))) - print (str.format(" * Searching for these paths as dependencies: {}", str(expectedDependencyPaths))) - print (str.format(" * Searching for these paths as unresolved paths: {}", str(expectedUnresolvedPaths))) - - sqlDatabasePath = os.path.join(projectCacheRoot, "assetdb.sqlite") - print (" * Connecting to database " + sqlDatabasePath) - sqlConnection = sqlite3.connect(sqlDatabasePath) - try: - # Not using os.path.join because this is an expected string in a database - levelPakProduct = str.format('{}/{}/levels/{}/level.pak', buildInfo.cacheSubfolder, projectName.lower(), testLevelName.lower()) - print (" * Looking in product table for " + levelPakProduct) - productRows = sqlConnection.execute( - str.format("SELECT ProductID FROM Products where ProductName='{}'", levelPakProduct)) - - productRowsList = list(productRows.fetchall()) - - productDbWait = dbCheckWaitTime - while len(productRowsList) == 0 and productDbWait > 0: - time.sleep(1) - productDbWait = productDbWait - 1 - productRows = sqlConnection.execute( - str.format("SELECT ProductID FROM Products where ProductName='{}'", levelPakProduct)) - productRowsList = list(productRows.fetchall()) - - - assert len(productRowsList) == 1, "productRowsList= {}".format(productRowsList) - - print (" * Searching product results for product ID") - productId = int(productRowsList[0][0]) - - assert productId - - print (str.format(" * Searching for dependencies for product ID {}", str(productId))) - dependencyDbSuccess = False - dependencyDbTimeout = dbCheckWaitTime - # Make copies of the list in case multiple runs are required - expectedUnresolvedPathsCopy = [] - expectedDependencyGuidsCopy = [] - remainingDependencies = [] - - while (not dependencyDbSuccess) and dependencyDbTimeout > 0: - expectedUnresolvedPathsCopy = expectedUnresolvedPaths.copy() - expectedDependencyGuidsCopy = expectedDependencyGuids.copy() - productDependencyRows = sqlConnection.execute("SELECT * FROM ProductDependencies where ProductPK={}".format(productId)) - - dependencyRowIndex_SourceId = 2 - dependencyRowIndex_SubId = 3 - dependencyRowIndex_UnresolvedPath = 6 - - productDependencyRowList = list(productDependencyRows.fetchall()) - - expectedDependencyCount = len(expectedDependencyGuidsCopy) + len( - expectedDependencyPaths) + len(expectedUnresolvedPathsCopy) - - dependencyDbSuccess = len( - productDependencyRowList) == expectedDependencyCount - - expectedSubId = 0 - - # This will contain SQL data buffers, which are not hashable. - remainingDependencies = [] - - for dependencyRow in productDependencyRowList: - dependencySourceId = dependencyRow[dependencyRowIndex_SourceId] - dependencySubId = int(dependencyRow[dependencyRowIndex_SubId]) - dependencyDbSuccess = dependencyDbSuccess and dependencySubId == expectedSubId - - dependencySourceAsHex = str(dependencySourceId).encode('hex') - wasExpectedDependency = False - # If this dependency's UUID is in our expected UUID list, then count it as found. - if dependencySourceAsHex in expectedDependencyGuidsCopy: - wasExpectedDependency = True - expectedDependencyGuidsCopy.remove(dependencySourceAsHex) - - # If this dependency has an unresolved path that we expect, then count it as found. - unresolvedPath = dependencyRow[dependencyRowIndex_UnresolvedPath] - if unresolvedPath in expectedUnresolvedPathsCopy: - wasExpectedDependency = True - expectedUnresolvedPathsCopy.remove(unresolvedPath) - - if not wasExpectedDependency: - remainingDependencies.append(dependencySourceId) - - dependencyDbSuccess = dependencyDbSuccess and (len(expectedDependencyGuidsCopy) == 0 and - len(expectedUnresolvedPathsCopy) == 0 and - len(remainingDependencies) == len(expectedDependencyPaths)) - if not dependencyDbSuccess: - time.sleep(1) - dependencyDbTimeout = dependencyDbTimeout - 1 - - # do all the checks in asserts, instead of just assert dependencyDbSuccess so that error messages are more specific - assert len(productDependencyRowList) == expectedDependencyCount, str.format("Expected {} dependencies, found {}", expectedDependencyCount, len(productDependencyRowList)) - assert len(expectedDependencyGuidsCopy) == 0, str.format( - "Expected dependencies were not found in the asset database: {}", str(expectedDependencyGuids)) - assert len(expectedUnresolvedPathsCopy) == 0, str.format( - "Expected unresolved paths were not found in the asset database: {}", str(expectedUnresolvedPathsCopy)) - assert len(remainingDependencies) == len(expectedDependencyPaths), str.format("Expected dependency sizes do not match for {} and {}", str(remainingDependencies), str(expectedDependencyPaths)) - - for remainingDependency in remainingDependencies: - sourceRows = sqlConnection.execute("SELECT SourceName FROM Sources where SourceGuid=?", (sqlite3.Binary(remainingDependency),) ) - sourceRowsList = list(sourceRows.fetchall()) - assert len(sourceRowsList) == 1, str.format("Expected to find 1 entry for {}, found {} instead.", str(remainingDependency).encode('hex'), len(sourceRowsList)) - sourcePath = sourceRowsList[0][0] - assert sourcePath in expectedDependencyPaths, str.format("Could not find {} for UUID {} in the list of expected dependencies.", str(sourcePath), str(remainingDependency).encode('hex')) - expectedDependencyPaths.remove(sourcePath) - assert len(expectedDependencyPaths) == 0, str.format("Missing expected dependencies {}", str(expectedDependencyPaths)) - - print (" * Found all expected dependencies") - finally: - print (" * Closing database connection") - sqlConnection.close() - print ("/CheckDatabaseForDependency") - diff --git a/Tests/pipeline/product_dependency_tests/SubprocessUtils.py b/Tests/pipeline/product_dependency_tests/SubprocessUtils.py deleted file mode 100755 index d5637527f2..0000000000 --- a/Tests/pipeline/product_dependency_tests/SubprocessUtils.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Automated scripts for tests calling AssetProcessorBatch validating basic features. - -""" - -import subprocess -import threading -import time - -class ThreadedSubprocess(): - def __init__(self, command, workingDirectory, timeOutMinutes): - self.command = command - self.workingDirectory = workingDirectory - self.timeOutSeconds=timeOutMinutes*60 - self.process = None - self.logOutput = [] - # Pytest doesn't handle asserts on other threads, capture them and report on the main thread. - self.assertError = None - - def RunCommand(self): - def RunThread(): - print (str.format("Subprocess thread starting for command: {}", self.command)) - self.process = subprocess.Popen(self.command, cwd=self.workingDirectory, shell=True, stdout=subprocess.PIPE, universal_newlines=True) - for stdoutLine in iter(self.process.stdout.readline, ""): - self.logOutput.append(stdoutLine) - self.process.communicate() - if self.process.returncode is None: - self.assertError = str.format("Subprocess call '{}' had no return code", self.command) - elif self.process.returncode != 0: - self.assertError = str.format("Subprocess call '{}' returned code {}", self.command, self.process.returncode) - print (str.format("Finished command, result {}: {}", self.process.returncode, self.command)) - - commandThread = threading.Thread(target=RunThread) - commandThread.start() - commandThread.join(self.timeOutSeconds) - assert not commandThread.is_alive(), str.format("Subprocess call '{}' timed out", self.command) - assert self.assertError is None, self.assertError - - -def SubprocessWithTimeout(command, workingDirectory, timeOutMinutes): - threadedSubprocess = ThreadedSubprocess(command, workingDirectory, timeOutMinutes) - threadedSubprocess.RunCommand() - return threadedSubprocess.logOutput diff --git a/Tests/pipeline/product_dependency_tests/TestAssets/updated_xml_schema_test.xmlschema b/Tests/pipeline/product_dependency_tests/TestAssets/updated_xml_schema_test.xmlschema deleted file mode 100644 index a979a1b245..0000000000 --- a/Tests/pipeline/product_dependency_tests/TestAssets/updated_xml_schema_test.xmlschema +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tests/pipeline/product_dependency_tests/TestAssets/xml_schema_test.xml b/Tests/pipeline/product_dependency_tests/TestAssets/xml_schema_test.xml deleted file mode 100644 index 95b20dc6c8..0000000000 --- a/Tests/pipeline/product_dependency_tests/TestAssets/xml_schema_test.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/Tests/pipeline/product_dependency_tests/TestAssets/xml_schema_test.xmlschema b/Tests/pipeline/product_dependency_tests/TestAssets/xml_schema_test.xmlschema deleted file mode 100644 index 69be99578d..0000000000 --- a/Tests/pipeline/product_dependency_tests/TestAssets/xml_schema_test.xmlschema +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tests/pipeline/product_dependency_tests/TestCleanup.py b/Tests/pipeline/product_dependency_tests/TestCleanup.py deleted file mode 100755 index d57119db26..0000000000 --- a/Tests/pipeline/product_dependency_tests/TestCleanup.py +++ /dev/null @@ -1,76 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Automated scripts for tests calling AssetProcessorBatch validating basic features. - -""" - -import os, subprocess, shutil, time - -import SubprocessUtils - -def KillProcess_Windows(processName): - # This only runs on Windows - processList = subprocess.check_output(str.format('tasklist /NH /FO CSV /FI "IMAGENAME eq {}"', processName)) - if processList is None or processList.startswith("INFO: No tasks are running which match the specified criteria."): - # Asset processor isn't running, no need to kill it. - return - os.system(str.format('taskkill /F /IM {}', processName)) - -def KillLumberyardTools(): - if os.name == 'nt': - KillProcess_Windows("Editor.exe") - KillProcess_Windows("AssetProcessor.exe") - else: - # Other operating systems are not yet supported, so have the test fail - assert False - - -def RemoveFolder(folderPath): - # The Asset Processor may take a bit to shut down, retry a few times if it's still holding a lock on a file. - retryCount = 5 - for retry in range(retryCount): - if os.path.exists(folderPath) == False: - return - - try: - shutil.rmtree(folderPath) - except: - if retry < retryCount-1: - # Wait a few seconds for whatever has the file handle open to close it. - time.sleep(5) - continue - else: - raise - - -def cleanUpArtifacts(engineRoot, projectName, buildInfo): - print ("cleanUpArtifacts") - - print (" * Shutting down Asset Processor") - KillLumberyardTools() - - # Setting the active project to one that is in Perforce will make sure other commands work correctly. Once the - # project created here is destroyed, lmbr_waf commands won't work. - if os.path.exists(os.path.join(buildInfo.buildFolder,buildInfo.lmbrCommand)): - print (" * Setting project to Helios") - SubprocessUtils.SubprocessWithTimeout(str.format("{} projects set-active Helios", buildInfo.lmbrCommand), buildInfo.buildFolder, 60) - else: - print (" * Cannot set project to FeatureTests, lmbr executable is not available.") - # Clearing the cache to guarantee that no data persists between tests. - print (" * Clearing the asset cache") - cachePath = os.path.join(engineRoot, "Cache", projectName) - RemoveFolder(cachePath) - - print (" * Clearing generated data") - projectPath = os.path.join(engineRoot, projectName) - RemoveFolder(projectPath) - - print ("/cleanUpArtifacts") diff --git a/Tests/pipeline/product_dependency_tests/TestFixtures.py b/Tests/pipeline/product_dependency_tests/TestFixtures.py deleted file mode 100755 index 91151748a5..0000000000 --- a/Tests/pipeline/product_dependency_tests/TestFixtures.py +++ /dev/null @@ -1,63 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Automated scripts for tests calling AssetProcessorBatch validating basic features. - -""" - -import os -import pytest - -import LmbrBuildInfo -import SubprocessUtils -import TestCleanup -import TestSetup - -@pytest.fixture(scope="session") -def EmptyProjectFixture(request): - print ("EmptyProjectFixture") - engineRoot = TestSetup.FindEngineRoot() - assert engineRoot - projectName = "LevelDepTestProj" - buildInfo = LmbrBuildInfo.GetBuildInfo(request.config.option.buildFlavor, engineRoot) - TestCleanup.cleanUpArtifacts(engineRoot, projectName, buildInfo) - - TestSetup.CreateLvlDepTestProject(engineRoot, projectName, buildInfo, request.config.option.thirdPartyPath) - # cleanUpArtifacts deleted the temp dir, so create it. - print ("Finished EmptyProjectFixture setup") - yield engineRoot, projectName, buildInfo, int(request.config.option.dbWaitTimes) - - print ("Tearing down EmptyProjectFixture") - TestCleanup.cleanUpArtifacts(engineRoot, projectName, buildInfo) - print ("Finished EmptyProjectFixture tear down") - - -# These tests require the Helios project to be built in profile and active before they are run. -# This external requirement allows faster iteration on these tests on Jenkins and locally. -@pytest.fixture(scope="session") -def HeliosProjectFixture(request): - print ("HeliosProjectFixture") - engineRoot = TestSetup.FindEngineRoot() - assert engineRoot - projectName = "Helios" - buildInfo = LmbrBuildInfo.GetBuildInfo(request.config.option.buildFlavor, engineRoot) - - # These tests are run on Jenkins after other tests, to minimize time spent on Jenkins jobs. - # Verify that the correct project has been set before this test starts. - - # Temporarily disabling while LY-103017 is not in Helios branch - # Creating a task to revert this change later: LY-103334 - - # Run asset processor once to process all assets, so the tests themselves can run at consistent speeds. - SubprocessUtils.SubprocessWithTimeout([buildInfo.assetProcessorBatch], engineRoot, 120) - - print ("Finished HeliosProjectFixture setup") - yield engineRoot, projectName, buildInfo, int(request.config.option.dbWaitTimes) - print ("Tearing down EmptyProjectFixture") diff --git a/Tests/pipeline/product_dependency_tests/XmlSchemaSystemTests.py b/Tests/pipeline/product_dependency_tests/XmlSchemaSystemTests.py deleted file mode 100755 index 51a349c185..0000000000 --- a/Tests/pipeline/product_dependency_tests/XmlSchemaSystemTests.py +++ /dev/null @@ -1,212 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Automated scripts for tests calling AssetProcessorBatch validating basic features. - -""" - -from TestFixtures import HeliosProjectFixture - -import pytest -import shutil -import subprocess -import os -import sqlite3 -import time - -TEST_ASSETS_FOLDER_NAME = 'TestAssetS' -TEST_XML_NAME = 'xml_schema_test.xml' -TEST_SCHEMA_NAME = 'xml_schema_test.xmlschema' -UPDATED_TEST_SCHEMA_NAME = 'updated_xml_schema_test.xmlschema' -SCHEMA_FOLDER_NAME = 'Schema' - -def test_XmlSchemaSystem_AddNewSchema_ReprocessXml(HeliosProjectFixture): - engineRoot, projectName, buildInfo, dbCheckWaitTime = HeliosProjectFixture - projectFolder = os.path.join(engineRoot, projectName) - - testAssetsFolder = os.path.dirname(os.path.realpath(__file__)) - testAssetsFolder = os.path.join(testAssetsFolder, TEST_ASSETS_FOLDER_NAME) - testXmlPath = os.path.join(testAssetsFolder, TEST_XML_NAME) - testSchemaPath = os.path.join(testAssetsFolder, TEST_SCHEMA_NAME) - updatedTestSchemaPath = os.path.join(testAssetsFolder, UPDATED_TEST_SCHEMA_NAME) - - projectXmlPath = os.path.join(projectFolder, TEST_XML_NAME) - schemaFolderInProject = os.path.join(projectFolder, SCHEMA_FOLDER_NAME) - projectSchemaPath = os.path.join(schemaFolderInProject, TEST_SCHEMA_NAME) - - # Clean up the existing test xml and schema assets - if CleanUpTestAsset(projectXmlPath) or CleanUpTestAsset(projectSchemaPath): - # Remove the test assets and its dependencies from the database - subprocess.call( - [os.path.join(buildInfo.buildFolder, 'AssetProcessorBatch'), "/gamefolder=Helios"]) - - # Copy test XML asset to the project - # No product dependency should be output for the XML asset without a corresponding schema - print ("Add new XML asset") - expectedUnresolvedPaths = {} - UpdateProjectAsset(testXmlPath, projectXmlPath, engineRoot, projectName, - expectedUnresolvedPaths, buildInfo, dbCheckWaitTime, False) - - # Copy the XML schema asset to the project - # XML assets are expected to be reprocessed and output product dependencies - print ("Add new XML schema") - if not os.path.exists(schemaFolderInProject): - os.makedirs(schemaFolderInProject) - expectedUnresolvedPaths = { 'dependency1' } - UpdateProjectAsset(testSchemaPath, projectSchemaPath, engineRoot, projectName, - expectedUnresolvedPaths, buildInfo, dbCheckWaitTime, False) - - # Update the XML schema asset inside the project - # XML assets are expected to be reprocessed and output product dependencies - print ("Update XML schema") - expectedUnresolvedPaths = { 'dependency1', 'dependency2'} - UpdateProjectAsset(updatedTestSchemaPath, projectSchemaPath, engineRoot, projectName, - expectedUnresolvedPaths, buildInfo, dbCheckWaitTime, False) - - # Delete the schema asset in the project folder - # No product dependency should be output for the XML asset since the schema has been deleted - print ("Delete XML schema") - expectedUnresolvedPaths = {} - UpdateProjectAsset(updatedTestSchemaPath, projectSchemaPath, engineRoot, projectName, - expectedUnresolvedPaths, buildInfo, dbCheckWaitTime, True) - - # Clean up the test assets - CleanUpTestAsset(projectXmlPath) - if not os.listdir(schemaFolderInProject): - os.rmdir(schemaFolderInProject) - -def test_XmlSchemaSystem_MoveSchemaToGem_ReprocessXml(HeliosProjectFixture): - engineRoot, projectName, buildInfo, dbCheckWaitTime = HeliosProjectFixture - - projectFolder = os.path.join(engineRoot, projectName) - testAssetsFolder = os.path.dirname(os.path.realpath(__file__)) - testAssetsFolder = os.path.join(testAssetsFolder, TEST_ASSETS_FOLDER_NAME) - testXmlPath = os.path.join(testAssetsFolder, TEST_XML_NAME) - testSchemaPath = os.path.join(testAssetsFolder, TEST_SCHEMA_NAME) - projectXmlPath = os.path.join(projectFolder, TEST_XML_NAME) - projectSchemaPath = os.path.join(projectFolder, SCHEMA_FOLDER_NAME, TEST_SCHEMA_NAME) - # The CertificateManager Gem was arbitrarily chosen. Schema can be added to any enabled gem - gemSchemaFolder = os.path.join(engineRoot, 'Gems', 'CertificateManager', 'Assets', SCHEMA_FOLDER_NAME) - gemSchemaPath = os.path.join(gemSchemaFolder, TEST_SCHEMA_NAME) - - # Clean up all the potential test xml and schema assets - if CleanUpTestAsset(projectXmlPath) or CleanUpTestAsset(projectSchemaPath) or CleanUpTestAsset(gemSchemaPath): - # Remove the test assets and its dependencies from the database - subprocess.call( - [os.path.join(buildInfo.buildFolder, 'AssetProcessorBatch'), "/gamefolder=Helios"]) - - print ('Add XML schema to CertificateManager gem') - if not os.path.exists(gemSchemaFolder): - os.makedirs(gemSchemaFolder) - shutil.copyfile(testSchemaPath, gemSchemaPath) - - print ('Reprocess test assets') - expectedUnresolvedPaths = { 'dependency1' } - UpdateProjectAsset(testXmlPath, projectXmlPath, engineRoot, projectName, expectedUnresolvedPaths, buildInfo, dbCheckWaitTime, False) - - # Clean up the test assets - CleanUpTestAsset(projectXmlPath) - CleanUpTestAsset(gemSchemaPath) - if not os.listdir(gemSchemaFolder): - os.rmdir(gemSchemaFolder) - -def CleanUpTestAsset(assetName): - assetExists = False; - if os.path.exists(assetName): - assetExists = True; - os.remove(assetName) - - assert not os.path.exists(assetName) - - return assetExists - - -def UpdateProjectAsset(testAssetPath, projectAssetPath, engineRoot, projectName, expectedUnresolvedPaths, buildInfo, dbCheckWaitTime, deleteAsset): - if deleteAsset: - os.remove(projectAssetPath) - else: - shutil.copyfile(testAssetPath, projectAssetPath) - - # Let AP reprocess the new/updated asset - subprocess.call( - [os.path.join(buildInfo.buildFolder, 'AssetProcessorBatch'), "/gamefolder=Helios"]) - - projectCacheRoot = os.path.join(engineRoot, 'Cache', projectName) - CheckDatabaseForDependency(projectCacheRoot, projectName, - expectedUnresolvedPaths, buildInfo, dbCheckWaitTime) - - -def CheckDatabaseForDependency(projectCacheRoot, projectName, expectedUnresolvedPaths, buildInfo, dbCheckWaitTime): - print ("CheckDatabaseForDependency") - - print (" * Searching for these paths as unresolved paths: {}".format(str(expectedUnresolvedPaths))) - - sqlDatabasePath = os.path.join(projectCacheRoot, "assetdb.sqlite") - print (" * Connecting to database " + sqlDatabasePath) - sqlConnection = sqlite3.connect(sqlDatabasePath) - try: - # Not using os.path.join because this is an expected string in a database - xmlProduct = '{}/{}/xml_schema_test.xml'.format(buildInfo.cacheSubfolder, projectName.lower()) - print (" * Looking in product table for " + xmlProduct) - productRows = sqlConnection.execute( - "SELECT ProductID FROM Products where ProductName='{}'".format(xmlProduct)) - productRowsList = list(productRows.fetchall()) - - productDbWait = dbCheckWaitTime - while len(productRowsList) == 0 and productDbWait > 0: - time.sleep(1) - productDbWait = productDbWait - 1 - productRows = sqlConnection.execute( - "SELECT ProductID FROM Products where ProductName='{}'".format(xmlProduct)) - productRowsList = list(productRows.fetchall()) - - assert len(productRowsList) == 1, "productRowsList= {}".format(productRowsList) - - print (" * Searching product results for product ID") - productId = int(productRowsList[0][0]) - - assert productId - - print (" * Searching for dependencies for product ID {}".format(productId)) - dependencyDbSuccess = False - dependencyDbTimeout = dbCheckWaitTime - # Make copies of the list in case multiple runs are required - expectedUnresolvedPathsCopy = [] - dependencyRowIndex_UnresolvedPath = 6 - while (not dependencyDbSuccess) and dependencyDbTimeout > 0: - expectedUnresolvedPathsCopy = expectedUnresolvedPaths.copy() - productDependencyRows = sqlConnection.execute("SELECT * FROM ProductDependencies where ProductPK={}".format(productId)) - - productDependencyRowList = list(productDependencyRows.fetchall()) - expectedDependencyCount = len(expectedUnresolvedPathsCopy) - dependencyDbSuccess = len( - productDependencyRowList) == expectedDependencyCount - - for dependencyRow in productDependencyRowList: - # If this dependency has an unresolved path that we expect, then count it as found. - unresolvedPath = dependencyRow[dependencyRowIndex_UnresolvedPath] - if unresolvedPath in expectedUnresolvedPathsCopy: - expectedUnresolvedPathsCopy.remove(unresolvedPath) - - dependencyDbSuccess = dependencyDbSuccess and len(expectedUnresolvedPathsCopy) == 0 - - if not dependencyDbSuccess: - time.sleep(1) - dependencyDbTimeout = dependencyDbTimeout - 1 - - # do all the checks in asserts, instead of just assert dependencyDbSuccess so that error messages are more specific - assert len(expectedUnresolvedPathsCopy) == 0, str.format( - "Expected unresolved paths were not found in the asset database: {}", str(expectedUnresolvedPathsCopy)) - - print (" * Found all expected dependencies") - finally: - print (" * Closing database connection") - sqlConnection.close() - print ("/CheckDatabaseForDependency") \ No newline at end of file diff --git a/Tests/pipeline/product_dependency_tests/conftest.py b/Tests/pipeline/product_dependency_tests/conftest.py deleted file mode 100755 index cdac03401a..0000000000 --- a/Tests/pipeline/product_dependency_tests/conftest.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Automated scripts for tests calling AssetProcessorBatch validating basic features. - -""" - -def pytest_addoption(parser): - parser.addoption( - "--buildFlavor", action="store", default="WindowsVS2017", help="Sets the build type (see lmbrBuildInfo.py for what's available)" - ) - - parser.addoption( - "--dbWaitTimes", action="store", default=60, help="How long (in seconds) to wait for a condition to be met in the database before timing out" - ) - - parser.addoption( - "--thirdPartyPath", action="store", help="Path to the 3rd party folder" - ) - -def pytest_generate_tests(metafunc): - if "buildFlavor" in metafunc.fixturenames: - metafunc.parametrize("buildFlavor", metafunc.config.getoption("buildFlavor"), scope="session") - - if "dbWaitTimes" in metafunc.fixturenames: - metafunc.parametrize("dbWaitTimes", metafunc.config.getoption("dbWaitTimes"), scope="session") - - if "thirdPartyPath" in metafunc.fixturenames: - metafunc.parametrize("thirdPartyPath", metafunc.config.getoption("thirdPartyPath"), scope="session") \ No newline at end of file diff --git a/Tests/pipeline/product_dependency_tests/export_test_level.template b/Tests/pipeline/product_dependency_tests/export_test_level.template deleted file mode 100644 index 2a6567904e..0000000000 --- a/Tests/pipeline/product_dependency_tests/export_test_level.template +++ /dev/null @@ -1,38 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -import os - -game_folder = general.get_game_folder() -levelName = ${LevelName} -meshGuid = ${MeshGuid} - -# Don't use terrain because creating a level with terrain causes a popup message that requires user input. -general.create_level(levelName, 128, 1, False) -levelPath = os.path.join(game_folder, "Levels", levelName, str.format("{}.ly", levelName)) - -if not isinstance(input, str): - # general.open_level_no_prompt expects the file path in utf8 format - levelPath = levelName.encode("utf-8") - -general.open_level_no_prompt(levelPath) - -newEntityIdStr = str(general.create_entity("TestEntityName")) - -componentResult = general.add_mesh_component_with_mesh(newEntityIdStr, meshGuid) - -if not componentResult: - raise Exception('Export Test Level', str.format('Failed to add mesh ID {} to entity ID {}', str(meshGuid), str(newEntityIdStr))) - -general.save_level() -general.export_to_engine() - -general.exit() diff --git a/Tests/samples/__init__.py b/Tests/samples/__init__.py deleted file mode 100755 index e912252f4e..0000000000 --- a/Tests/samples/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - - All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - its licensors. - - For complete copyright and license terms please see the LICENSE at the root of this - distribution (the "License"). All use of this software is governed by the License, - or, if provided, by the license below or the license accompanying this file. Do not - remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - diff --git a/Tests/samples/sample_tests.py b/Tests/samples/sample_tests.py deleted file mode 100755 index aef6dc0f97..0000000000 --- a/Tests/samples/sample_tests.py +++ /dev/null @@ -1,163 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Sample tests to demonstrate typical functionality of PythonTestTools and how to integrate into the BAT. -""" -# Workaround for tests which depend on old tools, before they are updated to ly_test_tools and Python 3 -import pytest -pytest.importorskip('test_tools') - -# System level imports -import os -import subprocess - -# Basic PythonTestTools imports, in most cases you should always import these -import test_tools.builtin.fixtures as fixtures -from test_tools import HOST_PLATFORM, WINDOWS_LAUNCHER - -# test_tools and shared are modules with a lot of useful utility functions already written, use them! -# Pick and choose the ones below that you need, don't just blindly copy/paste -import test_tools.launchers.phase -import test_tools.shared.process_utils as process_utils -from test_tools.shared.file_utils import gather_error_logs, clear_out_config_file, delete_screenshot_folder, move_file -from shared.network_utils import check_for_listening_port -from test_tools.shared.remote_console_commands import RemoteConsole -from test_tools.shared.waiter import wait_for - - -# This is where you should access lumberyard - building, asset processing, finding paths/logs, etc. Do NOT change or -# remove this unless you know what you're doing. -# See the documentation for PythonTestTools for more information. -workspace = fixtures.use_fixture(fixtures.builtin_empty_workspace_fixture, scope='function') - -# What are fixtures? -# Fixtures set up a testing process (or test) by running all necessary code to satisfy its preconditions. -# More reading here: https://docs.pytest.org/en/latest/fixture.html - -# This is a shared instance of the remote console that be used across multiple tests. It must have the @pytest.fixture! -# You should remove this if you aren't going to use the remote console. -@pytest.fixture -def remote_console_instance(request): - """ - Creates a remote console instance to send console commands. - """ - console = RemoteConsole() - - def teardown(): - try: - console.stop() - except: - pass - - request.addfinalizer(teardown) - - return console - - -# This is a shared instance of the launcher that can be used across multiple tests within this file and any that it -# includes. It must have the @pytest.fixture for pytest to automatically pass it around! -# You should remove this if you aren't going to use the level-specific launchers. -@pytest.fixture -def launcher_instance(request, workspace, level): - """ - Creates a launcher fixture instance with an extra teardown for error log grabbing. - """ - def teardown_launcher_copy_logs(): - """ - Tries to grab any error logs before moving on to the next test. - """ - - for file_name in os.listdir(launcher.workspace.release.paths.project_log()): - move_file(launcher.workspace.release.paths.project_log(), - launcher.workspace.artifact_manager.get_save_artifact_path(), - file_name) - - logs_exist = lambda: gather_error_logs( - launcher.workspace.release.paths.dev(), - launcher.workspace.artifact_manager.get_save_artifact_path()) - try: - test_tools.shared.waiter.wait_for(logs_exist) - except AssertionError: - print("No error logs found. Completing test...") - - request.addfinalizer(teardown_launcher_copy_logs) - - launcher = fixtures.launcher(request, workspace, level) - return launcher - -# For the rest of the file, these are sample tests that you should remove entirely, or cannibalize them to help your -# own test-writing process. -class TestSamplesAPBatch: - - # This is a shared instance of test teardown that will be used across all tests in this class. - # It must have the @pytest.fixture(autouse=True)! - @pytest.fixture(autouse=True) - def setup_teardown(self, request): - - # This is the teardown function that will be run after *each* test finishes - def teardown(): - pass - - # This is the setup section that will be run before *each* test starts - request.addfinalizer(teardown) - - # mark.BAT adds this test to the BAT. - # mark.test_case is used to link to your testrail id. - # mark.parameterize allows you to run the same test multiple times but with different parameters, such as platform, - # configuration, project, level, and more. See more on parameters here: https://docs.pytest.org/en/latest/parametrize.html - @pytest.mark.BAT - @pytest.mark.test_case(testrail_id='Foo') - @pytest.mark.parametrize('platform,configuration,project,spec', ( - pytest.param('win_x64_vs2017', 'profile', 'StarterGame', 'all', - marks=pytest.mark.skipif(HOST_PLATFORM != 'win_x64', reason='Only supported on Windows hosts')), - pytest.param('darwin_x64', 'profile', 'StarterGame', 'all', - marks=pytest.mark.skipif(HOST_PLATFORM != 'darwin_x64', reason='Only supported on Mac hosts')), - )) - def test_RunAPBatch_WorkspacePreconfigured_NoLeftoverProcessesExist(self, workspace): - """ - Tests that the Asset Processor Batch and run and doesn't leave leftover processes. - """ - # Your function docstrings (the above text) will be part of the test catalog! - - subprocess.check_call([os.path.join(workspace.release.paths.bin(), 'AssetProcessorBatch')]) - - # This is how you should do timeouts - wait_for(lambda: not process_utils.process_exists('AssetProcessorBatch', True), timeout=10) - - # Make sure to include an informative assert message to make debugging easier - assert not process_utils.process_exists('rc', True), 'rc process still exists' - assert not process_utils.process_exists('AssetBuilder', True), 'AssetBuilder process still exists' - - -# Notice that you can put the marks both on the class and on the individual methods (seen above). -@pytest.mark.BAT -@pytest.mark.parametrize("platform,configuration,project,spec,level", [ - pytest.param("win_x64_vs2017", "profile", "StarterGame", "all", "StarterGame", - marks=pytest.mark.skipif(not WINDOWS_LAUNCHER, reason="Only supported on Windows hosts")), - pytest.param("win_x64_vs2019", "profile", "StarterGame", "all", "StarterGame", - marks=pytest.mark.skipif(not WINDOWS_LAUNCHER, reason="Only supported on Windows hosts")) -]) -class TestSamplesRemoteConsole(object): - - # Notice here that both the launcher_instance and remote_console_instance fixtures are being reused from above - def test_LaunchRemoteConsoleAndLauncher_CanLaunch(self, launcher_instance, platform, configuration, project, spec, - level, remote_console_instance): - """ - Verifies launcher & remote console can successfully launch. Notice that there are no asserts here, and that is - because the called functions will raise exceptions if there is unexpected behavior. - """ - launcher_instance.launch() - - launcher_instance.run(test_tools.launchers.phase.TimePhase(120, 120)) - - test_tools.shared.waiter.wait_for(lambda: check_for_listening_port(4600), timeout=300, - exc=AssertionError('Port 4600 not listening.')) - - remote_console_instance.start() diff --git a/Tests/samples/sanity_test.py b/Tests/samples/sanity_test.py deleted file mode 100755 index 062bfeea63..0000000000 --- a/Tests/samples/sanity_test.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -A simple sanity test. -""" -import os -import pytest - -import ly_test_tools.builtin.helpers - - -class TestSanity: - - @pytest.fixture(autouse=True) - def setup_teardown(self, request, legacy_workspace): - def teardown(): - # Per-test cleanup goes here - pass - request.addfinalizer(teardown) # adds teardown to pytest important to hook before setup in case setup fails - - # Per-test setup goes here - - # builtin_empty_workspace_fixture doesn't configure lumberyard to run, it leaves all the configuration to - # the test. - # To run lumberayrd you need to at least setup 3rdParty and run setup assistant using the following lines. - # Alternatively, you can use 'builtin_workspace_fixture'. - # This sanity test only checks if the framework is sane, so there is no need to configure LY. - # workspace.run_waf_configure() - # workspace.setup_assistant.enable_default_capabilities() - - @pytest.mark.bvt - # Example of dynamically parametrized test, these parameters are consumed by the workspace fixture: - # TODO LY-109331 @pytest.mark.parametrize("platform", ["win_x64_vs2017", "win_x64_vs2019", "darwin_x64"]) - @pytest.mark.parametrize("platform", ["win_x64_vs2017", "win_x64_vs2019"]) - @pytest.mark.parametrize("configuration", ["profile"]) - @pytest.mark.parametrize("project", ["AutomatedTesting"]) - @pytest.mark.parametrize("spec", ["all"]) - def test_Paths_DevPathExists_PathItsADirectory(self, legacy_workspace, platform, configuration, project, spec): - # type: (WorkspaceManager, str, str, str, str) -> None - - # Test code goes here - # os.makedirs(workspace.paths.dev()) - - # These asserts verify that the parameters were correctly received by the workspace fixture - assert legacy_workspace.platform == platform, "Platform does not match parameters" - assert legacy_workspace.configuration == configuration, "Configuration does not match parameters" - assert legacy_workspace.project == project, "Project does not match parameters" - assert legacy_workspace.spec == spec, "Spec does not match parameters" - - # Verify that the dev folder exists - assert os.path.isdir(legacy_workspace.paths.dev()) diff --git a/Tests/shared/__init__.py b/Tests/shared/__init__.py deleted file mode 100755 index e912252f4e..0000000000 --- a/Tests/shared/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - - All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - its licensors. - - For complete copyright and license terms please see the LICENSE at the root of this - distribution (the "License"). All use of this software is governed by the License, - or, if provided, by the license below or the license accompanying this file. Do not - remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - diff --git a/Tests/shared/file_utils.py b/Tests/shared/file_utils.py deleted file mode 100755 index 98bf08c29a..0000000000 --- a/Tests/shared/file_utils.py +++ /dev/null @@ -1,182 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -import os -import shutil -import subprocess -import logging - -import test_tools.shared.file_system as file_system -from test_tools.shared.waiter import wait_for - -logger = logging.getLogger(__name__) - - -def clear_out_config_file(project_path, script_name): - """ - Clears out the specified config file to be empty. - :param project_path: The directory where the file is. - :param script_name: The file name. - """ - path = os.path.join(project_path, '{}.cfg'.format(script_name)) - - if os.path.exists(path): - # Clears read only flag - file_system.unlock_file(path) - - with open(path, 'w') as initialmap_script: - initialmap_script.write('') - - -def add_commands_to_config_file(config_file_dir, config_file_name, command_list): - """ - From the command list, appends each command to the specified config file. - :param config_file_dir: The directory the config file is contained in. - :param config_file_name: The config file name. - :param command_list: The commands to add to the file. - :return: - """ - config_file_path = os.path.join(config_file_dir, config_file_name) - os.chmod(config_file_path, 0755) - with open(config_file_path, 'w') as launch_config_file: - for command in command_list: - launch_config_file.write("{}\n".format(command)) - - -def gather_error_logs(dev_path, logs_path): - """ - Grabs all error logs (if there are any) and puts them into the specified logs path. - :param dev_path: Path to the dev directory. - :param logs_path: Path to the destination directory for the logs (the test results path). - """ - error_logs_sent = False - error_log_path = os.path.join(dev_path, 'error.log') - artifact_log_path = os.path.join(logs_path, 'error.log') - error_dump_path = os.path.join(dev_path, 'error.dmp') - artifact_dump_path = os.path.join(logs_path, 'error.dmp') - if os.path.exists(error_dump_path) and os.path.exists(error_log_path): - shutil.move(error_dump_path, artifact_dump_path) - shutil.move(error_log_path, artifact_log_path) - error_logs_sent = True - return error_logs_sent - - -def delete_screenshot_folder(platform_path): - """ - Deletes screenshot folder from platform path - :param platform_path: Platform Path - :return: None - """ - platform_path = r'{}\user\screenshots'.format(platform_path) - shutil.rmtree(platform_path, ignore_errors=True) - - -def move_file(src_dir, dest_dir, file_name, timeout=120): - """ - Attempts to move a file from the source directory to the destination directory. Raises an IOError if - the file is in use. - :param src_dir: Directory of the file to be moved. - :param dest_dir: Directory where the file will be moved to. - :param file_name: Name of the file to be moved. - :param timeout: Number of seconds to wait for the file to be released. - """ - file_path = os.path.join(src_dir, file_name) - if os.path.exists(file_path): - wait_for(lambda: move_file_check(src_dir, dest_dir, file_name), - timeout=timeout, - exc=IOError('Cannot move file {} while in use'.format(file_path))) - - -def move_file_check(src_dir, dest_dir, file_name): - """ - Moves file and checks if the file has been moved from the source to the destination directory. - """ - try: - shutil.move(os.path.join(src_dir, file_name), os.path.join(dest_dir, file_name)) - except OSError as e: - print e - return False - - return True - - -def revert_config_files(launcher): - """ - Reverts modified config files from test. Runs the perforce revert command. - :param launcher: The launcher instance to revert files from. - """ - files_revert = [os.path.join(launcher.workspace.release.paths.dev(), 'bootstrap.cfg'), - os.path.join(launcher.workspace.release.paths.dev(), 'AssetProcessorPlatformConfig.ini'), - launcher.workspace.release.paths.platform_config_file()] - - for file_path in files_revert: - subprocess.check_call(['p4', 'revert', file_path]) - subprocess.check_call(['p4', 'sync', '-f', file_path]) - - -def create_file(path, file_name): - """ - Creates a file with specified file name - :param path: The path of where the file needs to be created - :param file_name: The file name - :return: - """ - file_path = os.path.join(path, file_name) - - if not os.path.exists(file_path): - new_file = open(file_path, "w") - new_file.close() - else: - print "{} already exists".format(file_name) - -def delete_level(launcher, level_dir, timeout=120): - """ - Attempts to delete an entire level folder from the project. - :param launcher: The launcher instance to delete the level from. - :param level_dir: The level folder to delete - """ - - if (not level_dir): - logger.warning("level_dir is empty, nothing to delete.") - return - - full_level_dir = os.path.join(launcher.workspace.release.paths.project(), 'Levels', level_dir) - if (not os.path.isdir(full_level_dir)): - if (os.path.exists(full_level_dir)): - logger.error("level '{}' isn't a directory, it won't be deleted.".format(full_level_dir)) - else: - logger.info("level '{}' doesn't exist, nothing to delete.".format(full_level_dir)) - return - - wait_for(lambda: delete_check(full_level_dir), - timeout=timeout, - exc=IOError('Cannot delete directory {} while in use'.format(full_level_dir))) - -def delete_check(src_dir): - """ - Deletes directory and verifies that it's been deleted. - :param src_dir: The directory to delete - """ - try: - shutil.rmtree(src_dir) - except OSError as e: - logger.debug("Delete for '{}' failed: {}".format(src_dir, e)) - return False - - return (not os.path.exists(src_dir)) - -def get_log_file_path(launcher, project_name): - """ - Creates a log file path. - :param launcher: The launcher instance to get a path from. - :param project_name: The name of the project. - """ - return os.path.join(launcher.workspace.release.paths.dev(), 'Cache', project_name, 'pc', 'user', 'log', 'Editor.log') diff --git a/Tests/shared/hydra_test_utils.py b/Tests/shared/hydra_test_utils.py deleted file mode 100755 index cce54a0f45..0000000000 --- a/Tests/shared/hydra_test_utils.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -import logging -import os -import tempfile -import test_tools.shared.log_monitor -import test_tools.launchers.phase - -logger = logging.getLogger(__name__) - - -def prepare_cfg_file(editor_python_script_name, args=[]): - """ - Create a temporary .cfg file containing the python script and args to pass to the Editor. - Ex: If you pass in 'create_level.py', ['LevelName'], you'll get a cfg file with this: - pyRunFile create_level.py LevelName - :param editor_python_script_name: Name of script that will execute in the Editor. - :param args: Additional arguments for CFG, such as LevelName. - :return Config file for Hydra execution - """ - cfg_contents = '-- Auto-generated cfg file\n' - cfg_contents += 'pyRunFile ' + editor_python_script_name - for arg in args: - cfg_contents += ' ' + arg - cfg_contents += '\n' - - logger.debug("Preparing a cfg file with the following contents:\n{}".format(cfg_contents)) - f = tempfile.NamedTemporaryFile(mode='w+', suffix='.cfg', delete=False) - cfg_filename = f.name - f.write(cfg_contents) - f.close() - logger.debug("Cfg file name: {}".format(cfg_filename)) - return cfg_filename - - -def cleanup_cfg_file(cfg_filename): - """ - Removes the temporary cfg file created in prepare_cfg_file. - :param cfg_filename: Config file for Hydra execution to delete - """ - logger.debug('Cleaning up the generated cfg file') - if os.path.exists(cfg_filename): - os.remove(cfg_filename) - - -def launch_and_validate_results(test_directory, editor, editor_script, editor_timeout, expected_lines, cfg_args=[]): - """ - Creates a temporary config file for Hydra execution, runs the Editor with the specified script, and monitors for - expected log lines. - :param test_directory: Path to test directory that editor_script lives in. - :param editor: Configured editor object to run test against. - :param editor_script: Name of script that will execute in the Editor. - :param editor_timeout: Timeout for editor run. - :param expected_lines: Expected lines to search log for. - :param cfg_args: Additional arguments for CFG, such as LevelName. - """ - cfg_file_name = prepare_cfg_file(os.path.join(test_directory, editor_script), cfg_args) - - logger.debug("Running automated test: {}".format(editor_script)) - - editor.deploy() - editor.launch(["--skipWelcomeScreenDialog", "--autotest_mode", "--exec", cfg_file_name]) - - editorlog_file = os.path.join(editor.workspace.release.paths.project_log(), 'Editor.log') - - test_tools.shared.log_monitor.monitor_for_expected_lines(editor, editorlog_file, expected_lines) - - # Rely on the test script to quit after running - editor.run(test_tools.launchers.phase.WaitForLauncherToQuit(editor, editor_timeout)) diff --git a/Tests/shared/jenkins-3rdparty-symlink/symlink_utils.py b/Tests/shared/jenkins-3rdparty-symlink/symlink_utils.py deleted file mode 100755 index c8fa8b7023..0000000000 --- a/Tests/shared/jenkins-3rdparty-symlink/symlink_utils.py +++ /dev/null @@ -1,71 +0,0 @@ -""" - - All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - its licensors. - - For complete copyright and license terms please see the LICENSE at the root of this - distribution (the "License"). All use of this software is governed by the License, - or, if provided, by the license below or the license accompanying this file. Do not - remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -import argparse -import logging -import os -import subprocess - -logger = logging.getLogger(__name__) - - -def create_symlink(path, name, reference): - """ - Checks if the defined symlink exists and returns True if it does. If it does not, it will create a - new symlink and return True. Unsuccessful commands will return False. - :param path: Path to where symlink should be created. - :param name: Name of the symlink. - :param reference: Source path of directory. - """ - sym_path = os.path.join(path, name) - - if not os.path.exists(sym_path): - if os.path.isfile(reference): - proc = subprocess.Popen('cmd /c mklink "{}" "{}"'.format(name, reference), cwd=path, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - elif os.path.isdir(reference): - proc = subprocess.Popen('cmd /c mklink /J "{}" "{}"'.format(name, reference), cwd=path, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - exit_code = proc.wait() - - if exit_code == 0: - logger.info("Successfully created symlink from {} to {}".format(reference, sym_path)) - return True - elif "You do not have sufficient privilege to perform this operation" in proc.stderr.read(): - raise AssertionError("Permissions denied. You should be running this script with Admin rights.") - - else: - raise AssertionError("The command could not run successfully.") - - else: - logger.info("Directory or file already exists: {}".format(os.path.join(path, name))) - return False - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Creates 3rdParty symlink.') - parser.add_argument('-symlink_path', metavar='symlink_path', type=str, required=True, - help='The path to create the 3rdParty symlink. If folder exists, symlinks will be' - 'created inside the 3rdParty folder.') - parser.add_argument('-name', metavar='symlink_name', type=str, default='3rdParty', - help='Name of symlink created. Defaults to 3rdParty.') - parser.add_argument('-source_path', metavar='source_path', type=str, required=True, - help='The path where the source 3rdParty is located.') - args = parser.parse_args() - - sym_path = os.path.join(args.symlink_path, args.name) - - # If there is an existing directory, copy all source contents as symlinks. - if not create_symlink(args.symlink_path, args.name, args.source_path): - for file_dir in os.listdir(args.source_path): - create_symlink(sym_path, file_dir, os.path.join(args.source_path, file_dir)) diff --git a/Tests/shared/logging_utils.py b/Tests/shared/logging_utils.py deleted file mode 100755 index 0d822c077d..0000000000 --- a/Tests/shared/logging_utils.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -def extract_log_lines(start_line_marker, end_line_marker, log_file, platform, level): - """ - Parses log for a start and end marker, and extracts all lines between the markers. - :param start_line_marker: String that marks beginning of loglines to extract. - :param end_line_marker: String that marks end of loglines to extract. - :param log_file: Path to log to parse. - :param platform: Platform under test passed from test parameterization. - :param level: Level under test passed from test parameterization. - """ - extracted_log = '{}_{}_log.txt'.format(platform, level) - - with open(log_file) as log: - match = False - new_log = None - - for line in log: - if start_line_marker in line: - match = True - new_log = open(extracted_log, 'w') - elif end_line_marker in line: - break - elif match: - new_log.write(line) - if new_log: - new_log.close() diff --git a/Tests/shared/network_utils.py b/Tests/shared/network_utils.py deleted file mode 100755 index b51e12e2c5..0000000000 --- a/Tests/shared/network_utils.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -import logging -import psutil -import socket - - -logger = logging.getLogger(__name__) - - -def check_for_listening_port(port): - """ - Checks to see if the connection to the designated port was established. - :param port: Port to listen to. - :return: True if port is listening. - """ - port_listening = False - for conn in psutil.net_connections(): - if 'port={}'.format(port) in str(conn): - port_listening = True - return port_listening - - -def check_for_remote_listening_port(port, ip_addr='127.0.0.1'): - """ - Tries to connect to a port to see if port is listening. - :param port: Port being tested. - :param ip_addr: IP address of the host being connected to. - :return: True if connection to the port is established. - """ - port_listening = True - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - sock.connect((ip_addr, port)) - except socket.error as err: - if err.errno == 10061: - port_listening = False - finally: - sock.close() - return port_listening - - -def get_local_ip_address(): - """ - Finds the IP address for the primary ethernet adapter by opening a connection and grabbing its IP address. - :return: The IP address for the adapter used to make the connection. - """ - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - try: - # Connecting to Google's public DNS so there is an open connection - # and then getting the address used for that connection - sock.connect(('8.8.8.8', 80)) - host_ip = sock.getsockname()[0] - finally: - sock.close() - return host_ip diff --git a/Tests/shared/pipeline_utils.py b/Tests/shared/pipeline_utils.py deleted file mode 100755 index a909c7a685..0000000000 --- a/Tests/shared/pipeline_utils.py +++ /dev/null @@ -1,170 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Small library of functions to support autotests for asset processor - -""" - -import os -import shutil -import re -import ly_test_tools.environment.file_system as fs -import hashlib -import shutil -import logging - -logger = logging.getLogger(__name__) - -def compare_assets_with_cache(assets, assets_cache_path): - """ - Given a list of assets names, will try to find them (disrespecting file extensions) from project's Cache folder with test assets - :param assets: A list of assets to be compared with Cache - :param assets_cache_path: A path to cache test assets folder - :return: A tuple with two lists - first is missing in cache assets, second is existing in cache assets - """ - missing_assets = [] - existing_assets = [] - if os.path.exists(assets_cache_path): - files_in_cache = list(map(fs.remove_path_and_extension, os.listdir(assets_cache_path))) - for asset in assets: - file_without_ext = fs.remove_path_and_extension(asset).lower() - if file_without_ext in files_in_cache: - existing_assets.append(file_without_ext) - files_in_cache.remove(file_without_ext) - else: - missing_assets.append(file_without_ext) - else: - missing_assets = assets - return missing_assets, existing_assets - - -def copy_assets_to_project(assets, source_directory, target_asset_dir): - """ - Given a list of asset names and a directory, copy those assets into the target project directory - :param assets: A list of asset names to be copied - :param source_directory: A path string where assets are located - :param target_asset_dir: A path to project tests assets directory where assets will be copied over to - :return: None - """ - for asset in assets: - full_name = os.path.join(source_directory, asset) - shutil.copy(full_name, target_asset_dir) - - -def prepare_test_assets(assets_path, function_name, project_test_assets_dir): - """ - Given function name and assets cache path, will clear cache and copy test assets assigned to function name to project's folder - :param assets_path: Path to tests assets folder - :param function_name: Name of a function that corresponds to folder with assets - :param project_test_assets_dir: A path to project directory with test assets - :return: Returning path to copied assets folder - """ - test_assets_folder = os.path.join(assets_path, 'assets', function_name) - copy_assets_to_project(os.listdir(test_assets_folder), test_assets_folder, project_test_assets_dir) - return test_assets_folder - - -def find_joblog_file(joblogs_path, regexp): - """ - Given path to joblogs files and asset name in form of regexp, will try to find joblog file for provided asset; if multiple - will return first occurrence - :param joblogs_path: Path to a folder with joblogs files to look for needed file - :param regexp: Python Regexp containing name of the asset that was processed, for which we're looking joblog file for - :return: Full path to joblog file, empty string if not found - """ - for file_name in os.listdir(joblogs_path): - if re.match(regexp, file_name): - return os.path.join(joblogs_path, file_name) - return '' - - -def find_missing_lines_in_joblog(joblog_location, strings_to_verify): - """ - Given joblog file full path and list of strings to verify, will find all missing strings in the file - :param joblog_location: Full path to joblog file - :param strings_to_verify: List of string to look for in joblog file - :return: Subset of original strings list, that were not found in the file - """ - lines_not_found = [] - with open(joblog_location, 'r') as f: - read_data = f.read() - for line in strings_to_verify: - if line not in read_data: - lines_not_found.append(line) - return lines_not_found - - -def clear_project_test_assets_dir(test_assets_dir): - """ - On call - deletes test assets dir if it exists and creates new empty one - :param test_assets_dir: A path to tests assets dir - :return: None - """ - if os.path.exists(test_assets_dir): - fs.delete([test_assets_dir], True, True) - os.mkdir(test_assets_dir) - - -def get_files_hashsum(path_to_files_dir): - """ - On call - calculates md5 hashsums for filecontents. - :param path_to_files_dir: A path to files directory - :return: Returns a dict with initial filenames from path_to_files_dir as keys and their contents hashsums as values - """ - checksum_dict = {} - try: - for fname in os.listdir(path_to_files_dir): - with open(os.path.join(path_to_files_dir, fname), 'rb') as fopen: - checksum_dict[fname] = hashlib.sha256(fopen.read()).digest() - except IOError: - logger.error('An error occured trying to read file') - return checksum_dict - - -def append_to_filename(file_name, path_to_file, append_text, ignore_extension): - """ - Function for appending text to file and folder names - :param file_name: Name of a file or folder - :param path_to_file: Path to file or folder - :param append_text: Text to append - :param ignore_extension: True or False for ignoring extensions - :return: None - """ - new_name = '' - if not ignore_extension: - (name, extension) = file_name.split('.') - new_name = name + append_text + '.' + extension - else: - new_name = file_name + append_text - os.rename(os.path.join(path_to_file, file_name), os.path.join(path_to_file, new_name)) - - -def create_asset_processor_backup_directories(backup_root_directory, test_backup_directory): - """ - Function for creating the asset processor logs backup directory structure - :param backup_root_directory: The location where logs should be stored - :param test_backup_directory: The directory for the specific test being ran - :return: None - """ - if not os.path.exists(os.path.join(backup_root_directory, test_backup_directory)): - os.makedirs(os.path.join(backup_root_directory, test_backup_directory)) - - -def backup_asset_processor_logs(bin_directory, backup_directory): - """ - Function for backing up the logs created by asset processor to designated backup directory - :param bin_directory: The bin directory created by the lumberyard build process - :param backup_directory: The location where asset processor logs should be backed up to - :return: None - """ - ap_logs = os.path.join(bin_directory, 'logs') - - if os.path.exists(ap_logs): - destination = os.path.join(backup_directory, 'logs') - shutil.copytree(ap_logs, destination) diff --git a/Tests/shared/process_utils.py b/Tests/shared/process_utils.py deleted file mode 100755 index 33ab175600..0000000000 --- a/Tests/shared/process_utils.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -from contextlib import contextmanager -import psutil -import subprocess - - -@contextmanager -def managed_popen(args_list, cwd=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT): - """ - Context manager for subprocess.Popen objects which allows Popen to be used in a with/as statement. - This should not be used with processes that are to continue running outside of the with block. - :param args_list: Sequence of arguments as they would be passed to a terminal. - :param cwd: The current working directory to use when issuing the command. - :param stdout: File handle or pipe to use for stdout. - :param stderr: File handle or pipe to use for stderr. - """ - process = subprocess.Popen(args_list, cwd=cwd, stdout=stdout, stderr=stderr) - try: - yield process - finally: - if not process.poll(): - process.terminate() - - -def get_psutil_process(process_name): - """ - Gets a reference to a psutil.Process object with the given process name. - :return: A reference to the first process encountered with the given name or None if no process is found. - """ - for process in psutil.process_iter(): - if process_name == process.name(): - return process - return None diff --git a/Tests/shared/s3_utils.py b/Tests/shared/s3_utils.py deleted file mode 100755 index 403cf75642..0000000000 --- a/Tests/shared/s3_utils.py +++ /dev/null @@ -1,145 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -import boto3 -import botocore.exceptions -import logging -import os - -import test_tools.shared.file_system as file_system - -logger = logging.getLogger(__name__) -s3 = boto3.resource('s3') - - -class BucketNotExists(Exception): - pass - - -class KeyExistsError(Exception): - pass - - -class KeyDoesNotExistError(Exception): - pass - - -def create_folder_in_bucket(bucket_name, folder_key): - """ - Given bucket name and folder key will create specified folder if it doesn't exist - :param bucket_name: name of the bucket where folder will be created - :param folder_key: key in s3 where folder will be created (i.e. specifying full path to folder in s3) - :return: True if folder was successfully created, False otherwise, will raise BucketNotExists exception if - bucket doesn't exist - """ - if not bucket_exists_in_s3(bucket_name): - raise BucketNotExists("Bucket {} does not exist.".format(bucket_name)) - - if key_exists_in_bucket(bucket_name, '{}/'.format(folder_key)): - logger.error("Key {} already exists in bucket {}".format(folder_key, bucket_name)) - return False - - s3_bucket = s3.Bucket(bucket_name) - logger.info("Creating {} folder in a {} bucket".format(folder_key, bucket_name)) - s3_bucket.put_object(Bucket=bucket_name, Key=(folder_key+'/')) - return True - - -def upload_to_bucket(bucket_name, file_path, file_key=None, overwrite=False): - """ - Uploads a given file to the given S3 bucket. - :param bucket_name: Name of the S3 bucket where the file should be uploaded. - :param file_path: Full Path to the target file on hard drive. - :param file_key: Needed path to file on s3 (including file name). - :param overwrite: Overwrite the key if it exists. - """ - if not bucket_exists_in_s3(bucket_name): - s3.create_bucket(Bucket=bucket_name) - - s3_bucket = s3.Bucket(bucket_name) - - if file_key is None: - file_key = os.path.basename(file_path) - - if not overwrite and key_exists_in_bucket(bucket_name, file_key): - raise KeyExistsError("Key '{}' already exists in S3 bucket {}".format(file_key, bucket_name)) - - s3_bucket.upload_file(file_path, file_key) - logger.info("Uploading {} to S3 bucket {}".format(file_key, bucket_name)) - - -def download_from_bucket(bucket_name, file_key, destination_dir, file_name=None): - """ - Download the given key from the given S3 bucket to the given destination. Logs an error if there is not enough \ - space available for the download. - :param bucket_name: Name of the S3 bucket containing the desired file. - :param file_key: Name of the file stored in S3. - :param destination_dir: Directory where the file should be downloaded to. - :param file_name: The name of the file you want to save it as. Defaults to the file_key. - """ - bucket_exists_in_s3(bucket_name) - - if not key_exists_in_bucket(bucket_name, file_key): - raise KeyDoesNotExistError("Key '{}' does not exist in S3 bucket {}".format(file_key, bucket_name)) - - obj_summary = s3.ObjectSummary(bucket_name, file_key) - required_space = obj_summary.size - disk_name = os.path.splitdrive(destination_dir)[0] - - file_system.check_free_space(disk_name, required_space, "Insufficient space available for download:") - - if not os.path.exists(destination_dir): - os.makedirs(destination_dir) - - if file_name is None: - file_name = file_key - destination_path = os.path.join(destination_dir, file_name) - s3.Object(bucket_name, file_key).download_file(destination_path) - logger.info("Downloading {} to {}".format(file_key, destination_path)) - - -def bucket_exists_in_s3(bucket_name): - """ - Verifies that the S3 bucket exists. - :param bucket_name: Name of the S3 bucket that may or may not exist. - :return: True if the bucket exists. False otherwise. - """ - bucket_exists = True - - try: - s3.meta.client.head_bucket(Bucket=bucket_name) - except botocore.exceptions.ClientError as err: - if err.response['Error']['Code'] == '404': - bucket_exists = False - - return bucket_exists - - -def key_exists_in_bucket(bucket_name, file_key): - """ - Verifies that the given key does not already exist in the given S3 bucket. - :param bucket_name: Name of the S3 bucket that may or may not contain the file key. - :param file_key: Name of the file key in question. - :return: True if the key exists. False otherwise. - """ - key_exists = True - obj_summary = s3.ObjectSummary(bucket_name, file_key) - - # Attempting to access any member of ObjectSummary for a nonexistent key will throw an exception - # There is no built-in way to check key existence otherwise - try: - obj_summary.size - except botocore.exceptions.ClientError as err: - if err.response['Error']['Code'] == '404': - key_exists = False - - return key_exists - diff --git a/Tests/shared/screenshot_utils.py b/Tests/shared/screenshot_utils.py deleted file mode 100755 index 0c64bb848a..0000000000 --- a/Tests/shared/screenshot_utils.py +++ /dev/null @@ -1,203 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -import os -import botocore.exceptions -import string -import boto3 - -from test_tools.shared.file_utils import move_file - -import test_tools.launchers.phase as phase -import shared.s3_utils as s3_utils -from test_tools.shared.remote_console_commands import get_screenshot_command -from test_tools.shared.waiter import wait_for -from test_tools import HOST_PLATFORM -from test_tools.shared.images.qssim import qssim as compare_screenshots -from test_tools.shared.launcher_testlib import retry_console_command - - -def take_screenshot(remote_console_instance, launcher, screenshot_name): - """ - Takes an in game screenshot using the remote console instance passed in, validates that the screenshot exists - and then renames that screenshot to something defined by the user of this function. - :param remote_console_instance: Remote console instance that is attached to a specific launcher instance - :param launcher: Launcher instance so we can use the file exists functionality provided by test_tools - :param screenshot_name: Name of the screenshot - :return: None - """ - get_screenshot_command(remote_console_instance) - screenshot_path = os.path.join(launcher.workspace.release.paths.platform_cache(), 'user', 'screenshots') - launcher.run(phase.FileExistsPhase(os.path.join(screenshot_path, 'screenshot0000.jpg'))) - wait_for(lambda: rename_screenshot(screenshot_path, screenshot_name), - timeout=120, - exc=AssertionError('Screenshot at path:{} and with name:{} is still in use.'.format(screenshot_path, screenshot_name))) - - -def rename_screenshot(screenshot_path, screenshot_name): - """ - Tries to rename the screenshot when the file is done being written to - :param screenshot_path: Path to the Screenshot folder - :param screenshot_name: Name we wish to change the screenshot to - :return: True when operation is completed, False if the file is still in use - """ - try: - print 'Trying to rename {} to {}.'.format(os.path.join(screenshot_path, 'screenshot0000.jpg'), os.path.join(screenshot_path, '{}.jpg'.format(screenshot_name))) - os.rename(os.path.join(screenshot_path, 'screenshot0000.jpg'), - os.path.join(screenshot_path, '{}.jpg'.format(screenshot_name))) - return True - except OSError as e: - print ('Found error {0} when trying to rename screenshot. {1}'.format(str(e), str(e.message))) - return False - - -def move_screenshots(screenshot_path, file_type, logs_path): - """ - Moves screenshots of a specific file type to the flume location so we can gather all of the screenshots we took. - :param screenshot_path: Path to the screenshot folder - :param file_type: Types of Files to look for. IE .jpg, .tif, etc - :param logs_path: Path where flume gathers logs to be upload - """ - for file_name in os.listdir(screenshot_path): - if file_name.endswith(file_type): - move_file(screenshot_path, logs_path, file_name) - - -def screenshot_command(remote_console_instance, command_to_run, expected_log_line): - """ - This is just a helper function to help send and validate against screenshot console commands. - :param remote_console_instance: Remote console instance - :param command_to_run: The Screenshot command that you wish to run - :param expected_log_line: The console log line to expect in order to set the event to true - :return: - """ - return retry_console_command(remote_console_instance, command_to_run, expected_log_line) - - -def get_screenshot_command_with_retries(remote_console_instance): - """ - Used for an in-game screenshot - :param remote_console_instance: Remote console instance - :return: None - """ - if (HOST_PLATFORM == 'win_x64'): - command = 'Screenshot: @user@\screenshots/' - else: - command = 'Screenshot: @user@/screenshots/' - - wait_for(lambda: screenshot_command(remote_console_instance, 'r_GetScreenShot 1', command), timeout=240, - exc=AssertionError('Screenshot command failed')) - - -def take_screenshot_with_retries(remote_console_instance, launcher, screenshot_name): - """ - Takes an in game screenshot using the remote console instance passed in, validates that the screenshot exists - and then renames that screenshot to something defined by the user of this function. - :param remote_console_instance: Remote console instance that is attached to a specific launcher instance - :param launcher: Launcher instance so we can use the file exists functionality provided by test_tools - :param screenshot_name: Name of the screenshot - :return: None - """ - get_screenshot_command_with_retries(remote_console_instance) - screenshot_path = os.path.join(launcher.workspace.release.paths.platform_cache(), 'user', 'screenshots') - launcher.run(phase.FileExistsPhase(os.path.join(screenshot_path, 'screenshot0000.jpg'))) - wait_for(lambda: rename_screenshot(screenshot_path, screenshot_name), - timeout=120, - exc=AssertionError('Screenshot taken is still in use')) - - -def compare_golden_image(similarity_threshold, screenshot, screenshot_path, golden_image_name, - golden_image_path=None): - """ - This function assumes that your golden image filename contains the same base screenshot name and the word "golden" - ex. pc_gamelobby_golden - - :param similarity_threshold: A float from 0.0 - 1.0 that determines how similar images must be or an asserts - :param screenshot: A string that is the full name of the screenshot (ex. 'gamelobby_host.jpg') - :param screenshot_path: A string that contains the path to the screenshots - :param golden_image_path: A string that contains the path to the golden images, defaults to the screenshot_path - :return: - """ - if golden_image_path is None: - golden_image_path = screenshot_path - - mean_similarity = compare_screenshots('{}\{}'.format(screenshot_path, screenshot), - '{}\{}'.format(golden_image_path, golden_image_name)) - assert mean_similarity > similarity_threshold, \ - '{} screenshot comparison failed! Mean similarity value is: {}'\ - .format(screenshot, mean_similarity) - - -def take_screenshot_and_compare(remote_console, launcher, screenshot, similarity_threshold, - golden_image_name, screenshot_path=None, file_type='.jpg'): - """ - Takes a screenshot and compares it with its golden image. This utilizes the take_screenshot_with_retries function - which is only used for PC. There are some assumptions with the golden image naming convention that is explained in - the compare_golden_image function. This also assumes it is a jpg file. - - This function enforces a naming convention such that the golden image and screenshot share part of the same name. - Also, the screenshot will be appended with the screenshot_key as shown below. - - The screenshot name will be screenshot_base + screenshot_key + filetype - (ex. 'gamelobby_host.jpg', 'MultiplayerSampleClient_1.png') - - :param screenshot_base: A string that is the base name of the screenshot (ex. 'gamelobby') - :param screenshot_key: A string that acts as a key identifier for the screenshot. - :param similarity_threshold: A float from 0.0 - 1.0 that determines how similar images must be or it asserts - :param screenshot_path: A string for the screenshot path. Defaults to user/screenshots - :param file_type: A string for the screenshot filetype. Defaults to '.jpg' - :return: - """ - if screenshot_path is None: - screenshot_path = r'{}\user\screenshots'.format(launcher.workspace.release.paths.platform_cache()) - - take_screenshot_with_retries(remote_console, launcher, screenshot) - compare_golden_image(similarity_threshold, '{}{}'.format(screenshot, file_type), - screenshot_path, golden_image_name) - - -def download_qa_golden_images(project_name, destination_dir, platform): - """ - Downloads the golden images for a specified project from s3. The project_name, platform, and filetype are used to - filter which images will be downloaded as the golden images. - - https://s3.console.aws.amazon.com/s3/buckets/ly-qae-jenkins-configs/golden-images/?region=us-west-1&tab=overview - - :param project_name: a string of the project name of the folder in s3. ex: 'MultiplayerSample' - :param destination_dir: a string of where the images will be downloaded to - :param platform: a string for the platform type ('pc', 'android', 'ios', 'darwin') - :param filetype: a string for the file type. ex: '.jpg', '.png' - :return: - """ - bucket_name = 'ly-qae-jenkins-configs' - path = 'golden-images/{}/{}/'.format(project_name, platform) - - if not s3_utils.key_exists_in_bucket(bucket_name, path): - raise s3_utils.KeyDoesNotExistError("Key '{}' does not exist in S3 bucket {}".format(path, bucket_name)) - for image in s3_utils.s3.Bucket(bucket_name).objects.filter(Prefix=path): - file_name = string.replace(image.key, path, '') - if file_name != '': - s3_utils.download_from_bucket(bucket_name, image.key, destination_dir, file_name) - - -def prepare_for_screenshot_compare(remote_console_instance): - """ - Prepares launcher for screenshot comparison. Removes any debug text and antialiasing that may result in interference - with the comparison. - - :param remote_console_instance: Remote console instance that is attached to a specific launcher instance - :return: - """ - wait_for(lambda: retry_console_command(remote_console_instance, 'r_displayinfo 0', - '$3r_DisplayInfo = $60 $5[DUMPTODISK, RESTRICTEDMODE]$4'), timeout=120) - wait_for(lambda: retry_console_command(remote_console_instance, 'r_antialiasingmode 0', - '$3r_AntialiasingMode = $60 $5[]$4'), timeout=120) - diff --git a/Tests/shared/shader_compile_server_utils.py b/Tests/shared/shader_compile_server_utils.py deleted file mode 100755 index 4c93970561..0000000000 --- a/Tests/shared/shader_compile_server_utils.py +++ /dev/null @@ -1,120 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -import logging -import os -import psutil -import subprocess - -import test_tools.shared.file_system as file_system -from test_tools.shared.process_utils import kill_processes_named -from test_tools import HOST_PLATFORM - -logger = logging.getLogger(__name__) - - -def start_shader_compile_server(tools_dir, build_config, win_x64_compiler, clean=True): - """ - Starts the shader compile server for the given compiler version. Failure is currently not checked due to issues - with detecting if the process is alive immediately after requesting it to be spun up. - :param tools_dir: Tools directory inside of the dev root. - :param build_config: Build configuration name (profile, debug, etc.). - :param win_x64_compiler: Windows compiler version used to build the project. - :param clean: Removes Cache, Error, Shaders, and Temp directories if set to True. - """ - shader_compile_server_dir = os.path.join(tools_dir, 'CrySCompileServer', 'x64', build_config) - os.chdir(shader_compile_server_dir) - - shader_compile_server_exe = build_shader_compile_server_file_name(win_x64_compiler) - - # Only one shader compile server can run at a time, so kill the currently running process if any - kill_processes_named(shader_compile_server_exe) - - if clean: - clean_shader_compile_server_files(tools_dir, build_config) - - logger.info("Attempting to start shader compile server") - # Running with basic user permissions since the shader compile server warns against running as admin - subprocess.Popen(['RunAs', '/trustlevel:0x20000', shader_compile_server_exe]) - - -def start_mac_shader_compile_server(tools_dir, build_config, clean=True): - """ - Mac version of starting shader compiler given build configuration. Failure is currently not checked due to issues - with detecting if the process is alive immediately after requesting it to be spun up. - :param tools_dir: Tools directory inside of the dev root. - :param build_config: Build configuration name (profile, debug, etc.). - :param clean: Removes Cache, Error, Shaders, and Temp directories if set to True. - :return: Returns the actual process. - """ - shader_compile_server_dir = os.path.join(tools_dir, 'CrySCompileServer', 'osx', build_config) - os.chdir(shader_compile_server_dir) - - kill_processes_named('CrySCompileServer') - - if clean: - clean_shader_compile_server_files(tools_dir, build_config) - - logger.info("Attempting to start shader compile server") - subprocess.Popen('./CrySCompileServer', shell=True) - - -def build_shader_compile_server_file_name(win_x64_compiler): - """ - Puts together the shader compile server file name based on the specified VC compiler version. - :param win_x64_compiler: The VC compiler version specified in vsyyyy format, where yyyy is a year (ex: vs2017) - :return: The shader compile server file name complete with extension. - """ - shader_compile_server_exe = 'CrySCompileServer' - - return '{}.exe'.format(shader_compile_server_exe) - - -def clean_shader_compile_server_files(tools_dir, build_config): - """ - Removes the shader compile server generated Cache, Error, Shaders, and Temp directories. - :param tools_dir: Tools directory inside of the dev root. - :param build_config: Build configuration name (profile, debug, etc.). - """ - if HOST_PLATFORM == 'win_x64': - shader_compile_server_dir = os.path.join(tools_dir, 'CrySCompileServer', 'x64', build_config) - else: - shader_compile_server_dir = os.path.join(tools_dir, 'CrySCompileServer', 'osx', build_config) - os.chdir(shader_compile_server_dir) - - shader_compile_server_dirs = [os.path.join(shader_compile_server_dir, 'Cache'), - os.path.join(shader_compile_server_dir, 'Error'), - os.path.join(shader_compile_server_dir, 'Shaders'), - os.path.join(shader_compile_server_dir, 'Temp')] - if not file_system.delete(shader_compile_server_dirs, True, True): - directories_still_present = [] - for directory in shader_compile_server_dirs: - if os.path.exists(directory): - directories_still_present.append(directory) - raise RuntimeError("Failed to clean folders {} from directory {}".format(directories_still_present, - shader_compile_server_dir)) - - -def stop_shader_compile_server(): - """ - Finds any process with CrySCompileServer in its name and kills it. - """ - # This is necessary because the shader compile server is spawned from another process which - # immediately terminates after spawning its child - for process in psutil.process_iter(): - try: - if 'CrySCompileServer' in process.name(): - success_code = process.kill() - if success_code == 0: - logger.error("Failed to terminate CrySCompileServer process") - except psutil.NoSuchProcess: - # Process was already killed but caught as a zombie process, so pass as normal. - pass diff --git a/Tests/shared/substring.py b/Tests/shared/substring.py deleted file mode 100755 index 45c5f6d3af..0000000000 --- a/Tests/shared/substring.py +++ /dev/null @@ -1,73 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -String and search related functions. -""" -import os -import re - - -def in_file(file_path, pattern_string): - """ - This method checks if pattern_string exists in any line in the file specified in file_path. - It cannot match multi-line patterns. - :param file_path: files path for the file to search in - :param pattern_string: string to search for. - :return: True if the String is found, False otherwise. - """ - if not os.path.exists(file_path): - raise RuntimeError("File does not exist at {}".format(file_path)) - if not pattern_string: - raise RuntimeError("Must provide string to search for") - - with open(file_path, "r") as game_log: - for line in game_log.readlines(): - if pattern_string in line: - return True - return False - - -def regex_in_file(file_path, pattern_string): - """ - This method uses regex to check if pattern_string exists in the file specified in file_path. - It can match multi-line patterns but is a lot heavier as it parses whole file as string. - :param file_path: files path for the file to search in - :param pattern_string: regex-pattern to search for. - :return: True if the regex-pattern is found found in file, False otherwise. - """ - if not os.path.exists(file_path): - raise RuntimeError("File does not exist at {}".format(file_path)) - if not pattern_string: - raise RuntimeError("Must provide string to search for") - - re.compile(pattern_string) - with open(file_path, "r") as game_log: - return re.search(pattern_string, game_log.read()) - - -def regex_in_lines_in_file(file_path, pattern_string): - """ - This method uses regex to check if pattern_string exists in any line in the file specified in file_path. - It cannot match multi-line patterns, but will often more performant than regex_in_file. - :param file_path: files path for the file to search in - :param pattern_string: regex-pattern to search for. - :return: True if the regex-pattern is found found in file, False otherwise. - """ - if not os.path.exists(file_path): - raise RuntimeError("File does not exist at {}".format(file_path)) - if not pattern_string: - raise RuntimeError("Must provide string to search for") - - re.compile(pattern_string) - with open(file_path, "r") as game_log: - for line in game_log.readlines(): - if re.search(pattern_string, line): - return True - return False diff --git a/Tests/shared/windows_registry_utils.py b/Tests/shared/windows_registry_utils.py deleted file mode 100755 index 9f10317ea9..0000000000 --- a/Tests/shared/windows_registry_utils.py +++ /dev/null @@ -1,96 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Small library of functions to support autotests for utilizing Windows Utilities - -""" - -import logging -import winreg -logger = logging.getLogger(__name__) - - -def registry_key_exists(registry_hive, registry_key, registry_subkey): - """ - Searches the Windows registry for the existance of a registry key - :param registry_hive: The hive in which to find keys & subkeys. EG: HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER - :param registry_key: The bin directory from which to launch the AssetProcessor executable. - :param registry_subkey: The subkey that can contain a value assignment - :return: A boolean value of the existance of the key - """ - - try: - registryKey = winreg.OpenKey(registry_hive, registry_key) - logger.debug("Registry Key: {0} found.".format(registry_key)) - - winreg.QueryValueEx(registryKey, registry_subkey) - logger.debug("Registry Subkey: {0} found.".format(registry_subkey)) - - registryKey.Close() - - return True - except WindowsError: - # Do not raise an assert since tests could revolve around a non-existant key - logger.debug("Registry SubKey: {0} was not found.".format(registry_key)) - return False - - -def get_registry_key_value(registry_hive, registry_key, registry_subkey): - """ - If a registry key exists, it will return the value else return None - :param registry_hive: The hive in which to find keys & subkeys. EG: HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER - :param registry_key: The bin directory from which to launch the AssetProcessor executable. - :param registry_subkey: The subkey that can contain a value assignment - :return: The value of the registry key value - """ - - if registry_key_exists(registry_hive, registry_key, registry_subkey): - - registryKey = winreg.OpenKey(registry_hive, registry_key) - logger.debug("Registry Key: {0} found.".format(registry_key)) - - subkeyValue = winreg.QueryValueEx(registryKey, registry_subkey) - logger.debug("Registry Subkey: {0} value found is is set to {1}".format(registry_subkey, subkeyValue)) - - registryKey.Close() - logger.debug("Registry Key hander closed") - - return str(subkeyValue[0]) # Index 0 contains the value, Index 1 contains the registry value type - else: - assert None, "Could not retrieve Registry Key Value since Registry Key '{0}' was not found.".format(registry_key) - - -def check_registry_key_value(registry_hive, registry_key, registry_subkey, expected='', case_sensitive=True): - """ - If registry key exists, then case insensitively checks that the registry key value is as expected - :param registry_hive: The hive in which to find keys & subkeys. EG: HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER - :param registry_key: The bin directory from which to launch the AssetProcessor executable. - :param registry_subkey: The subkey that can contain a value assignment - :param expected: The expected value to be found in the registry - :param case_sensitive: Whether or not to perform a case sensitive or insentive validation - :return: A boolean value if the registry key value matches expected - """ - - if registry_key_exists(registry_hive, registry_key, registry_subkey): - - subkeyValue = get_registry_key_value(registry_hive, registry_key, registry_subkey) - - if case_sensitive: - logger.debug("Case sensitive comparison of Subkey '{0}' to Expected Value '{1}'" - .format(subkeyValue, expected)) - return subkeyValue == expected - else: - logger.debug("Case insensitive comparison of Subkey '{0}' to Expected Value '{1}'" - .format(subkeyValue, expected)) - return subkeyValue.lower() == expected.lower() - else: - logger.debug("Could not compare Subkey Value to expected value, Subkey '{0}' was not found at Key {1}." - .format(registry_subkey, registry_key)) - return False diff --git a/Tests/shared/windows_utils.py b/Tests/shared/windows_utils.py deleted file mode 100755 index a6bcff8d91..0000000000 --- a/Tests/shared/windows_utils.py +++ /dev/null @@ -1,130 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Small library of functions to support autotests for utilizing Windows Utilities - -""" - -import logging -import psutil -import os -import subprocess -import pyscreenshot as winScreenshot - -from _winreg import * - -logger = logging.getLogger(__name__) - -def kill_app_by_name(project): - """ - Kills the app on windows for the specified app name - :param name: name of app running on the devkit to kill - """ - process_name = project + 'Launcher.exe' - for proc in psutil.process_iter(): - # check whether the process to kill name matches - if proc.name() == process_name: - proc.kill() - -def take_screenshot(result_path, image_filename, project=None): - """ - Takes a windows screenshot - :param result_path: path for the output file - :param image_filename: filename for the new image - :param project: project name for the running process, if provided only the window for this process will be captured - """ - if not os.path.exists(result_path): - os.mkdir(result_path) - - filename = "{}.png".format(image_filename) - filename = os.path.join(result_path, filename) - - image = winScreenshot.grab() # bbox=(10, 10, 510, 510)) # X1,Y1,X2,Y2 - image.save(filename) - - if not os.path.exists(filename): - # Capture failed - return False - - return True - -def launch(bin_path, project, parameters = None): - command_line = [os.path.join(bin_path, project + 'Launcher.exe')] - if parameters != None: - command_line = command_line + parameters - process = subprocess.Popen(command_line, stdout=subprocess.PIPE) - process.poll() - -def check_registry_key_exits(registry_key): - """ - Searches the Windows registry for the existance of a registry key - :param registry_key: The bin directory from which to launch the AssetProcessor executable. - :return: A boolean value of the existance of the key - """ - try: - registryHandle = ConnectRegistry(None, HKEY_LOCAL_MACHINE) - registryKey = OpenKey(registryHandle, registry_key) - - logger.info("Registry Key: {0} found.".format(registry_key)) - registryKey.Close() - registryHandle.Close() - - return True - except: - logger.error("Registry Key: {0} was not found.".format(registry_key)) - return False - -def get_registry_key_value(registry_key): - """ - If a registry key exists, it will return the value else return None - :param registry_key: The bin directory from which to launch the AssetProcessor executable. - :return: The value of the registry key value - """ - if check_registry_key_exits(registry_key): - logger.log("Retrieving the value of Registry Key '{0}'" - .format(registry_key)) - - registryHandle = ConnectRegistry(None, HKEY_LOCAL_MACHINE) - registryKey = OpenKey(registryHandle, registry_key) - - keyValue = registryKey.Value() - - registryKey.Close() - registryHandle.Close() - - return keyValue - else: - logger.error("Could not retrieve Registry Key Value since Registry Key '{0}' was not found." - .format(registry_key)) - return None - - -def check_registry_key_value(registry_key, expected): - """ - If registry key exists, then checks that the registry key value is as expected - :param registry_key: The bin directory from which to launch the AssetProcessor executable. - :return: A boolean value if the registry key value matches expected - """ - if check_registry_key_exits(registry_key): - - keyValue = get_registry_key_value(registry_key) - - if str.lower(keyValue) == str.lower(expected): - logger.info("The value of Registry Key '{0}' matched the expected '{1}'" - .format(registry_key, expected)) - return True - else: - logger.error("The value of Registry Key '{0}' die not match the expected '{1}'" - .format(registry_key, expected)) - return False - else: - logger.error("Could not compare Registry Key Value to expected Registry Key '{0}' was not found." - .format(registry_key)) - return False diff --git a/Tests/test_lib/launcher_testlib.py b/Tests/test_lib/launcher_testlib.py deleted file mode 100755 index 00ff8bc598..0000000000 --- a/Tests/test_lib/launcher_testlib.py +++ /dev/null @@ -1,194 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -This launcher_testlib file is used for a collection of reusable functionality that QA will use in their scripts. -""" - -import os - -import test_tools.shared.asset_processor_utils as asset_processor_utils -import test_tools.shared.file_utils as file_utils -import shared.shader_compile_server_utils as compile_server -import test_tools.shared.file_system as file_system - - -def setup_win_launcher_test(launcher): - """ - Assert that the launcher was able to build and that assets were processed successfully. - - :param launcher: The test-tools Launcher to be built. - """ - assert_build_success(launcher) - assert_process_assets(launcher) - - -def setup_mac_launcher_test(launcher, configuration): - """ - Assert that the launcher was able to build and that assets were processed successfully. Also starts the Mac - shader compile server. - - :param launcher: The test-tools Launcher to be built. - :param configuration: The shader compile server configuration to be launched. - """ - assert_build_success(launcher) - assert_process_assets(launcher) - start_mac_shader_compile_server(launcher, configuration, '127.0.0.1') - - -def setup_android_launcher_test(launcher, configuration, win_x64_compiler_version): - """ - Builds the Windows asset processor, asserts that the launcher was able to build and that assets were processed - successfully, and starts the Windows shader compile server. - - :param launcher: The test-tools Launcher to be built. - :param configuration: The shader compile server configuration to be launched. - :param win_x64_compiler_version: The msvc compiler version used to build the shader compile server executable. - """ - assert_asset_processor_build_success(launcher.workspace.release, win_x64_compiler_version) - assert_build_success(launcher) - assert_process_assets(launcher) - - start_shader_compile_server(launcher, configuration, '127.0.0.1', win_x64_compiler_version) - - -def assert_asset_processor_build_success(release, win_x64_compiler_version): - """ - Builds the win_x64 AssetProcessor and asserts on build failure. - - For use with platforms that require Windows tools but not the Windows launcher. - - :param release: The test-tools Release which holds path info for the test environment in use. - :param win_x64_compiler_version: The msvc compiler version used to build the AssetProcessor. - """ - asset_proc_build_success = asset_processor_utils.build_win_x64(release.paths.dev(), release.configuration, - win_x64_compiler_version) - assert asset_proc_build_success, "AssetProcessor did not build properly" - - -def assert_process_assets(launcher): - """ - Runs the Asset Processor Batch and asserts if any assets fail to process. - - :param launcher: The test-tools Launcher to be built. - """ - process_assets_success = launcher.workspace.release.process_assets() - assert process_assets_success, 'Assets did not process correctly' - - -def assert_build_success(launcher): - """ - Runs the build command for specified launcher configuration and asserts if the build fails. - - :param launcher: The test-tools Launcher to be built. - """ - build_success = launcher.workspace.release.build() - assert build_success, "{} - Build Failed! - {} {} {}".format(launcher.workspace.release.project, - launcher.workspace.release.platform, - launcher.workspace.release.configuration, - launcher.workspace.release.spec) - -def start_shader_compile_server(launcher, configuration, ip_addr, win_x64_compiler_version): - """ - Deals with the extra setup to start the Windows shader compiler. - - :param launcher: The test-tools Launcher to be built. - :param configuration: The shader compile server configuration to be launched. - :param ip_addr: The IP address of the remote shader compile server and AssetProcessor. - :param win_x64_compiler_version: The msvc compiler version used to build the shader compile server executable. - """ - launcher.workspace.release.modify_bootstrap_setting('remote_ip', ip_addr) - launcher.workspace.release.modify_platform_setting('r_ShaderCompilerServer', ip_addr) - launcher.workspace.release.modify_platform_setting('log_RemoteConsoleAllowedAddresses', ip_addr) - compile_server.start_shader_compile_server(launcher.workspace.release.paths.tools(), configuration, - win_x64_compiler_version) - - -def start_mac_shader_compile_server(launcher, configuration, ip_addr='127.0.0.1'): - """ - Deals with the extra setup to start the Mac shader compiler. - - :param launcher: The test-tools Launcher to be built. - :param configuration: The shader compile server configuration to be launched. - :param ip_addr: The IP address of the remote shader compile server and AssetProcessor. - """ - launcher.workspace.release.modify_bootstrap_setting('remote_ip', ip_addr) - launcher.workspace.release.modify_platform_setting('r_ShaderCompilerServer', ip_addr) - launcher.workspace.release.modify_platform_setting('log_RemoteConsoleAllowedAddresses', ip_addr) - compile_server.start_mac_shader_compile_server(launcher.workspace.release.paths.tools(), configuration) - - -def set_launcher_startup_config_file(launcher, project_name, config_name, commands): - """ - Clears out the specified config_name file and adds the commands specified to the cfg file. - - :param launcher: The test-tools Launcher to be built. - :param project_name: Name of the game project in test. - :param config_name: Name of the config file holding the launcher's startup commands. - :param commands: List of commands to append to the specified config file. - """ - file_utils.clear_out_config_file(launcher.workspace.release.paths.project(), config_name) - config_path = os.path.join(launcher.workspace.release.paths.platform_cache(), project_name) - file_utils.add_commands_to_config_file(config_path, '{}.cfg'.format(config_name), commands) - - -def configure_setup(launcher, delete_logs=True, delete_shaders=True): - """ - Deletes old artifact folders and clears out any config files. - - :param launcher: - :param delete_logs: Delete the game project's logs directory if True. - :param delete_shaders: Delete the project's cache directory if True. - """ - if delete_logs: - delete_project_logs(launcher) - - if delete_shaders: - delete_shader_cache(launcher) - - file_utils.delete_screenshot_folder(launcher.workspace.release.paths.platform_cache()) - - file_utils.clear_out_config_file(launcher.workspace.release.paths.project(), 'initialmap') - file_utils.clear_out_config_file(launcher.workspace.release.paths.project(), 'autoexec') - - -def delete_project_logs(launcher): - """ - Deletes project logs in the launcher's project folder. - """ - if os.path.exists(launcher.workspace.release.paths.project_log()): - file_system.delete([launcher.workspace.release.paths.project_log()], True, True) - - -def delete_shader_cache(launcher, asset_type="pc"): - """ - Deletes shader cache in the launcher's project folder. - """ - user_folder = os.path.join(launcher.workspace.release.paths.project_cache(), asset_type, "user", "cache") - if os.path.exists(user_folder): - file_system.delete([user_folder], True, True) - - -def retry_console_command(remote_console, command, output, tries=10, timeout=10): - """ - Retries specified console command multiple times and asserts if it still can not send. - :param remote_console: the remote console connected to the launcher. - :param command: the command to send to the console. - :param output: The expected output to check if the command was sent successfully. - :param tries: The amount of times to try before asserting. - :param timeout: The amount of time in seconds to wait for each retry send. - :return: True if succeeded, will assert otherwise. - """ - while tries > 0: - check_command = remote_console.expect_log_line(output, timeout) - remote_console.send_command(command) - if check_command(): - return True - tries -= 1 - assert False, "Command \"{}\" failed to run in remote console.".format(command) diff --git a/Tests/workflow/__init__.py b/Tests/workflow/__init__.py deleted file mode 100755 index e912252f4e..0000000000 --- a/Tests/workflow/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - - All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - its licensors. - - For complete copyright and license terms please see the LICENSE at the root of this - distribution (the "License"). All use of this software is governed by the License, - or, if provided, by the license below or the license accompanying this file. Do not - remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - diff --git a/Tests/workflow/android/__init__.py b/Tests/workflow/android/__init__.py deleted file mode 100755 index e912252f4e..0000000000 --- a/Tests/workflow/android/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - - All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - its licensors. - - For complete copyright and license terms please see the LICENSE at the root of this - distribution (the "License"). All use of this software is governed by the License, - or, if provided, by the license below or the license accompanying this file. Do not - remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - diff --git a/Tests/workflow/android/workflow_android.py b/Tests/workflow/android/workflow_android.py deleted file mode 100755 index 4eb0f578b1..0000000000 --- a/Tests/workflow/android/workflow_android.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -""" -import logging -import pytest - -import workflow.shared.workflow_shared as workflow_shared \ No newline at end of file diff --git a/Tests/workflow/ios/__init__.py b/Tests/workflow/ios/__init__.py deleted file mode 100755 index e912252f4e..0000000000 --- a/Tests/workflow/ios/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - - All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - its licensors. - - For complete copyright and license terms please see the LICENSE at the root of this - distribution (the "License"). All use of this software is governed by the License, - or, if provided, by the license below or the license accompanying this file. Do not - remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - diff --git a/Tests/workflow/ios/workflow_ios.py b/Tests/workflow/ios/workflow_ios.py deleted file mode 100755 index 4eb0f578b1..0000000000 --- a/Tests/workflow/ios/workflow_ios.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -""" -import logging -import pytest - -import workflow.shared.workflow_shared as workflow_shared \ No newline at end of file diff --git a/Tests/workflow/mac/__init__.py b/Tests/workflow/mac/__init__.py deleted file mode 100755 index e912252f4e..0000000000 --- a/Tests/workflow/mac/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - - All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - its licensors. - - For complete copyright and license terms please see the LICENSE at the root of this - distribution (the "License"). All use of this software is governed by the License, - or, if provided, by the license below or the license accompanying this file. Do not - remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - diff --git a/Tests/workflow/mac/workflow_mac.py b/Tests/workflow/mac/workflow_mac.py deleted file mode 100755 index 4eb0f578b1..0000000000 --- a/Tests/workflow/mac/workflow_mac.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -""" -import logging -import pytest - -import workflow.shared.workflow_shared as workflow_shared \ No newline at end of file diff --git a/Tests/workflow/shared/__init__.py b/Tests/workflow/shared/__init__.py deleted file mode 100755 index e912252f4e..0000000000 --- a/Tests/workflow/shared/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - - All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - its licensors. - - For complete copyright and license terms please see the LICENSE at the root of this - distribution (the "License"). All use of this software is governed by the License, - or, if provided, by the license below or the license accompanying this file. Do not - remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - diff --git a/Tests/workflow/shared/workflow_shared.py b/Tests/workflow/shared/workflow_shared.py deleted file mode 100755 index 3581f9349e..0000000000 --- a/Tests/workflow/shared/workflow_shared.py +++ /dev/null @@ -1,95 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -This demos_testlib file is used for a collection of reusable functionality that QA will use in their scripts specific -to the setup of demo level tests. -""" -import sys - -import shared.network_utils as network_utils -from shared.screenshot_utils import move_screenshots, take_screenshot_with_retries - -from test_tools.shared.launcher_testlib import * - -import test_tools.shared.waiter -import test_tools.launchers.phase - - -def start_launcher(launcher): - """ - For PC: Used to start launcher and give time to load. - """ - launcher.launch() - launcher.run(test_tools.launchers.phase.TimePhase(120, 120)) - - -def remote_console_load_level(launcher, remote_console, level): - """ - Uses the remote console to use the map command to load a level and checks the console output for a successful load. - """ - command = 'map {}'.format(level) - load = remote_console.expect_log_line('LEVEL_LOAD_COMPLETE', 300) - retry_console_command(remote_console, command, "Executing console command '{}'".format(command)) - assert load(), "{} level failed to load.".format(level) - - # Allow one minute to let level fully render and to test for stability - launcher.run(test_tools.launchers.phase.TimePhase(60, 60)) - - -def start_remote_console(launcher, remote_console, on_devkit=False): - """ - Starts the remote console. Used in QA scripts that require the use of remote console. - """ - if on_devkit: - test_tools.shared.waiter.wait_for(lambda: network_utils.check_for_remote_listening_port(4600, launcher.ip), - timeout=600, exc=AssertionError('Port 4600 not listening.')) - else: - test_tools.shared.waiter.wait_for(lambda: network_utils.check_for_listening_port(4600), timeout=300, - exc=AssertionError('Port 4600 not listening.')) - - remote_console.start() - - # Allows remote console time to connect to launcher. - launcher.run(test_tools.launchers.phase.TimePhase(60, 60)) - - -def remote_console_take_screenshot(launcher, remote_console, level): - """ - Uses the remote console to run the r_GetScreenshot command to take a screenshot of the current launcher and move - the screenshot to the test results location. - """ - screenshot_path = os.path.join(launcher.workspace.release.paths.platform_cache(), "user", "screenshots") - take_screenshot_with_retries(remote_console, launcher, level) - if os.path.exists(screenshot_path): - move_screenshots(screenshot_path, '.jpg', launcher.workspace.artifact_manager.get_save_artifact_path()) - - -def build_setup(launcher, project_dir): - game_cfg = os.path.join(project_dir, 'game.cfg') - test_tools.shared.settings.edit_text_settings_file(game_cfg, 'sys_primaryUserSelectionEnabled', 0) - test_tools.shared.settings.edit_text_settings_file(game_cfg, 'sys_localUserLobbyEnabled', 0) - - -def enable_full_mode(launcher, console_remote_filesystem, console_paks): - print("Enabling FULL Mode") - launcher.workspace.release.modify_bootstrap_setting(console_remote_filesystem, 0) - launcher.workspace.release.modify_user_setting(console_paks, "False") - - -def enable_pak_mode(launcher, console_remote_filesystem, console_paks): - print("Enabling PAK Mode") - launcher.workspace.release.modify_bootstrap_setting(console_remote_filesystem, 0) - launcher.workspace.release.modify_user_setting(console_paks, "True") - - -def enable_vfs_mode(launcher, console_remote_filesystem, console_paks): - print("Enabling VFS Mode") - launcher.workspace.release.modify_bootstrap_setting(console_remote_filesystem, 1) - launcher.workspace.release.modify_user_setting(console_paks, "False") diff --git a/Tests/workflow/win/__init__.py b/Tests/workflow/win/__init__.py deleted file mode 100755 index e912252f4e..0000000000 --- a/Tests/workflow/win/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - - All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - its licensors. - - For complete copyright and license terms please see the LICENSE at the root of this - distribution (the "License"). All use of this software is governed by the License, - or, if provided, by the license below or the license accompanying this file. Do not - remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - diff --git a/Tests/workflow/win/workflow_win.py b/Tests/workflow/win/workflow_win.py deleted file mode 100755 index 4eb0f578b1..0000000000 --- a/Tests/workflow/win/workflow_win.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -""" -import logging -import pytest - -import workflow.shared.workflow_shared as workflow_shared \ No newline at end of file diff --git a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake index 4ef0f8af85..447083dca4 100644 --- a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake +++ b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake @@ -29,7 +29,7 @@ ly_associate_package(PACKAGE_NAME SQLite-3.32.2-rev3-multiplatform ly_associate_package(PACKAGE_NAME SPIRVCross-2020.04.20-rev1-multiplatform TARGETS SPIRVCross PACKAGE_HASH 7c8c0eaa0166c26745c62d2238525af7e27ac058a5db3defdbaec1878e8798dd) ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-2020.08.07-rev1-multiplatform TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 04a6850ce03d4c16e19ed206f7093d885276dfb74047e6aa99f0a834c8b7cc73) ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxcAz-5.0.0_az-rev1-multiplatform TARGETS DirectXShaderCompilerDxcAz PACKAGE_HASH 94f24989a7a371d840b513aa5ffaff02747b3d19b119bc1f899427e29978f753) -ly_associate_package(PACKAGE_NAME azslc-1.7.20-rev1-multiplatform TARGETS azslc PACKAGE_HASH 45d55f28bea2ef823ed3204f60df52e5e329f42923923d4555fdbdf3bea0af60) +ly_associate_package(PACKAGE_NAME azslc-1.7.21-rev1-multiplatform TARGETS azslc PACKAGE_HASH 772b7a2d9cc68aa1da4f0ee7db57ee1b4e7a8f20b81961fc5849af779582f4df) ly_associate_package(PACKAGE_NAME glad-2.0.0-beta-rev2-multiplatform TARGETS glad PACKAGE_HASH ff97ee9664e97d0854b52a3734c2289329d9f2b4cd69478df6d0ca1f1c9392ee) ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux_core PACKAGE_HASH c8c13cf7bc351643e1abd294d0841b24dee60e51647dff13db7aec396ad1e0b5) ly_associate_package(PACKAGE_NAME xxhash-0.7.4-rev1-multiplatform TARGETS xxhash PACKAGE_HASH e81f3e6c4065975833996dd1fcffe46c3cf0f9e3a4207ec5f4a1b564ba75861e) diff --git a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake index b36248b929..d6f9270c68 100644 --- a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake +++ b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake @@ -29,7 +29,7 @@ ly_associate_package(PACKAGE_NAME SQLite-3.32.2-rev3-multiplatform ly_associate_package(PACKAGE_NAME SPIRVCross-2020.04.20-rev1-multiplatform TARGETS SPIRVCross PACKAGE_HASH 7c8c0eaa0166c26745c62d2238525af7e27ac058a5db3defdbaec1878e8798dd) ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-2020.08.07-rev1-multiplatform TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 04a6850ce03d4c16e19ed206f7093d885276dfb74047e6aa99f0a834c8b7cc73) ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxcAz-5.0.0_az-rev1-multiplatform TARGETS DirectXShaderCompilerDxcAz PACKAGE_HASH 94f24989a7a371d840b513aa5ffaff02747b3d19b119bc1f899427e29978f753) -ly_associate_package(PACKAGE_NAME azslc-1.7.20-rev1-multiplatform TARGETS azslc PACKAGE_HASH 45d55f28bea2ef823ed3204f60df52e5e329f42923923d4555fdbdf3bea0af60) +ly_associate_package(PACKAGE_NAME azslc-1.7.21-rev1-multiplatform TARGETS azslc PACKAGE_HASH 772b7a2d9cc68aa1da4f0ee7db57ee1b4e7a8f20b81961fc5849af779582f4df) ly_associate_package(PACKAGE_NAME glad-2.0.0-beta-rev2-multiplatform TARGETS glad PACKAGE_HASH ff97ee9664e97d0854b52a3734c2289329d9f2b4cd69478df6d0ca1f1c9392ee) ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux_core PACKAGE_HASH c8c13cf7bc351643e1abd294d0841b24dee60e51647dff13db7aec396ad1e0b5) ly_associate_package(PACKAGE_NAME xxhash-0.7.4-rev1-multiplatform TARGETS xxhash PACKAGE_HASH e81f3e6c4065975833996dd1fcffe46c3cf0f9e3a4207ec5f4a1b564ba75861e) diff --git a/scripts/build/Jenkins/Jenkinsfile b/scripts/build/Jenkins/Jenkinsfile index ad01d6ed05..eb0778791d 100644 --- a/scripts/build/Jenkins/Jenkinsfile +++ b/scripts/build/Jenkins/Jenkinsfile @@ -196,7 +196,8 @@ def CheckoutBootstrapScripts(String branchName) { [ $class: 'SparseCheckoutPath', path: 'scripts/build/bootstrap/' ], [ $class: 'SparseCheckoutPath', path: 'scripts/build/Platform' ] ]], - [$class: 'CloneOption', depth: 1, noTags: false, reference: '', shallow: true] + // Shallow checkouts break changelog computation. Do not enable. + [$class: 'CloneOption', noTags: false, reference: '', shallow: false] ], submoduleCfg: [], userRemoteConfigs: scm.userRemoteConfigs