From ea030d2890abfb4c95e87ba816bf823d35aeb92d Mon Sep 17 00:00:00 2001 From: mrieggeramzn Date: Tue, 12 Oct 2021 17:48:12 -0700 Subject: [PATCH 01/27] Removing unused softening boundary width controls Signed-off-by: mrieggeramzn --- .../Shadow/DirectionalLightShadow.azsli | 76 ------- .../Atom/Features/Shadow/JitterTablePcf.azsli | 185 ------------------ .../Features/Shadow/ProjectedShadow.azsli | 35 ---- .../Atom/Features/Shadow/Shadow.azsli | 3 - .../CoreLights/ViewSrg.azsli | 3 - .../atom_feature_common_asset_files.cmake | 1 - ...irectionalLightFeatureProcessorInterface.h | 6 - .../DiskLightFeatureProcessorInterface.h | 2 - .../PointLightFeatureProcessorInterface.h | 3 - .../Atom/Feature/CoreLights/ShadowConstants.h | 1 - ...ProjectedShadowFeatureProcessorInterface.h | 2 - .../DirectionalLightFeatureProcessor.cpp | 70 +------ .../DirectionalLightFeatureProcessor.h | 7 +- .../CoreLights/DiskLightFeatureProcessor.cpp | 5 - .../CoreLights/DiskLightFeatureProcessor.h | 1 - .../Source/CoreLights/EsmShadowmapsPass.cpp | 22 --- .../Source/CoreLights/EsmShadowmapsPass.h | 10 - .../CoreLights/PointLightFeatureProcessor.cpp | 5 - .../CoreLights/PointLightFeatureProcessor.h | 1 - .../ProjectedShadowFeatureProcessor.cpp | 79 +------- .../Shadows/ProjectedShadowFeatureProcessor.h | 4 +- .../CommonFeatures/CoreLights/AreaLightBus.h | 7 - .../CoreLights/AreaLightComponentConfig.h | 1 - .../CoreLights/DirectionalLightBus.h | 9 - .../DirectionalLightComponentConfig.h | 4 - .../CoreLights/AreaLightComponentConfig.cpp | 1 - .../AreaLightComponentController.cpp | 18 -- .../CoreLights/AreaLightComponentController.h | 2 - .../DirectionalLightComponentConfig.cpp | 1 - .../DirectionalLightComponentController.cpp | 19 -- .../DirectionalLightComponentController.h | 2 - .../Source/CoreLights/DiskLightDelegate.cpp | 8 - .../Source/CoreLights/DiskLightDelegate.h | 1 - .../CoreLights/EditorAreaLightComponent.cpp | 9 - .../EditorDirectionalLightComponent.cpp | 9 - .../Source/CoreLights/LightDelegateBase.h | 1 - .../CoreLights/LightDelegateInterface.h | 2 - .../Source/CoreLights/SphereLightDelegate.cpp | 8 - .../Source/CoreLights/SphereLightDelegate.h | 1 - 39 files changed, 8 insertions(+), 616 deletions(-) delete mode 100644 Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/JitterTablePcf.azsli diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli index dd235fcd3a..633ea85387 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli @@ -10,7 +10,6 @@ #include #include -#include "JitterTablePcf.azsli" #include "Shadow.azsli" #include "ShadowmapAtlasLib.azsli" #include "BicubicPcfFilters.azsli" @@ -82,12 +81,6 @@ class DirectionalLightShadow // result.y == true if the given coordinate is in shadow. bool2 IsShadowed(float3 shadowCoord, uint indexOfCascade); - // This checks if the point is shadowed or not for the given center coordinate and jitter. - bool IsShadowedWithJitter( - float3 jitterUnit, - float jitterDepthDiffBase, - uint jitterIndex); - // This outputs visibility ratio (from 0.0 to 1.0) of the given coordinate // from the light origin without filtering. float GetVisibilityFromLightNoFilter(); @@ -189,75 +182,6 @@ bool2 DirectionalLightShadow::IsShadowed(float3 shadowCoord, uint indexOfCascade return bool2(false, false); } -bool DirectionalLightShadow::IsShadowedWithJitter( - float3 jitterUnit, - float jitterDepthDiffBase, - uint jitterIndex) -{ - const uint cascadeCount = ViewSrg::m_directionalLightShadows[m_lightIndex].m_cascadeCount; - const float4x4 worldToLightViewMatrices[ViewSrg::MaxCascadeCount] = - ViewSrg::m_directionalLightShadows[m_lightIndex].m_worldToLightViewMatrices; - const float4x4 lightViewToShadowmapMatrices[ViewSrg::MaxCascadeCount] = - ViewSrg::m_directionalLightShadows[m_lightIndex].m_lightViewToShadowmapMatrices; - const float boundaryScale = - ViewSrg::m_directionalLightShadows[m_lightIndex].m_boundaryScale; - - const float2 jitterXY = g_jitterTablePcf[jitterIndex]; - - // jitterLightView is the jittering diff vector from the lighted point on the surface - // in the light view space. It is remarked as "v_J" in the comment - // named "Calculate depth adjusting diff for jittered samples" - // just before the function GetJitterUnitVectorDepthDiffBase. - const float4 jitterLightView = float4(jitterXY, 0., 0.) * boundaryScale; - - // It checks the jittered point is lit or shadowed from the detailed cascade - // to the less detailed one. - for (uint indexOfCascade = 0; indexOfCascade < cascadeCount; ++indexOfCascade) - { - // jitterShadowmap is the jittering diff vector in the shadowmap space. - const float4 jitterShadowmap = mul(lightViewToShadowmapMatrices[indexOfCascade], jitterLightView); - - // Calculation of the jittering for Z-coordinate (light direction) is required - // to check lit/shadowed for the jittered point. - // jitterDepthDiff is the Z-coordinate of the jittering diff vector - // in the shadowmap space. - float jitterDepthDiff = 0.; - - // jitterDepthDiffBase is "1/tan(theta)" in the comment. - if (jitterDepthDiffBase != 0.) - { - // jitterUnitLightView is the unit vector in the light view space - // noted as "v_M" in the comment. - const float3 jitterUnitLightView = - normalize(mul(worldToLightViewMatrices[indexOfCascade], float4(jitterUnit, 0.)).xyz); - const float lightViewToShadowmapZScale = -lightViewToShadowmapMatrices[indexOfCascade]._m22; - // jitterDepthDiff is the "d" in the note, and it is calculated by - // d = (v_J . v_M) / tan(theta) - // in the light view space. Furthermore it have to be converted - // to the light clip space, which can be done by lightViewToShadowmapZScale. - jitterDepthDiff = - dot(jitterLightView.xyz, jitterUnitLightView) * jitterDepthDiffBase * - lightViewToShadowmapZScale; - } - // jitteredCoord is the coordinate of the jittered point in the shadowmap space. - const float3 jitteredCoord = - m_shadowCoords[indexOfCascade] + float3(jitterShadowmap.xy, jitterDepthDiff); - // Check for the jittered point is lit or shadowed. - const bool2 checkedShadowed = IsShadowed( - jitteredCoord, - indexOfCascade); - // If check is done, return the lit/shadowed flag. - // Otherwise make it pend to the next cascade. - if (checkedShadowed.x) - { - m_debugInfo.m_cascadeIndex = indexOfCascade; - return checkedShadowed.y; - } - } - m_debugInfo.m_cascadeIndex = cascadeCount; - return false; -} - float DirectionalLightShadow::GetVisibilityFromLightNoFilter() { const uint cascadeCount = ViewSrg::m_directionalLightShadows[m_lightIndex].m_cascadeCount; diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/JitterTablePcf.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/JitterTablePcf.azsli deleted file mode 100644 index 9082286758..0000000000 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/JitterTablePcf.azsli +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - - /* - The following is the output of - $ python3 pcf_jitter_table.py 6 g_jitterTablePcf 0 - where pcf_jitter_table.py has the following contents. - -@code -#!/usr/bin/env python3 - -import random -import sys -import math - - -""" Returns if a point in the range -[radius_min, radius_sup)*[angle_min, angle_sup) -is contained in the tuple polar coordinates. -""" -def is_point_include(radius_min, radius_sup, angle_min, angle_sup, polars): - for polar in polars: - if (radius_min <= polar[0] and polar[0] < radius_sup and - angle_min <= polar[1] and polar[1] < angle_sup): - return True - return False - - -""" Insert a randomly generated polar coordianted point in each -range [r0, r1)*[a0, a1) if there has not been such a point -in tuple coords yet, where [0, 1)*[0, 2pi) is divided -into the rad_count*agl_count ranges. -""" -def add_jitter_coords(radius_count, angle_count, polars): - radius_base = 1.0 / math.sqrt(radius_count) - for radius_index in range(radius_count): - # range of radius - radius_min = math.sqrt(radius_index) * radius_base - radius_sup = math.sqrt(radius_index + 1) * radius_base - - # randomize angle order - random_state = random.getstate() - angle_indices = list(range(angle_count)) - random.shuffle(angle_indices) - random.setstate(random_state) - - for angle_index in angle_indices: - # range of angle - angle_min = 2 * math.pi * angle_index / angle_count - angle_sup = 2 * math.pi * (angle_index + 1) / angle_count - - # if no point in the radius/angle range, add a new point - if not is_point_include(radius_min, radius_sup, - angle_min, angle_sup, - polars): - radius = radius_min + (radius_sup - radius_min) * random.random() - angle = angle_min + (angle_sup - angle_min) * random.random() - polars += [[radius, angle]] - - -""" Return a formatted string readable as an array of -orthogonal coordinated points which are in inside of the unit disk. -""" -def conv_array_string(polars): - result = "{\n" - for [radius, angle] in polars: - x = radius * math.cos(angle) - y = radius * math.sin(angle) - result += str.format(" float2({: 1.20e}, {: 1.20e}),\n", x, y) - result = result.rstrip(",\n") + "\n};\n" - return result - - -if __name__ == "__main__": - rad_size = 1 - ang_size = 1 - - if len(sys.argv) > 3: - random_seed = int(sys.argv[3]) - else: - random_seed = 0 - - if len(sys.argv) > 2: - array_name = sys.argv[2] - else: - array_name = False - - if len(sys.argv) > 1: - len_log = int(sys.argv[1]) - else: - print(" usage: {} array_len_log2 [array_file_name] [random_seed]".format(__file__)) - print(" array_len_log2 = 2 -> array length = 4") - print(" array_len_log2 = 6 -> array length = 64") - sys.exit() - - random.seed(random_seed) - coords = [] - add_jitter_coords(rad_size, ang_size, coords) - for index in range(len_log): - if index % 2 == 0: - rad_size *= 2 - else: - ang_size *= 2 - add_jitter_coords(rad_size, ang_size, coords) - - if array_name: - print(str.format("static const float2 {}[{}] =", array_name, len(coords))) - print(conv_array_string(coords)) - - @endcode - */ -#pragma once - -static const float2 g_jitterTablePcf[64] = -{ - float2( 4.21857815578105532772e-02, -8.43367430701083664601e-01), - float2(-1.66526814909220763350e-02, 2.96922406531470617352e-01), - float2(-1.06374665780382349212e-01, -3.45521852905696924552e-01), - float2( 5.42648241814168375008e-01, 7.63475573328278533936e-01), - float2(-1.55045122122251910479e-01, 5.78282315712970729216e-01), - float2( 1.01310018770242576264e-02, -6.88001749851880561870e-01), - float2(-5.41276603451248283783e-01, 5.21888233660957712168e-01), - float2(-6.69885071867917680777e-01, -6.72019666097878665134e-01), - float2( 1.22985029409499718039e-02, 4.54706838949524849713e-01), - float2( 4.00334354168925599105e-01, -6.20112671104014120949e-02), - float2( 2.32326155804074424571e-01, 5.14183027524470093184e-01), - float2(-3.26788693165450228051e-01, -6.03339478694129849323e-01), - float2( 7.72374386126136736053e-01, 1.23204314299169448432e-01), - float2(-4.45379212004159807936e-01, -6.35591042627205338178e-01), - float2( 9.86986293787213919693e-01, -5.18195017297516449806e-02), - float2(-9.09197225477999193544e-01, 1.95281945570711268356e-01), - float2( 8.78123785413316704229e-02, -2.77671865082058690055e-02), - float2( 1.93947312440399088906e-01, 4.27852204081567363825e-03), - float2(-2.06133675819526185347e-01, -1.49183652412411493771e-01), - float2(-4.11351098583102647854e-01, 2.36214692717993696158e-01), - float2( 3.50058750095615767162e-01, -3.57193658067260721989e-01), - float2(-5.54174780014121681759e-01, -2.23361040823672196698e-01), - float2(-6.29913348094886860196e-01, 1.29962593232600148729e-01), - float2( 3.96119563669521335125e-01, 4.90495219155295036906e-01), - float2( 7.26077464944819728210e-01, -3.70531027878536270426e-02), - float2(-5.50726266551596621568e-01, 6.48997654184258587762e-01), - float2(-6.98067624269093189859e-01, -3.83843898992943299842e-01), - float2( 8.72900706885875177221e-02, 8.24287559846993866941e-01), - float2( 6.65413234189638491678e-01, -5.66029707430476647367e-01), - float2(-5.97071574457786802270e-01, -6.93417220711863180327e-01), - float2( 6.09778569514949131403e-01, 6.92279483269558570946e-01), - float2(-8.10051800827623957879e-01, 5.82366304247235455627e-01), - float2(-8.77200948157437071506e-02, -1.88326609190753474499e-01), - float2( 9.79306884403889771340e-02, 1.86693151785678163046e-01), - float2( 4.60071424048798319206e-02, -1.98255149016034859510e-01), - float2(-5.37585860722621794450e-02, 3.99205315590760584366e-02), - float2( 2.18621803321778829243e-01, -3.85632280444686503795e-01), - float2(-2.98409571230789372187e-02, 4.22286693608096730390e-01), - float2( 3.58654757584850270025e-01, 2.95175871390239985548e-01), - float2(-3.85631921979480485341e-01, -3.00322047091407640096e-01), - float2( 4.49800763439369810648e-01, 3.98492182500493397068e-01), - float2(-4.97878650048238891035e-01, 2.57984038389083569776e-01), - float2(-3.12055242602567339816e-01, -4.88013525550807125697e-01), - float2( 5.87078632117718268724e-01, -6.97256834327608099322e-02), - float2( 6.23692403999373534695e-01, 3.11519734097943645779e-01), - float2( 6.64426445690903810792e-01, -2.27661844509491811950e-01), - float2(-3.24662942872471160793e-01, 5.68939932480760024447e-01), - float2(-5.31263995010459511015e-01, -4.66108719959298256619e-01), - float2( 5.10323549430644951563e-01, 5.81027848262460677731e-01), - float2( 2.82695533021593392586e-01, -7.03582425015577883620e-01), - float2(-5.98419541732174709026e-01, -4.68015982003612274198e-01), - float2(-3.95281650646674975746e-01, 6.10614720709622194050e-01), - float2( 7.87454411900813555647e-01, 1.37726315874787758053e-01), - float2(-7.36310249594224086600e-01, 4.25723821775386646049e-01), - float2( 6.48232481978769037312e-01, -5.53108138515975955585e-01), - float2(-1.88558544306507869237e-01, -7.79120748356531223067e-01), - float2(-3.78614630625567993860e-01, 7.82366459873827913007e-01), - float2(-8.48582606942172357201e-01, -3.78504015913022351381e-01), - float2( 1.91472859899175090748e-02, -9.13050020447597532325e-01), - float2( 8.08826910050883585157e-01, 4.17202663034078935489e-01), - float2(-9.27062588380768493046e-01, -2.94160352051227980130e-01), - float2( 6.67882607007592055126e-01, -6.88642020601400450808e-01), - float2(-1.59349274307943010454e-02, 9.37629353656756814317e-01), - float2( 9.86975590293644233775e-01, 1.44401793964158337014e-01) -}; diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/ProjectedShadow.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/ProjectedShadow.azsli index b91d0ce915..daed3a2921 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/ProjectedShadow.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/ProjectedShadow.azsli @@ -13,7 +13,6 @@ #include #include #include "BicubicPcfFilters.azsli" -#include "JitterTablePcf.azsli" #include "Shadow.azsli" // ProjectedShadow calculates shadowed area projected from a light. @@ -44,11 +43,6 @@ class ProjectedShadow float GetThickness(); bool IsShadowed(float3 shadowPosition); - bool IsShadowedWithJitter( - float3 jitterUnitX, - float3 jitterUnitY, - float jitterDepthDiffBase, - uint jitterIndex); void SetShadowPosition(); float3 GetAtlasPosition(float2 texturePosition); static float UnprojectDepth(uint shadowIndex, float depthBufferValue); @@ -321,35 +315,6 @@ bool ProjectedShadow::IsShadowed(float3 shadowPosition) return false; } -bool ProjectedShadow::IsShadowedWithJitter( - float3 jitterUnitX, - float3 jitterUnitY, - float jitterDepthDiffBase, - uint jitterIndex) -{ - ViewSrg::ProjectedShadow shadow = ViewSrg::m_projectedShadows[m_shadowIndex]; - const float4x4 depthBiasMatrix = shadow.m_depthBiasMatrix; - const float boundaryScale = shadow.m_boundaryScale; - - const float2 jitterXY = g_jitterTablePcf[jitterIndex]; - - const float dist = distance(m_worldPosition, m_viewPosition); - const float boundaryRadius = dist * tan(boundaryScale); - // jitterWorldXY is the jittering diff vector from the lighted point on the surface - // in the world space. It is remarked as "v_J" in the comment - // named "Calculate depth adjusting diff for jittered samples" - // just before the function GetJitterUnitVectorDepthDiffBase. - const float3 jitterWorldXY = jitterUnitX * (jitterXY.x * boundaryRadius) + jitterUnitY * (jitterXY.y * boundaryRadius); - // The adjusting diff of depth ("d" in the comment) is calculated by - // jitterXY.y * boundaryRadius * jitterDepthDiffBase. - const float3 jitterWorldZ = m_lightDirection * (jitterXY.y * boundaryRadius * jitterDepthDiffBase); - - const float3 jitteredWorldPosition = m_worldPosition + jitterWorldXY + jitterWorldZ; - const float4 jitteredShadowmapHomogeneous = mul(depthBiasMatrix, float4(jitteredWorldPosition, 1)); - - return IsShadowed(jitteredShadowmapHomogeneous.xyz / jitteredShadowmapHomogeneous.w); -} - void ProjectedShadow::SetShadowPosition() { const float4x4 depthBiasMatrix = ViewSrg::m_projectedShadows[m_shadowIndex].m_depthBiasMatrix; diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/Shadow.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/Shadow.azsli index e05ff076cb..1fe81016cf 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/Shadow.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/Shadow.azsli @@ -23,14 +23,11 @@ struct FilterParameter uint m_isEnabled; uint2 m_shadowmapOriginInSlice; uint m_shadowmapSize; - uint m_parameterOffset; - uint m_parameterCount; float m_lightDistanceOfCameraViewFrustum; float m_n_f_n; // n / (f - n) float m_n_f; // n - f float m_f; // f // where n: nearDepth, f: farDepth. - float2 m_padding; // explicit padding }; class Shadow diff --git a/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli index 2065e28703..94d6f20da3 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli @@ -22,14 +22,11 @@ partial ShaderResourceGroup ViewSrg uint m_isEnabled; uint2 m_shadowmapOriginInSlice; uint m_shadowmapSize; - uint m_parameterOffset; - uint m_parameterCount; float m_lightDistanceOfCameraViewFrustum; float m_n_f_n; // n / (f - n) float m_n_f; // n - f float m_f; // f // where n: nearDepth, f: farDepth. - float2 m_padding; // explicit padding }; // Simple Point Lights diff --git a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake index 60614993b5..cf456b2e41 100644 --- a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake +++ b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake @@ -286,7 +286,6 @@ set(FILES ShaderLib/Atom/Features/ScreenSpace/ScreenSpaceUtil.azsli ShaderLib/Atom/Features/Shadow/BicubicPcfFilters.azsli ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli - ShaderLib/Atom/Features/Shadow/JitterTablePcf.azsli ShaderLib/Atom/Features/Shadow/ProjectedShadow.azsli ShaderLib/Atom/Features/Shadow/Shadow.azsli ShaderLib/Atom/Features/Shadow/ShadowmapAtlasLib.azsli diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h index 75d266cc52..769c7b95a6 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h @@ -154,12 +154,6 @@ namespace AZ //! @param count Sample Count for filtering (up to 64) virtual void SetFilteringSampleCount(LightHandle handle, uint16_t count) = 0; - //! This specifies the width of boundary between shadowed area and lit area. - //! @param handle the light handle. - //! @param width Boundary width. The shadow is gradually changed the degree of shadowed. - //! If width == 0, softening edge is disabled. Units are in meters. - virtual void SetShadowBoundaryWidth(LightHandle handle, float boundaryWidth) = 0; - //! Sets whether the directional shadowmap should use receiver plane bias. //! This attempts to reduce shadow acne when using large pcf filters. virtual void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) = 0; diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h index 3ab83200ae..5aa2dfb800 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h @@ -90,8 +90,6 @@ namespace AZ virtual void SetShadowmapMaxResolution(LightHandle handle, ShadowmapSize shadowmapSize) = 0; //! Specifies filter method of shadows. virtual void SetShadowFilterMethod(LightHandle handle, ShadowFilterMethod method) = 0; - //! Specifies the width of boundary between shadowed area and lit area in radians. The degree ofshadowed gradually changes on the boundary. 0 disables softening. - virtual void SetSofteningBoundaryWidthAngle(LightHandle handle, float boundaryWidthRadians) = 0; //! Sets sample count for filtering of shadow boundary (up to 64) virtual void SetFilteringSampleCount(LightHandle handle, uint16_t count) = 0; //! Sets the Esm exponent to use. Higher values produce a steeper falloff in the border areas between light and shadow. diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/PointLightFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/PointLightFeatureProcessorInterface.h index 6752ac4c52..1a5a776cdf 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/PointLightFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/PointLightFeatureProcessorInterface.h @@ -70,9 +70,6 @@ namespace AZ virtual void SetShadowBias(LightHandle handle, float bias) = 0; //! Specifies filter method of shadows. virtual void SetShadowFilterMethod(LightHandle handle, ShadowFilterMethod method) = 0; - //! Specifies the width of boundary between shadowed area and lit area in radians. The degree ofshadowed gradually changes on - //! the boundary. 0 disables softening. - virtual void SetSofteningBoundaryWidthAngle(LightHandle handle, float boundaryWidthRadians) = 0; //! Sets sample count for filtering of shadow boundary (up to 64) virtual void SetFilteringSampleCount(LightHandle handle, uint16_t count) = 0; //! Sets the Esm exponent to use. Higher values produce a steeper falloff in the border areas between light and shadow. diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/ShadowConstants.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/ShadowConstants.h index dbad3af21f..309331dcf5 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/ShadowConstants.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/ShadowConstants.h @@ -42,7 +42,6 @@ namespace AZ // [GFX TODO][ATOM-2408] Make the max number of cascade modifiable at runtime. static constexpr uint16_t MaxNumberOfCascades = 4; static constexpr uint16_t MaxPcfSamplingCount = 64; - static constexpr float MaxSofteningBoundaryWidth = 0.1f; } // namespace Shadow } // namespace Render diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Shadows/ProjectedShadowFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Shadows/ProjectedShadowFeatureProcessorInterface.h index 3d6c0c3015..46560f435d 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Shadows/ProjectedShadowFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Shadows/ProjectedShadowFeatureProcessorInterface.h @@ -54,8 +54,6 @@ namespace AZ::Render virtual void SetShadowBias(ShadowId id, float bias) = 0; //! Sets the shadow filter method virtual void SetShadowFilterMethod(ShadowId id, ShadowFilterMethod method) = 0; - //! Sets the width of boundary between shadowed area and lit area. - virtual void SetSofteningBoundaryWidthAngle(ShadowId id, float boundaryWidthRadians) = 0; //! Sets the sample count for filtering of the shadow boundary, max 64. virtual void SetFilteringSampleCount(ShadowId id, uint16_t count) = 0; //! Sets all of the shadow properites in one call diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp index 42cca0e57c..0a9f3480ad 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp @@ -584,15 +584,6 @@ namespace AZ m_shadowBufferNeedsUpdate = true; } - void DirectionalLightFeatureProcessor::SetShadowBoundaryWidth(LightHandle handle, float boundaryWidth) - { - for (auto& it : m_shadowData) - { - it.second.GetData(handle.GetIndex()).m_boundaryScale = boundaryWidth / 2.f; - } - m_shadowBufferNeedsUpdate = true; - } - void DirectionalLightFeatureProcessor::SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) { m_shadowProperties.GetData(handle.GetIndex()).m_isReceiverPlaneBiasEnabled = enable; @@ -1116,50 +1107,13 @@ namespace AZ for (const auto& passIt : m_esmShadowmapsPasses) { const RPI::View* cameraView = passIt.second.front()->GetRenderPipeline()->GetDefaultView().get(); - UpdateStandardDeviations(handle, cameraView); - UpdateFilterOffsetsCounts(handle, cameraView); + UpdateFilterEnabled(handle, cameraView); UpdateShadowmapPositionInAtlas(handle, cameraView); SetFilterParameterToPass(handle, cameraView); } } - void DirectionalLightFeatureProcessor::UpdateStandardDeviations(LightHandle handle, const RPI::View* cameraView) - { - if (handle != m_shadowingLightHandle) - { - return; - } - - const DirectionalLightShadowData& data = m_shadowData.at(cameraView).GetData(handle.GetIndex()); - const ShadowProperty& property = m_shadowProperties.GetData(handle.GetIndex()); - AZStd::fixed_vector standardDeviations; - for (size_t cascadeIndex = 0; cascadeIndex < property.m_segments.at(cameraView).size(); ++cascadeIndex) - { - const Aabb& aabb = property.m_segments.at(cameraView)[cascadeIndex].m_aabb; - const float aabbDiameter = AZStd::GetMax( - aabb.GetMax().GetX() - aabb.GetMin().GetX(), - aabb.GetMax().GetZ() - aabb.GetMin().GetZ()); - float standardDeviation = 0.f; - if (aabbDiameter > 0.f) - { - const float boundaryWidth = data.m_boundaryScale * 2.f; - const float ratioToAabbWidth = boundaryWidth / aabbDiameter; - const float widthInPixels = ratioToAabbWidth * data.m_shadowmapSize; - standardDeviation = widthInPixels / (2 * GaussianMathFilter::ReliableSectionFactor); - } - standardDeviations.push_back(standardDeviation); - } - - for (const RPI::RenderPipelineId& pipelineId : m_renderPipelineIdsForPersistentView.at(cameraView)) - { - for (EsmShadowmapsPass* esmPass : m_esmShadowmapsPasses.at(pipelineId)) - { - esmPass->SetFilterParameters(standardDeviations); - } - } - } - - void DirectionalLightFeatureProcessor::UpdateFilterOffsetsCounts(LightHandle handle, const RPI::View* cameraView) + void DirectionalLightFeatureProcessor::UpdateFilterEnabled(LightHandle handle, const RPI::View* cameraView) { if (handle != m_shadowingLightHandle) { @@ -1170,29 +1124,11 @@ namespace AZ if (shadowData.m_shadowFilterMethod == aznumeric_cast(ShadowFilterMethod::Esm) || (shadowData.m_shadowFilterMethod == aznumeric_cast(ShadowFilterMethod::EsmPcf))) { - // Get array of filter counts for the camera view. - const RPI::RenderPipelineId& pipelineId = m_renderPipelineIdsForPersistentView.at(cameraView).front(); - AZ_Assert(!m_esmShadowmapsPasses.at(pipelineId).empty(), "Cannot find a EsmShadowmapsPass."); - const AZStd::array_view filterCounts = m_esmShadowmapsPasses.at(pipelineId).front()->GetFilterCounts(); - AZ_Assert(filterCounts.size() == GetCascadeCount(handle), "FilterCounts differs with cascade count."); - - // Create array of filter offsets - AZStd::vector filterOffsets; - filterOffsets.reserve(filterCounts.size()); - uint32_t filterOffset = 0; - for (const uint32_t count : filterCounts) - { - filterOffsets.push_back(filterOffset); - filterOffset += count; - } - // Write filter offsets and filter counts to ESM data for (uint16_t index = 0; index < GetCascadeCount(handle); ++index) { EsmShadowmapsPass::FilterParameter& filterParameter = m_esmParameterData.at(cameraView).GetData(index); filterParameter.m_isEnabled = true; - filterParameter.m_parameterOffset = filterOffsets[index]; - filterParameter.m_parameterCount = filterCounts[index]; } } else @@ -1202,8 +1138,6 @@ namespace AZ { EsmShadowmapsPass::FilterParameter& filterParameter = m_esmParameterData.at(cameraView).GetData(index); filterParameter.m_isEnabled = false; - filterParameter.m_parameterOffset = 0; - filterParameter.m_parameterCount = 0; } } } diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h index 039f51d549..3c1ff8eabd 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h @@ -217,7 +217,6 @@ namespace AZ void SetDebugFlags(LightHandle handle, DebugDrawFlags flags) override; void SetShadowFilterMethod(LightHandle handle, ShadowFilterMethod method) override; void SetFilteringSampleCount(LightHandle handle, uint16_t count) override; - void SetShadowBoundaryWidth(LightHandle handle, float boundaryWidth) override; void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) override; const Data::Instance GetLightBuffer() const; @@ -278,10 +277,8 @@ namespace AZ //! This updates the parameter of Gaussian filter used in ESM. void UpdateFilterParameters(LightHandle handle); - //! This updates standard deviations for each cascade. - void UpdateStandardDeviations(LightHandle handle, const RPI::View* cameraView); - //! This updates filter offset and size for each cascade. - void UpdateFilterOffsetsCounts(LightHandle handle, const RPI::View* cameraView); + //! This updates if the filter is enabled. + void UpdateFilterEnabled(LightHandle handle, const RPI::View* cameraView); //! This updates shadowmap position(origin and size) in the atlas for each cascade. void UpdateShadowmapPositionInAtlas(LightHandle handle, const RPI::View* cameraView); //! This set filter parameters to passes which execute filtering. diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.cpp index 26e1757a5a..acf81ede32 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.cpp @@ -322,11 +322,6 @@ namespace AZ { SetShadowSetting(handle, &ProjectedShadowFeatureProcessor::SetShadowFilterMethod, method); } - - void DiskLightFeatureProcessor::SetSofteningBoundaryWidthAngle(LightHandle handle, float boundaryWidthRadians) - { - SetShadowSetting(handle, &ProjectedShadowFeatureProcessor::SetSofteningBoundaryWidthAngle, boundaryWidthRadians); - } void DiskLightFeatureProcessor::SetFilteringSampleCount(LightHandle handle, uint16_t count) { diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.h index d65f587718..275712f84f 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.h @@ -53,7 +53,6 @@ namespace AZ void SetShadowBias(LightHandle handle, float bias) override; void SetShadowmapMaxResolution(LightHandle handle, ShadowmapSize shadowmapSize) override; void SetShadowFilterMethod(LightHandle handle, ShadowFilterMethod method) override; - void SetSofteningBoundaryWidthAngle(LightHandle handle, float boundaryWidthRadians) override; void SetFilteringSampleCount(LightHandle handle, uint16_t count) override; void SetEsmExponent(LightHandle handle, float esmExponent) override; diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/EsmShadowmapsPass.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/EsmShadowmapsPass.cpp index 99eaa8a919..b92d538fb5 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/EsmShadowmapsPass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/EsmShadowmapsPass.cpp @@ -42,28 +42,6 @@ namespace AZ return m_lightTypeName; } - void EsmShadowmapsPass::SetFilterParameters(const AZStd::array_view& standardDeviations) - { - // Set descriptor for Gaussian filters for given set of standard deviations. - MathFilterDescriptor descriptor; - descriptor.m_kind = MathFilterKind::Gaussian; - descriptor.m_gaussians.reserve(standardDeviations.size()); - for (const float standardDeviation : standardDeviations) - { - descriptor.m_gaussians.emplace_back(GaussianFilterDescriptor{ standardDeviation }); - } - - // Set filter paramter buffer along with element counts for each filter. - MathFilter::BufferWithElementCounts bufferCounts = MathFilter::FindOrCreateFilterBuffer(descriptor); - m_filterTableBuffer = bufferCounts.first; - m_filterCounts = AZStd::move(bufferCounts.second); - } - - AZStd::array_view EsmShadowmapsPass::GetFilterCounts() const - { - return m_filterCounts; - } - void EsmShadowmapsPass::SetShadowmapIndexTableBuffer(const Data::Instance& tableBuffer) { m_shadowmapIndexTableBuffer = tableBuffer; diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/EsmShadowmapsPass.h b/Gems/Atom/Feature/Common/Code/Source/CoreLights/EsmShadowmapsPass.h index 6e5e1aa311..5a9898d507 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/EsmShadowmapsPass.h +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/EsmShadowmapsPass.h @@ -50,14 +50,11 @@ namespace AZ uint32_t m_isEnabled = false; AZStd::array m_shadowmapOriginInSlice = { {0, 0 } }; // shadowmap origin in the slice of the atlas. uint32_t m_shadowmapSize = static_cast(ShadowmapSize::None); // width and height of shadowmap. - uint32_t m_parameterOffset; // offset of the filter parameter. - uint32_t m_parameterCount; // element count of the filter parameter. float m_lightDistanceOfCameraViewFrustum = 0.f; float m_n_f_n = 0.f; // n / (f - n) float m_n_f = 0.f; // n - f float m_f = 0.f; // f // where n: nearDepth, f: farDepth. - AZStd::array m_padding = {{0.f, 0.f}}; // explicit padding }; virtual ~EsmShadowmapsPass() = default; @@ -65,13 +62,6 @@ namespace AZ const Name& GetLightTypeName() const; - //! This sets the standard deviations of the Gaussian filter - //! for each cascade. - void SetFilterParameters(const AZStd::array_view& standardDeviations); - - //! This returns element count of filters. - AZStd::array_view GetFilterCounts() const; - //! This sets the buffer of the table which enable to get shadowmap index //! from the coordinate in the atlas. //! Note that shadowmpa index is shader light index for a spot light diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/PointLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/PointLightFeatureProcessor.cpp index d3b5646e0b..dcf412c35d 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/PointLightFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/PointLightFeatureProcessor.cpp @@ -292,11 +292,6 @@ namespace AZ SetShadowSetting(handle, &ProjectedShadowFeatureProcessor::SetShadowFilterMethod, method); } - void PointLightFeatureProcessor::SetSofteningBoundaryWidthAngle(LightHandle handle, float boundaryWidthRadians) - { - SetShadowSetting(handle, &ProjectedShadowFeatureProcessor::SetSofteningBoundaryWidthAngle, boundaryWidthRadians); - } - void PointLightFeatureProcessor::SetFilteringSampleCount(LightHandle handle, uint16_t count) { SetShadowSetting(handle, &ProjectedShadowFeatureProcessor::SetFilteringSampleCount, count); diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/PointLightFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/CoreLights/PointLightFeatureProcessor.h index b784eb1bb5..54cb0303cc 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/PointLightFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/PointLightFeatureProcessor.h @@ -50,7 +50,6 @@ namespace AZ void SetShadowBias(LightHandle handle, float bias) override; void SetShadowmapMaxResolution(LightHandle handle, ShadowmapSize shadowmapSize) override; void SetShadowFilterMethod(LightHandle handle, ShadowFilterMethod method) override; - void SetSofteningBoundaryWidthAngle(LightHandle handle, float boundaryWidthRadians) override; void SetFilteringSampleCount(LightHandle handle, uint16_t count) override; void SetEsmExponent(LightHandle handle, float esmExponent) override; void SetPointData(LightHandle handle, const PointLightData& data) override; diff --git a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp index c0202c7c74..c5c81486ce 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp @@ -186,17 +186,6 @@ namespace AZ::Render m_filterParameterNeedsUpdate = true; } - void ProjectedShadowFeatureProcessor::SetSofteningBoundaryWidthAngle(ShadowId id, float boundaryWidthRadians) - { - AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetShadowBoundaryWidthAngle()."); - - ShadowData& shadowData = m_shadowData.GetElement(id.GetIndex()); - shadowData.m_boundaryScale = boundaryWidthRadians / 2.0f; - - m_shadowmapPassNeedsUpdate = true; - m_filterParameterNeedsUpdate = true; - } - void ProjectedShadowFeatureProcessor::SetFilteringSampleCount(ShadowId id, uint16_t count) { AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetFilteringSampleCount()."); @@ -368,14 +357,13 @@ namespace AZ::Render { if (m_filterParameterNeedsUpdate) { - UpdateStandardDeviations(); - UpdateFilterOffsetsCounts(); + UpdateEsmPassEnabled(); SetFilterParameterToPass(); m_filterParameterNeedsUpdate = false; } } - void ProjectedShadowFeatureProcessor::UpdateStandardDeviations() + void ProjectedShadowFeatureProcessor::UpdateEsmPassEnabled() { if (m_esmShadowmapsPasses.empty()) { @@ -383,24 +371,7 @@ namespace AZ::Render return; } - AZStd::vector standardDeviations(m_shadowProperties.GetDataCount()); - - for (uint32_t i = 0; i < m_shadowProperties.GetDataCount(); ++i) - { - ShadowProperty& shadowProperty = m_shadowProperties.GetDataVector().at(i); - const ShadowData& shadow = m_shadowData.GetElement(shadowProperty.m_shadowId.GetIndex()); - if (!FilterMethodIsEsm(shadow)) - { - continue; - } - const FilterParameter& filter = m_shadowData.GetElement(shadowProperty.m_shadowId.GetIndex()); - const float boundaryWidthAngle = shadow.m_boundaryScale * 2.0f; - const float fieldOfView = GetMax(shadowProperty.m_desc.m_fieldOfViewYRadians, MinimumFieldOfView); - const float ratioToEntireWidth = boundaryWidthAngle / fieldOfView; - const float widthInPixels = ratioToEntireWidth * filter.m_shadowmapSize; - standardDeviations.at(i) = widthInPixels / (2.0f * GaussianMathFilter::ReliableSectionFactor); - } - if (standardDeviations.empty()) + if (m_shadowProperties.GetDataCount() == 0) { for (EsmShadowmapsPass* esmPass : m_esmShadowmapsPasses) { @@ -411,50 +382,6 @@ namespace AZ::Render for (EsmShadowmapsPass* esmPass : m_esmShadowmapsPasses) { esmPass->SetEnabledComputation(true); - esmPass->SetFilterParameters(standardDeviations); - } - } - - void ProjectedShadowFeatureProcessor::UpdateFilterOffsetsCounts() - { - if (m_esmShadowmapsPasses.empty()) - { - AZ_Error("ProjectedShadowFeatureProcessor", false, "Cannot find a required pass."); - return; - } - - // Get array of filter counts for the camera view. - const AZStd::array_view filterCounts = m_esmShadowmapsPasses.front()->GetFilterCounts(); - - // Create array of filter offsets. - AZStd::vector filterOffsets; - filterOffsets.reserve(filterCounts.size()); - uint32_t filterOffset = 0; - for (const uint32_t count : filterCounts) - { - filterOffsets.push_back(filterOffset); - filterOffset += count; - } - - auto& shadowProperties = m_shadowProperties.GetDataVector(); - for (uint32_t i = 0; i < shadowProperties.size(); ++i) - { - ShadowProperty& shadowProperty = shadowProperties.at(i); - const ShadowId shadowId = shadowProperty.m_shadowId; - ShadowData& shadowData = m_shadowData.GetElement(shadowId.GetIndex()); - FilterParameter& filterData = m_shadowData.GetElement(shadowId.GetIndex()); - - if (FilterMethodIsEsm(shadowData)) - { - filterData.m_parameterOffset = filterOffsets[i]; - filterData.m_parameterCount = filterCounts[i]; - } - else - { - // If filter is not required, reset offsets and counts of filter in ESM data. - filterData.m_parameterOffset = 0; - filterData.m_parameterCount = 0; - } } } diff --git a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.h index 0b266b9a40..6269166827 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.h @@ -49,7 +49,6 @@ namespace AZ::Render void SetShadowmapMaxResolution(ShadowId id, ShadowmapSize size) override; void SetShadowBias(ShadowId id, float bias) override; void SetShadowFilterMethod(ShadowId id, ShadowFilterMethod method) override; - void SetSofteningBoundaryWidthAngle(ShadowId id, float boundaryWidthRadians) override; void SetFilteringSampleCount(ShadowId id, uint16_t count) override; void SetShadowProperties(ShadowId id, const ProjectedShadowDescriptor& descriptor) override; const ProjectedShadowDescriptor& GetShadowProperties(ShadowId id) override; @@ -101,8 +100,7 @@ namespace AZ::Render //! Functions to update the parameter of Gaussian filter used in ESM. void UpdateFilterParameters(); - void UpdateStandardDeviations(); - void UpdateFilterOffsetsCounts(); + void UpdateEsmPassEnabled(); void SetFilterParameterToPass(); bool FilterMethodIsEsm(const ShadowData& shadowData) const; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/AreaLightBus.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/AreaLightBus.h index 557e6b3dd2..72c4ef97a8 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/AreaLightBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/AreaLightBus.h @@ -120,13 +120,6 @@ namespace AZ //! Sets the filter method of shadows. virtual void SetShadowFilterMethod(ShadowFilterMethod method) = 0; - //! Gets the width of softening boundary between shadowed area and lit area in degrees. - virtual float GetSofteningBoundaryWidthAngle() const = 0; - - //! Sets the width of softening boundary between shadowed area and lit area in degrees. - //! 0 disables softening. - virtual void SetSofteningBoundaryWidthAngle(float degrees) = 0; - //! Gets the sample count for filtering of the shadow boundary. virtual uint32_t GetFilteringSampleCount() const = 0; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/AreaLightComponentConfig.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/AreaLightComponentConfig.h index a6d3c6fbed..c76c922385 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/AreaLightComponentConfig.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/AreaLightComponentConfig.h @@ -59,7 +59,6 @@ namespace AZ float m_bias = 0.1f; ShadowmapSize m_shadowmapMaxSize = ShadowmapSize::Size256; ShadowFilterMethod m_shadowFilterMethod = ShadowFilterMethod::None; - float m_boundaryWidthInDegrees = 0.25f; uint16_t m_filteringSampleCount = 12; float m_esmExponent = 87.0f; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h index 644856e768..a8088c63ac 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h @@ -153,15 +153,6 @@ namespace AZ //! @param method filter method. virtual void SetShadowFilterMethod(ShadowFilterMethod method) = 0; - //! This gets the width of boundary between shadowed area and lit area. - //! @return Boundary width. The shadow is gradually changed the degree of shadowed. - virtual float GetSofteningBoundaryWidth() const = 0; - - //! This specifies the width of boundary between shadowed area and lit area. - //! @param width Boundary width. The shadow is gradually changed the degree of shadowed. - //! If width == 0, softening edge is disabled. Units are in meters. - virtual void SetSofteningBoundaryWidth(float width) = 0; - //! This gets the sample count for filtering of the shadow boundary. //! @return Sample Count for filtering (up to 64) virtual uint32_t GetFilteringSampleCount() const = 0; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h index 0123d06275..a58acc0114 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h @@ -101,10 +101,6 @@ namespace AZ //! Method of shadow's filtering. ShadowFilterMethod m_shadowFilterMethod = ShadowFilterMethod::None; - //! Width of the boundary between shadowed area and lit one. - //! If this is 0, edge softening is disabled. Units are in meters. - float m_boundaryWidth = 0.03f; // 3cm - //! Sample Count for filtering (from 4 to 64) //! It is used only when the pixel is predicted as on the boundary. uint16_t m_filteringSampleCount = 32; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/AreaLightComponentConfig.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/AreaLightComponentConfig.cpp index c7af44a28e..f0418a5024 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/AreaLightComponentConfig.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/AreaLightComponentConfig.cpp @@ -36,7 +36,6 @@ namespace AZ ->Field("Shadow Bias", &AreaLightComponentConfig::m_bias) ->Field("Shadowmap Max Size", &AreaLightComponentConfig::m_shadowmapMaxSize) ->Field("Shadow Filter Method", &AreaLightComponentConfig::m_shadowFilterMethod) - ->Field("Softening Boundary Width", &AreaLightComponentConfig::m_boundaryWidthInDegrees) ->Field("Filtering Sample Count", &AreaLightComponentConfig::m_filteringSampleCount) ->Field("Esm Exponent", &AreaLightComponentConfig::m_esmExponent) ; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/AreaLightComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/AreaLightComponentController.cpp index c0204ecac5..36cb2a7f5a 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/AreaLightComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/AreaLightComponentController.cpp @@ -74,8 +74,6 @@ namespace AZ::Render ->Event("SetShadowmapMaxSize", &AreaLightRequestBus::Events::SetShadowmapMaxSize) ->Event("GetShadowFilterMethod", &AreaLightRequestBus::Events::GetShadowFilterMethod) ->Event("SetShadowFilterMethod", &AreaLightRequestBus::Events::SetShadowFilterMethod) - ->Event("GetSofteningBoundaryWidthAngle", &AreaLightRequestBus::Events::GetSofteningBoundaryWidthAngle) - ->Event("SetSofteningBoundaryWidthAngle", &AreaLightRequestBus::Events::SetSofteningBoundaryWidthAngle) ->Event("GetFilteringSampleCount", &AreaLightRequestBus::Events::GetFilteringSampleCount) ->Event("SetFilteringSampleCount", &AreaLightRequestBus::Events::SetFilteringSampleCount) ->Event("GetEsmExponent", &AreaLightRequestBus::Events::GetEsmExponent) @@ -95,7 +93,6 @@ namespace AZ::Render ->VirtualProperty("ShadowBias", "GetShadowBias", "SetShadowBias") ->VirtualProperty("ShadowmapMaxSize", "GetShadowmapMaxSize", "SetShadowmapMaxSize") ->VirtualProperty("ShadowFilterMethod", "GetShadowFilterMethod", "SetShadowFilterMethod") - ->VirtualProperty("SofteningBoundaryWidthAngle", "GetSofteningBoundaryWidthAngle", "SetSofteningBoundaryWidthAngle") ->VirtualProperty("FilteringSampleCount", "GetFilteringSampleCount", "SetFilteringSampleCount") ->VirtualProperty("EsmExponent", "GetEsmExponent", "SetEsmExponent"); ; @@ -307,7 +304,6 @@ namespace AZ::Render m_lightShapeDelegate->SetShadowBias(m_configuration.m_bias); m_lightShapeDelegate->SetShadowmapMaxSize(m_configuration.m_shadowmapMaxSize); m_lightShapeDelegate->SetShadowFilterMethod(m_configuration.m_shadowFilterMethod); - m_lightShapeDelegate->SetSofteningBoundaryWidthAngle(m_configuration.m_boundaryWidthInDegrees); m_lightShapeDelegate->SetFilteringSampleCount(m_configuration.m_filteringSampleCount); m_lightShapeDelegate->SetEsmExponent(m_configuration.m_esmExponent); } @@ -506,20 +502,6 @@ namespace AZ::Render } } - float AreaLightComponentController::GetSofteningBoundaryWidthAngle() const - { - return m_configuration.m_boundaryWidthInDegrees; - } - - void AreaLightComponentController::SetSofteningBoundaryWidthAngle(float width) - { - m_configuration.m_boundaryWidthInDegrees = width; - if (m_lightShapeDelegate) - { - m_lightShapeDelegate->SetSofteningBoundaryWidthAngle(width); - } - } - uint32_t AreaLightComponentController::GetFilteringSampleCount() const { return m_configuration.m_filteringSampleCount; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/AreaLightComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/AreaLightComponentController.h index 3bec61551f..cc6223e7e5 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/AreaLightComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/AreaLightComponentController.h @@ -82,8 +82,6 @@ namespace AZ void SetShadowmapMaxSize(ShadowmapSize size) override; ShadowFilterMethod GetShadowFilterMethod() const override; void SetShadowFilterMethod(ShadowFilterMethod method) override; - float GetSofteningBoundaryWidthAngle() const override; - void SetSofteningBoundaryWidthAngle(float width) override; uint32_t GetFilteringSampleCount() const override; void SetFilteringSampleCount(uint32_t count) override; float GetEsmExponent() const override; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp index 37d94f5ed1..9d384c2e24 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp @@ -37,7 +37,6 @@ namespace AZ ->Field("IsCascadeCorrectionEnabled", &DirectionalLightComponentConfig::m_isCascadeCorrectionEnabled) ->Field("IsDebugColoringEnabled", &DirectionalLightComponentConfig::m_isDebugColoringEnabled) ->Field("ShadowFilterMethod", &DirectionalLightComponentConfig::m_shadowFilterMethod) - ->Field("SofteningBoundaryWidth", &DirectionalLightComponentConfig::m_boundaryWidth) ->Field("PcfFilteringSampleCount", &DirectionalLightComponentConfig::m_filteringSampleCount) ->Field("ShadowReceiverPlaneBiasEnabled", &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled); } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp index fbc1ccc35d..78558cfc85 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp @@ -80,8 +80,6 @@ namespace AZ ->Event("SetDebugColoringEnabled", &DirectionalLightRequestBus::Events::SetDebugColoringEnabled) ->Event("GetShadowFilterMethod", &DirectionalLightRequestBus::Events::GetShadowFilterMethod) ->Event("SetShadowFilterMethod", &DirectionalLightRequestBus::Events::SetShadowFilterMethod) - ->Event("GetSofteningBoundaryWidth", &DirectionalLightRequestBus::Events::GetSofteningBoundaryWidth) - ->Event("SetSofteningBoundaryWidth", &DirectionalLightRequestBus::Events::SetSofteningBoundaryWidth) ->Event("GetFilteringSampleCount", &DirectionalLightRequestBus::Events::GetFilteringSampleCount) ->Event("SetFilteringSampleCount", &DirectionalLightRequestBus::Events::SetFilteringSampleCount) ->Event("GetShadowReceiverPlaneBiasEnabled", &DirectionalLightRequestBus::Events::GetShadowReceiverPlaneBiasEnabled) @@ -99,7 +97,6 @@ namespace AZ ->VirtualProperty("ViewFrustumCorrectionEnabled", "GetViewFrustumCorrectionEnabled", "SetViewFrustumCorrectionEnabled") ->VirtualProperty("DebugColoringEnabled", "GetDebugColoringEnabled", "SetDebugColoringEnabled") ->VirtualProperty("ShadowFilterMethod", "GetShadowFilterMethod", "SetShadowFilterMethod") - ->VirtualProperty("SofteningBoundaryWidth", "GetSofteningBoundaryWidth", "SetSofteningBoundaryWidth") ->VirtualProperty("FilteringSampleCount", "GetFilteringSampleCount", "SetFilteringSampleCount") ->VirtualProperty("ShadowReceiverPlaneBiasEnabled", "GetShadowReceiverPlaneBiasEnabled", "SetShadowReceiverPlaneBiasEnabled"); ; @@ -404,21 +401,6 @@ namespace AZ } } - float DirectionalLightComponentController::GetSofteningBoundaryWidth() const - { - return m_configuration.m_boundaryWidth; - } - - void DirectionalLightComponentController::SetSofteningBoundaryWidth(float width) - { - width = GetMin(Shadow::MaxSofteningBoundaryWidth, GetMax(0.f, width)); - m_configuration.m_boundaryWidth = width; - if (m_featureProcessor) - { - m_featureProcessor->SetShadowBoundaryWidth(m_lightHandle, width); - } - } - uint32_t DirectionalLightComponentController::GetFilteringSampleCount() const { return aznumeric_cast(m_configuration.m_filteringSampleCount); @@ -517,7 +499,6 @@ namespace AZ SetViewFrustumCorrectionEnabled(m_configuration.m_isCascadeCorrectionEnabled); SetDebugColoringEnabled(m_configuration.m_isDebugColoringEnabled); SetShadowFilterMethod(m_configuration.m_shadowFilterMethod); - SetSofteningBoundaryWidth(m_configuration.m_boundaryWidth); SetFilteringSampleCount(m_configuration.m_filteringSampleCount); SetShadowReceiverPlaneBiasEnabled(m_configuration.m_receiverPlaneBiasEnabled); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h index b8052bfc36..933f2705e7 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h @@ -76,8 +76,6 @@ namespace AZ void SetDebugColoringEnabled(bool enabled) override; ShadowFilterMethod GetShadowFilterMethod() const override; void SetShadowFilterMethod(ShadowFilterMethod method) override; - float GetSofteningBoundaryWidth() const override; - void SetSofteningBoundaryWidth(float width) override; uint32_t GetFilteringSampleCount() const override; void SetFilteringSampleCount(uint32_t count) override; bool GetShadowReceiverPlaneBiasEnabled() const override; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DiskLightDelegate.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DiskLightDelegate.cpp index baf0cdced1..ebc79abca5 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DiskLightDelegate.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DiskLightDelegate.cpp @@ -147,14 +147,6 @@ namespace AZ::Render } } - void DiskLightDelegate::SetSofteningBoundaryWidthAngle(float widthInDegrees) - { - if (GetShadowsEnabled() && GetLightHandle().IsValid()) - { - GetFeatureProcessor()->SetSofteningBoundaryWidthAngle(GetLightHandle(), DegToRad(widthInDegrees)); - } - } - void DiskLightDelegate::SetFilteringSampleCount(uint32_t count) { if (GetShadowsEnabled() && GetLightHandle().IsValid()) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DiskLightDelegate.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DiskLightDelegate.h index e0fd16f6be..2be782c69c 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DiskLightDelegate.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DiskLightDelegate.h @@ -44,7 +44,6 @@ namespace AZ void SetShadowBias(float bias) override; void SetShadowmapMaxSize(ShadowmapSize size) override; void SetShadowFilterMethod(ShadowFilterMethod method) override; - void SetSofteningBoundaryWidthAngle(float widthInDegrees) override; void SetFilteringSampleCount(uint32_t count) override; void SetEsmExponent(float exponent) override; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp index 954ec4cad8..1e5b2580f7 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp @@ -154,15 +154,6 @@ namespace AZ ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::AttributesAndValues) ->Attribute(Edit::Attributes::Visibility, &AreaLightComponentConfig::SupportsShadows) ->Attribute(Edit::Attributes::ReadOnly, &AreaLightComponentConfig::ShadowsDisabled) - ->DataElement(Edit::UIHandlers::Slider, &AreaLightComponentConfig::m_boundaryWidthInDegrees, "Softening boundary width", - "Width of the boundary between shadowed area and lit one. " - "Units are in degrees. " - "If this is 0, softening edge is disabled.") - ->Attribute(Edit::Attributes::Min, 0.f) - ->Attribute(Edit::Attributes::Max, 1.f) - ->Attribute(Edit::Attributes::Suffix, " deg") - ->Attribute(Edit::Attributes::Visibility, &AreaLightComponentConfig::SupportsShadows) - ->Attribute(Edit::Attributes::ReadOnly, &AreaLightComponentConfig::IsEsmDisabled) ->DataElement(Edit::UIHandlers::Slider, &AreaLightComponentConfig::m_filteringSampleCount, "Filtering sample count", "This is only used when the pixel is predicted to be on the boundary. Specific to PCF and ESM+PCF.") ->Attribute(Edit::Attributes::Min, 4) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp index 2854244f6b..69ba295e9b 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp @@ -133,15 +133,6 @@ namespace AZ ->EnumAttribute(ShadowFilterMethod::Esm, "ESM") ->EnumAttribute(ShadowFilterMethod::EsmPcf, "ESM+PCF") ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) - ->DataElement(Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_boundaryWidth, "Softening boundary width", - "Width of the boundary between shadowed area and lit one. " - "Units are in meters. " - "If this is 0, softening edge is disabled.") - ->Attribute(Edit::Attributes::Min, 0.f) - ->Attribute(Edit::Attributes::Max, 0.1f) - ->Attribute(Edit::Attributes::Suffix, " m") - ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) - ->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsEsmDisabled) ->DataElement(Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_filteringSampleCount, "Filtering sample count", "This is used only when the pixel is predicted as on the boundary. " "Specific to PCF and ESM+PCF.") diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/LightDelegateBase.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/LightDelegateBase.h index 2bd25b76a3..336c67f55d 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/LightDelegateBase.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/LightDelegateBase.h @@ -56,7 +56,6 @@ namespace AZ void SetShadowBias([[maybe_unused]] float bias) override {}; void SetShadowmapMaxSize([[maybe_unused]] ShadowmapSize size) override {}; void SetShadowFilterMethod([[maybe_unused]] ShadowFilterMethod method) override {}; - void SetSofteningBoundaryWidthAngle([[maybe_unused]] float widthInDegrees) override {}; void SetFilteringSampleCount([[maybe_unused]] uint32_t count) override {}; void SetEsmExponent([[maybe_unused]] float esmExponent) override{}; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/LightDelegateInterface.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/LightDelegateInterface.h index 6d08971542..9bb8188898 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/LightDelegateInterface.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/LightDelegateInterface.h @@ -75,8 +75,6 @@ namespace AZ virtual void SetShadowmapMaxSize(ShadowmapSize size) = 0; //! Sets the filter method for the shadow virtual void SetShadowFilterMethod(ShadowFilterMethod method) = 0; - //! Sets the width of boundary between shadowed area and lit area in degrees. - virtual void SetSofteningBoundaryWidthAngle(float widthInDegrees) = 0; //! Sets the sample count for filtering of the shadow boundary, max 64. virtual void SetFilteringSampleCount(uint32_t count) = 0; //! Sets the Esm exponent to use. Higher values produce a steeper falloff between light and shadow. diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/SphereLightDelegate.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/SphereLightDelegate.cpp index 8853db5751..661b0c6b25 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/SphereLightDelegate.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/SphereLightDelegate.cpp @@ -92,14 +92,6 @@ namespace AZ::Render } } - void SphereLightDelegate::SetSofteningBoundaryWidthAngle(float widthInDegrees) - { - if (GetShadowsEnabled() && GetLightHandle().IsValid()) - { - GetFeatureProcessor()->SetSofteningBoundaryWidthAngle(GetLightHandle(), DegToRad(widthInDegrees)); - } - } - void SphereLightDelegate::SetFilteringSampleCount(uint32_t count) { if (GetShadowsEnabled() && GetLightHandle().IsValid()) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/SphereLightDelegate.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/SphereLightDelegate.h index e2903b2d72..8bdee2442a 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/SphereLightDelegate.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/SphereLightDelegate.h @@ -34,7 +34,6 @@ namespace AZ void SetShadowBias(float bias) override; void SetShadowmapMaxSize(ShadowmapSize size) override; void SetShadowFilterMethod(ShadowFilterMethod method) override; - void SetSofteningBoundaryWidthAngle(float widthInDegrees) override; void SetFilteringSampleCount(uint32_t count) override; void SetEsmExponent(float esmExponent) override; From d7ac1be72633ed74f6077aa69241d27d8e55ae60 Mon Sep 17 00:00:00 2001 From: mrieggeramzn Date: Wed, 13 Oct 2021 11:46:29 -0700 Subject: [PATCH 02/27] Adding directional light shadow bias Signed-off-by: mrieggeramzn --- .../Shadow/DirectionalLightShadow.azsli | 16 ++++++++------- .../CoreLights/ViewSrg.azsli | 3 ++- ...irectionalLightFeatureProcessorInterface.h | 3 +++ .../DirectionalLightFeatureProcessor.cpp | 13 ++++++++---- .../DirectionalLightFeatureProcessor.h | 10 ++++------ .../CoreLights/DirectionalLightBus.h | 8 ++++++++ .../DirectionalLightComponentConfig.h | 3 +++ .../DirectionalLightComponentConfig.cpp | 3 ++- .../DirectionalLightComponentController.cpp | 20 ++++++++++++++++++- .../DirectionalLightComponentController.h | 2 ++ .../EditorDirectionalLightComponent.cpp | 17 ++++++++++++---- 11 files changed, 74 insertions(+), 24 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli index 633ea85387..b53dda13aa 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli @@ -111,16 +111,18 @@ void DirectionalLightShadow::GetShadowCoords( float3 worldPosition, out float3 shadowCoords[ViewSrg::MaxCascadeCount]) { - const float4x4 depthBiasMatrices[ViewSrg::MaxCascadeCount] = - ViewSrg::m_directionalLightShadows[lightIndex].m_depthBiasMatrices; const uint cascadeCount = ViewSrg::m_directionalLightShadows[lightIndex].m_cascadeCount; - + const float shadowBias = ViewSrg::m_directionalLightShadows[lightIndex].m_shadowBias; + const float4x4 lightViewToShadowmapMatrices[ViewSrg::MaxCascadeCount] = ViewSrg::m_directionalLightShadows[lightIndex].m_lightViewToShadowmapMatrices; + const float4x4 worldToLightViewMatrices[ViewSrg::MaxCascadeCount] = ViewSrg::m_directionalLightShadows[lightIndex].m_worldToLightViewMatrices; + for (uint index = 0; index < cascadeCount; ++index) { - const float4x4 depthBiasMatrix = depthBiasMatrices[index]; - const float4 shadowCoordHomogeneous = mul(depthBiasMatrix, - float4(worldPosition, 1.)); - shadowCoords[index] = shadowCoordHomogeneous.xyz / shadowCoordHomogeneous.w; + float4 lightSpacePos = mul(worldToLightViewMatrices[index], float4(worldPosition, 1.)); + lightSpacePos.z += shadowBias; + + const float4 clipSpacePos = mul(lightViewToShadowmapMatrices[index], lightSpacePos); + shadowCoords[index] = clipSpacePos.xyz / clipSpacePos.w; } } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli index 94d6f20da3..27fa36182c 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli @@ -102,18 +102,19 @@ partial ShaderResourceGroup ViewSrg struct DirectionalLightShadow { - float4x4 m_depthBiasMatrices[MaxCascadeCount]; float4x4 m_lightViewToShadowmapMatrices[MaxCascadeCount]; float4x4 m_worldToLightViewMatrices[MaxCascadeCount]; float m_slopeBiasBase[MaxCascadeCount]; float m_boundaryScale; uint m_shadowmapSize; // width and height of shadowmap uint m_cascadeCount; + float m_shadowBias; uint m_predictionSampleCount; uint m_filteringSampleCount; uint m_debugFlags; uint m_shadowFilterMethod; float m_far_minus_near; + float3 m_padding; }; enum ShadowFilterMethod diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h index 769c7b95a6..2bba1338a1 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h @@ -157,6 +157,9 @@ namespace AZ //! Sets whether the directional shadowmap should use receiver plane bias. //! This attempts to reduce shadow acne when using large pcf filters. virtual void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) = 0; + + //! Reduces acne by applying a small amount of bias along shadow-space z. + virtual void SetShadowBias(LightHandle handle, float bias) = 0; }; } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp index 0a9f3480ad..4774ac31a1 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp @@ -589,6 +589,15 @@ namespace AZ m_shadowProperties.GetData(handle.GetIndex()).m_isReceiverPlaneBiasEnabled = enable; } + void DirectionalLightFeatureProcessor::SetShadowBias(LightHandle handle, float bias) + { + for (auto& it : m_shadowData) + { + it.second.GetData(handle.GetIndex()).m_shadowBias = bias; + } + m_shadowBufferNeedsUpdate = true; + } + void DirectionalLightFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline) { PrepareForChangingRenderPipelineAndCameraView(); @@ -1522,10 +1531,6 @@ namespace AZ for (uint16_t cascadeIndex = 0; cascadeIndex < GetCascadeCount(handle); ++cascadeIndex) { - const Matrix4x4& worldToLightClipMatrix = property.m_segments.at(cameraView)[cascadeIndex].m_view->GetWorldToClipMatrix(); - const Matrix4x4 depthBiasMatrix = Shadow::GetClipToShadowmapTextureMatrix() * worldToLightClipMatrix; - shadowData.m_depthBiasMatrices[cascadeIndex] = depthBiasMatrix; - const Matrix4x4& lightViewToLightClipMatrix = property.m_segments.at(cameraView)[cascadeIndex].m_view->GetViewToClipMatrix(); const Matrix4x4 lightViewToShadowmapMatrix = Shadow::GetClipToShadowmapTextureMatrix() * lightViewToLightClipMatrix; shadowData.m_lightViewToShadowmapMatrices[cascadeIndex] = lightViewToShadowmapMatrix; diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h index 3c1ff8eabd..2cf5b0b1e6 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h @@ -72,12 +72,6 @@ namespace AZ // [GFX TODO][ATOM-15172] Look into compacting struct DirectionalLightShadowData struct DirectionalLightShadowData { - AZStd::array m_depthBiasMatrices = - { { - Matrix4x4::CreateIdentity(), - Matrix4x4::CreateIdentity(), - Matrix4x4::CreateIdentity(), - Matrix4x4::CreateIdentity() } }; AZStd::array m_lightViewToShadowmapMatrices = { { Matrix4x4::CreateIdentity(), @@ -97,11 +91,14 @@ namespace AZ float m_boundaryScale = 0.f; uint32_t m_shadowmapSize = 1; // width and height of shadowmap uint32_t m_cascadeCount = 1; + // Reduce acne by applying a small amount of bias to apply along shadow-space z. + float m_shadowBias = 0.0f; uint32_t m_predictionSampleCount = 0; uint32_t m_filteringSampleCount = 0; uint32_t m_debugFlags = 0; uint32_t m_shadowFilterMethod = 0; float m_far_minus_near = 0; + float m_padding[3]; }; class DirectionalLightFeatureProcessor final @@ -218,6 +215,7 @@ namespace AZ void SetShadowFilterMethod(LightHandle handle, ShadowFilterMethod method) override; void SetFilteringSampleCount(LightHandle handle, uint16_t count) override; void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) override; + void SetShadowBias(LightHandle handle, float bias) override; const Data::Instance GetLightBuffer() const; uint32_t GetLightCount() const; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h index a8088c63ac..9ccc4f329b 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h @@ -168,6 +168,14 @@ namespace AZ //! Sets whether the directional shadowmap should use receiver plane bias. //! @param enable flag specifying whether to enable the receiver plane bias feature virtual void SetShadowReceiverPlaneBiasEnabled(bool enable) = 0; + + //! Shadow bias reduces acne by applying a small amount of offset along shadow-space z. + //! @return Returns the amount of bias to apply. + virtual float GetShadowBias() const = 0; + + //! Shadow bias reduces acne by applying a small amount of offset along shadow-space z. + //! @param Sets the amount of bias to apply. + virtual void SetShadowBias(float bias) = 0; }; using DirectionalLightRequestBus = EBus; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h index a58acc0114..20073f437c 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h @@ -109,6 +109,9 @@ namespace AZ //! This uses partial derivatives to reduce shadow acne when using large pcf kernels. bool m_receiverPlaneBiasEnabled = true; + //! Reduces shadow acne by applying a small amount of offset along shadow-space z. + float m_shadowBias = 0.0f; + bool IsSplitManual() const; bool IsSplitAutomatic() const; bool IsCascadeCorrectionDisabled() const; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp index 9d384c2e24..98d3f838c0 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp @@ -38,7 +38,8 @@ namespace AZ ->Field("IsDebugColoringEnabled", &DirectionalLightComponentConfig::m_isDebugColoringEnabled) ->Field("ShadowFilterMethod", &DirectionalLightComponentConfig::m_shadowFilterMethod) ->Field("PcfFilteringSampleCount", &DirectionalLightComponentConfig::m_filteringSampleCount) - ->Field("ShadowReceiverPlaneBiasEnabled", &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled); + ->Field("ShadowReceiverPlaneBiasEnabled", &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled) + ->Field("Shadow Bias", &DirectionalLightComponentConfig::m_shadowBias); } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp index 78558cfc85..c20a9f8e17 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp @@ -84,6 +84,8 @@ namespace AZ ->Event("SetFilteringSampleCount", &DirectionalLightRequestBus::Events::SetFilteringSampleCount) ->Event("GetShadowReceiverPlaneBiasEnabled", &DirectionalLightRequestBus::Events::GetShadowReceiverPlaneBiasEnabled) ->Event("SetShadowReceiverPlaneBiasEnabled", &DirectionalLightRequestBus::Events::SetShadowReceiverPlaneBiasEnabled) + ->Event("GetShadowBias", &DirectionalLightRequestBus::Events::GetShadowBias) + ->Event("SetShadowBias", &DirectionalLightRequestBus::Events::SetShadowBias) ->VirtualProperty("Color", "GetColor", "SetColor") ->VirtualProperty("Intensity", "GetIntensity", "SetIntensity") ->VirtualProperty("AngularDiameter", "GetAngularDiameter", "SetAngularDiameter") @@ -98,7 +100,8 @@ namespace AZ ->VirtualProperty("DebugColoringEnabled", "GetDebugColoringEnabled", "SetDebugColoringEnabled") ->VirtualProperty("ShadowFilterMethod", "GetShadowFilterMethod", "SetShadowFilterMethod") ->VirtualProperty("FilteringSampleCount", "GetFilteringSampleCount", "SetFilteringSampleCount") - ->VirtualProperty("ShadowReceiverPlaneBiasEnabled", "GetShadowReceiverPlaneBiasEnabled", "SetShadowReceiverPlaneBiasEnabled"); + ->VirtualProperty("ShadowReceiverPlaneBiasEnabled", "GetShadowReceiverPlaneBiasEnabled", "SetShadowReceiverPlaneBiasEnabled") + ->VirtualProperty("ShadowBias", "GetShadowBias", "SetShadowBias"); ; } } @@ -406,6 +409,20 @@ namespace AZ return aznumeric_cast(m_configuration.m_filteringSampleCount); } + void DirectionalLightComponentController::SetShadowBias(float bias) + { + m_configuration.m_shadowBias = bias; + if (m_featureProcessor) + { + m_featureProcessor->SetShadowBias(m_lightHandle, bias); + } + } + + float DirectionalLightComponentController::GetShadowBias() const + { + return m_configuration.m_shadowBias; + } + void DirectionalLightComponentController::SetFilteringSampleCount(uint32_t count) { const uint16_t count16 = GetMin(Shadow::MaxPcfSamplingCount, aznumeric_cast(count)); @@ -499,6 +516,7 @@ namespace AZ SetViewFrustumCorrectionEnabled(m_configuration.m_isCascadeCorrectionEnabled); SetDebugColoringEnabled(m_configuration.m_isDebugColoringEnabled); SetShadowFilterMethod(m_configuration.m_shadowFilterMethod); + SetShadowBias(m_configuration.m_shadowBias); SetFilteringSampleCount(m_configuration.m_filteringSampleCount); SetShadowReceiverPlaneBiasEnabled(m_configuration.m_receiverPlaneBiasEnabled); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h index 933f2705e7..a0d552cf99 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h @@ -80,6 +80,8 @@ namespace AZ void SetFilteringSampleCount(uint32_t count) override; bool GetShadowReceiverPlaneBiasEnabled() const override; void SetShadowReceiverPlaneBiasEnabled(bool enable) override; + float GetShadowBias() const override; + void SetShadowBias(float width) override; private: friend class EditorDirectionalLightComponent; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp index 69ba295e9b..99e2cc1485 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp @@ -133,8 +133,8 @@ namespace AZ ->EnumAttribute(ShadowFilterMethod::Esm, "ESM") ->EnumAttribute(ShadowFilterMethod::EsmPcf, "ESM+PCF") ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) - ->DataElement(Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_filteringSampleCount, "Filtering sample count", - "This is used only when the pixel is predicted as on the boundary. " + ->DataElement(Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_filteringSampleCount, "Filtering sample count\n", + "This is used only when the pixel is predicted as on the boundary.\n" "Specific to PCF and ESM+PCF.") ->Attribute(Edit::Attributes::Min, 4) ->Attribute(Edit::Attributes::Max, 64) @@ -142,10 +142,19 @@ namespace AZ ->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled) ->DataElement( Edit::UIHandlers::CheckBox, &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled, - "Shadow Receiver Plane Bias Enable", + "Shadow Receiver Plane Bias Enable\n", "This reduces shadow acne when using large pcf kernels.") ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) - ->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled); + ->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled) + ->DataElement( + Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_shadowBias, + "Shadow Bias\n", + "Reduces acne by applying a fixed bias along z in shadow-space.\n" + "If this is 0, no biasing is applied.") + ->Attribute(Edit::Attributes::Min, 0.f) + ->Attribute(Edit::Attributes::Max, 0.2) + ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + ; } } From e5a73fe8ff5e9ac641ec641b1ba3f8beb1ffa48a Mon Sep 17 00:00:00 2001 From: AMZN-Phil Date: Mon, 18 Oct 2021 16:32:42 -0700 Subject: [PATCH 03/27] Add helper function for UI to return paths to all cached gem jsons for a given repo Signed-off-by: AMZN-Phil --- scripts/o3de/o3de/repo.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/scripts/o3de/o3de/repo.py b/scripts/o3de/o3de/repo.py index 191fdbd3e1..6b6dffe8b3 100644 --- a/scripts/o3de/o3de/repo.py +++ b/scripts/o3de/o3de/repo.py @@ -92,6 +92,42 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, return 0 +def get_gem_json_paths_from_cached_repo(repo_uri : str) -> list: + url = f'{repo_uri}/repo.json' + repo_sha256 = hashlib.sha256(url.encode()) + cache_folder = manifest.get_o3de_cache_folder() + cache_filename = cache_folder / str(repo_sha256.hexdigest() + '.json') + + gem_list = [] + + file_name = pathlib.Path(cache_filename).resolve() + if not file_name.is_file(): + return gem_list + + with file_name.open('r') as f: + try: + repo_data = json.load(f) + except json.JSONDecodeError as e: + logger.error(f'{file_name} failed to load: {str(e)}') + return gem_list + + # Get list of gems, then add all json paths to the list if they exist in the cache + repo_gems = [] + try: + repo_gems.append((repo_data['gems'], 'gem.json')) + except KeyError: + pass + + for o3de_object_uris, manifest_json in repo_gems: + for o3de_object_uri in o3de_object_uris: + manifest_json_uri = f'{o3de_object_uri}/{manifest_json}' + manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode()) + cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') + if cache_file.is_file(): + gem_list.append(cache_file) + + return gem_list + def refresh_repos() -> int: json_data = manifest.load_o3de_manifest() From c48d95748557d17c246285c8a8b84c4248a17cd6 Mon Sep 17 00:00:00 2001 From: AMZN-Phil Date: Mon, 18 Oct 2021 16:34:07 -0700 Subject: [PATCH 04/27] Slightly better variable naming Signed-off-by: AMZN-Phil --- scripts/o3de/o3de/repo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/o3de/o3de/repo.py b/scripts/o3de/o3de/repo.py index 6b6dffe8b3..ae8af43338 100644 --- a/scripts/o3de/o3de/repo.py +++ b/scripts/o3de/o3de/repo.py @@ -122,9 +122,9 @@ def get_gem_json_paths_from_cached_repo(repo_uri : str) -> list: for o3de_object_uri in o3de_object_uris: manifest_json_uri = f'{o3de_object_uri}/{manifest_json}' manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode()) - cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') - if cache_file.is_file(): - gem_list.append(cache_file) + cache_gem_json_filepath = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') + if cache_gem_json_filepath.is_file(): + gem_list.append(cache_gem_json_filepath) return gem_list From 7ce376b5b2a6901a55718e3e9f953da6a1c2594b Mon Sep 17 00:00:00 2001 From: AMZN-Phil Date: Tue, 19 Oct 2021 08:40:36 -0700 Subject: [PATCH 05/27] Add some log output if the cached files cannot be found Signed-off-by: AMZN-Phil --- scripts/o3de/o3de/repo.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/o3de/o3de/repo.py b/scripts/o3de/o3de/repo.py index ae8af43338..5c47c2bee8 100644 --- a/scripts/o3de/o3de/repo.py +++ b/scripts/o3de/o3de/repo.py @@ -92,7 +92,7 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, return 0 -def get_gem_json_paths_from_cached_repo(repo_uri : str) -> list: +def get_gem_json_paths_from_cached_repo(repo_uri: str) -> list: url = f'{repo_uri}/repo.json' repo_sha256 = hashlib.sha256(url.encode()) cache_folder = manifest.get_o3de_cache_folder() @@ -102,6 +102,7 @@ def get_gem_json_paths_from_cached_repo(repo_uri : str) -> list: file_name = pathlib.Path(cache_filename).resolve() if not file_name.is_file(): + logger.error(f'Could not find cached repo json file for {repo_uri}') return gem_list with file_name.open('r') as f: @@ -124,6 +125,7 @@ def get_gem_json_paths_from_cached_repo(repo_uri : str) -> list: manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode()) cache_gem_json_filepath = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') if cache_gem_json_filepath.is_file(): + logger.warn(f'Could not find cached gem json file for {o3de_object_uri} in repo {repo_uri}') gem_list.append(cache_gem_json_filepath) return gem_list From 77ec88f86e5eff5969f397a15b1da1520b14af2b Mon Sep 17 00:00:00 2001 From: Junbo Liang <68558268+junbo75@users.noreply.github.com> Date: Wed, 20 Oct 2021 11:28:28 -0700 Subject: [PATCH 06/27] Improve the stability of metrics gem tests by removing local file operations (#4761) Signed-off-by: Junbo Liang --- .../Code/Include/Private/MetricsManager.h | 13 +++++----- .../AWSMetrics/Code/Tests/AWSMetricsGemMock.h | 5 ---- .../Code/Tests/MetricsManagerTest.cpp | 25 ++++++++++++++----- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/Gems/AWSMetrics/Code/Include/Private/MetricsManager.h b/Gems/AWSMetrics/Code/Include/Private/MetricsManager.h index 2cd5ba97a8..3bb06acd75 100644 --- a/Gems/AWSMetrics/Code/Include/Private/MetricsManager.h +++ b/Gems/AWSMetrics/Code/Include/Private/MetricsManager.h @@ -31,7 +31,7 @@ namespace AWSMetrics static const unsigned int DesiredMaxWorkers = 2; MetricsManager(); - ~MetricsManager(); + virtual ~MetricsManager(); //! Initializing the metrics manager //! @return Whether the operation is successful. @@ -93,6 +93,12 @@ namespace AWSMetrics //! @return Total number of requests for sending metrics events. int GetNumTotalRequests() const; + protected: + //! Send metrics to a local file. + //! @param metricsQueue metricsQueue Metrics queue that stores the metrics. + //! @return Outcome of the operation. + virtual AZ::Outcome SendMetricsToFile(AZStd::shared_ptr metricsQueue); + private: //! Job management void SetupJobContext(); @@ -112,11 +118,6 @@ namespace AWSMetrics //! @param metricsQueue Metrics events to send. void SendMetricsToServiceApiAsync(const MetricsQueue& metricsQueue); - //! Send metrics to a local file. - //! @param metricsQueue metricsQueue Metrics queue that stores the metrics. - //! @return Outcome of the operation. - AZ::Outcome SendMetricsToFile(AZStd::shared_ptr metricsQueue); - //! Push metrics events to the front of the queue for retry. //! @param metricsEventsForRetry Metrics events for retry. void PushMetricsForRetry(MetricsQueue& metricsEventsForRetry); diff --git a/Gems/AWSMetrics/Code/Tests/AWSMetricsGemMock.h b/Gems/AWSMetrics/Code/Tests/AWSMetricsGemMock.h index b77f39c93c..4f40fd6ff1 100644 --- a/Gems/AWSMetrics/Code/Tests/AWSMetricsGemMock.h +++ b/Gems/AWSMetrics/Code/Tests/AWSMetricsGemMock.h @@ -139,11 +139,6 @@ namespace AWSMetrics return true; } - bool RemoveDirectory(const AZStd::string& directory) - { - return AZ::IO::SystemFile::DeleteDir(directory.c_str()); - } - AZ::IO::FileIOBase* m_priorFileIO = nullptr; AZ::IO::FileIOBase* m_localFileIO = nullptr; diff --git a/Gems/AWSMetrics/Code/Tests/MetricsManagerTest.cpp b/Gems/AWSMetrics/Code/Tests/MetricsManagerTest.cpp index 3f87a1079d..9fcebec524 100644 --- a/Gems/AWSMetrics/Code/Tests/MetricsManagerTest.cpp +++ b/Gems/AWSMetrics/Code/Tests/MetricsManagerTest.cpp @@ -75,6 +75,23 @@ namespace AZ namespace AWSMetrics { + class MetricsManagerMock + : public MetricsManager + { + private: + AZ::Outcome SendMetricsToFile(AZStd::shared_ptr metricsQueue) override + { + if (AZ::IO::FileIOBase::GetInstance()) + { + return AZ::Success(); + } + else + { + return AZ::Failure(AZStd::string{ "Invalid File IO" }); + } + } + }; + class AWSMetricsNotificationBusMock : protected AWSMetricsNotificationBus::Handler { @@ -134,13 +151,11 @@ namespace AWSMetrics AWSMetricsGemAllocatorFixture::SetUp(); AWSMetricsRequestBus::Handler::BusConnect(); - m_metricsManager = AZStd::make_unique(); + m_metricsManager = AZStd::make_unique(); AZStd::string configFilePath = CreateClientConfigFile(true, (double) TestMetricsEventSizeInBytes / MbToBytes * 2, DefaultFlushPeriodInSeconds, 0); m_settingsRegistry->MergeSettingsFile(configFilePath, AZ::SettingsRegistryInterface::Format::JsonMergePatch, {}); m_metricsManager->Init(); - RemoveFile(m_metricsManager->GetMetricsFilePath()); - ReplaceLocalFileIOWithMockIO(); } @@ -149,8 +164,6 @@ namespace AWSMetrics RevertMockIOToLocalFileIO(); RemoveFile(GetDefaultTestFilePath()); - RemoveFile(m_metricsManager->GetMetricsFilePath()); - RemoveDirectory(m_metricsManager->GetMetricsFileDirectory()); m_metricsManager.reset(); @@ -233,7 +246,7 @@ namespace AWSMetrics } } - AZStd::unique_ptr m_metricsManager; + AZStd::unique_ptr m_metricsManager; AWSMetricsNotificationBusMock m_notifications; AZ::IO::FileIOBase* m_fileIOMock; From af2790659812238907762dc3e782de0bc963508c Mon Sep 17 00:00:00 2001 From: AMZN-AlexOteiza Date: Wed, 20 Oct 2021 20:00:53 +0100 Subject: [PATCH 07/27] Moved atom flaky tests to sandbox --- .../Gem/PythonTests/Atom/TestSuite_Main.py | 70 ------------------ .../Gem/PythonTests/Atom/TestSuite_Sandbox.py | 72 +++++++++++++++++++ 2 files changed, 72 insertions(+), 70 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py index 3403c938a8..6cc48984ab 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py @@ -253,73 +253,3 @@ class TestAtomEditorComponentsMain(object): ) -@pytest.mark.parametrize("project", ["AutomatedTesting"]) -@pytest.mark.parametrize("launcher_platform", ['windows_generic']) -@pytest.mark.system -class TestMaterialEditorBasicTests(object): - @pytest.fixture(autouse=True) - def setup_teardown(self, request, workspace, project): - def delete_files(): - file_system.delete( - [ - os.path.join(workspace.paths.project(), "Materials", "test_material.material"), - os.path.join(workspace.paths.project(), "Materials", "test_material_1.material"), - os.path.join(workspace.paths.project(), "Materials", "test_material_2.material"), - ], - True, - True, - ) - # Cleanup our newly created materials - delete_files() - - def teardown(): - # Cleanup our newly created materials - delete_files() - - request.addfinalizer(teardown) - - @pytest.mark.parametrize("exe_file_name", ["MaterialEditor"]) - @pytest.mark.test_case_id("C34448113") # Creating a New Asset. - @pytest.mark.test_case_id("C34448114") # Opening an Existing Asset. - @pytest.mark.test_case_id("C34448115") # Closing Selected Material. - @pytest.mark.test_case_id("C34448116") # Closing All Materials. - @pytest.mark.test_case_id("C34448117") # Closing all but Selected Material. - @pytest.mark.test_case_id("C34448118") # Saving Material. - @pytest.mark.test_case_id("C34448119") # Saving as a New Material. - @pytest.mark.test_case_id("C34448120") # Saving as a Child Material. - @pytest.mark.test_case_id("C34448121") # Saving all Open Materials. - def test_MaterialEditorBasicTests( - self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name): - - expected_lines = [ - "Material opened: True", - "Test asset doesn't exist initially: True", - "New asset created: True", - "New Material opened: True", - "Material closed: True", - "All documents closed: True", - "Close All Except Selected worked as expected: True", - "Actual Document saved with changes: True", - "Document saved as copy is saved with changes: True", - "Document saved as child is saved with changes: True", - "Save All worked as expected: True", - ] - unexpected_lines = [ - # "Trace::Assert", - # "Trace::Error", - "Traceback (most recent call last):" - ] - - hydra.launch_and_validate_results( - request, - TEST_DIRECTORY, - generic_launcher, - "hydra_AtomMaterialEditor_BasicTests.py", - run_python="--runpython", - timeout=120, - expected_lines=expected_lines, - unexpected_lines=unexpected_lines, - halt_on_unexpected=True, - null_renderer=True, - log_file_name="MaterialEditor.log", - ) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py index 58e5d00ff2..9bb7f9c50e 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py @@ -70,3 +70,75 @@ class TestAtomEditorComponentsSandbox(object): null_renderer=True, cfg_args=cfg_args, ) + +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.parametrize("launcher_platform", ['windows_generic']) +@pytest.mark.system +class TestMaterialEditorBasicTests(object): + @pytest.fixture(autouse=True) + def setup_teardown(self, request, workspace, project): + def delete_files(): + file_system.delete( + [ + os.path.join(workspace.paths.project(), "Materials", "test_material.material"), + os.path.join(workspace.paths.project(), "Materials", "test_material_1.material"), + os.path.join(workspace.paths.project(), "Materials", "test_material_2.material"), + ], + True, + True, + ) + # Cleanup our newly created materials + delete_files() + + def teardown(): + # Cleanup our newly created materials + delete_files() + + request.addfinalizer(teardown) + + @pytest.mark.parametrize("exe_file_name", ["MaterialEditor"]) + @pytest.mark.test_case_id("C34448113") # Creating a New Asset. + @pytest.mark.test_case_id("C34448114") # Opening an Existing Asset. + @pytest.mark.test_case_id("C34448115") # Closing Selected Material. + @pytest.mark.test_case_id("C34448116") # Closing All Materials. + @pytest.mark.test_case_id("C34448117") # Closing all but Selected Material. + @pytest.mark.test_case_id("C34448118") # Saving Material. + @pytest.mark.test_case_id("C34448119") # Saving as a New Material. + @pytest.mark.test_case_id("C34448120") # Saving as a Child Material. + @pytest.mark.test_case_id("C34448121") # Saving all Open Materials. + def test_MaterialEditorBasicTests( + self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name): + + expected_lines = [ + "Material opened: True", + "Test asset doesn't exist initially: True", + "New asset created: True", + "New Material opened: True", + "Material closed: True", + "All documents closed: True", + "Close All Except Selected worked as expected: True", + "Actual Document saved with changes: True", + "Document saved as copy is saved with changes: True", + "Document saved as child is saved with changes: True", + "Save All worked as expected: True", + ] + unexpected_lines = [ + # "Trace::Assert", + # "Trace::Error", + "Traceback (most recent call last):" + ] + + hydra.launch_and_validate_results( + request, + TEST_DIRECTORY, + generic_launcher, + "hydra_AtomMaterialEditor_BasicTests.py", + run_python="--runpython", + timeout=120, + expected_lines=expected_lines, + unexpected_lines=unexpected_lines, + halt_on_unexpected=True, + null_renderer=True, + log_file_name="MaterialEditor.log", + ) + From 714f5357b2ebacf1db8540e2adf9f49e4037ce7d Mon Sep 17 00:00:00 2001 From: amzn-phist <52085794+amzn-phist@users.noreply.github.com> Date: Wed, 20 Oct 2021 14:45:32 -0500 Subject: [PATCH 08/27] Add an error message to AP when the project path is invalid (#4801) * Add an error message to AP when bad project path Produce a log error or a dialog box error when the project path for AP does not have a project.json and is invalid. Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com> * Fix a failing unit test - AssetProcessorMessages Adding a check for 'project.json' caused BeforeRun() in a test fixture to fail. Teardown of the fixture was also broken if the test failed to fully startup the application manager, so added null checks there. Added an assert to the fixture's Setup to check the status of BeforeRun(). Added additional settings registry setup to the fixture to make sure the project path and branch token are configured before BeforeRun() is called. Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com> --- .../tests/AssetProcessorMessagesTests.cpp | 42 ++++++-- .../native/utilities/ApplicationManager.cpp | 9 +- .../utilities/GUIApplicationManager.cpp | 96 +------------------ 3 files changed, 43 insertions(+), 104 deletions(-) diff --git a/Code/Tools/AssetProcessor/native/tests/AssetProcessorMessagesTests.cpp b/Code/Tools/AssetProcessor/native/tests/AssetProcessorMessagesTests.cpp index 63536a160b..516f6beb3c 100644 --- a/Code/Tools/AssetProcessor/native/tests/AssetProcessorMessagesTests.cpp +++ b/Code/Tools/AssetProcessor/native/tests/AssetProcessorMessagesTests.cpp @@ -10,7 +10,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -98,10 +100,26 @@ namespace AssetProcessorMessagesTests int argC = 0; m_batchApplicationManager = AZStd::make_unique(&argC, nullptr, nullptr); - m_batchApplicationManager->BeforeRun(); - // Override Game Name to be "AutomatedTesting" - AssetUtilities::ComputeProjectName("AutomatedTesting", true); + auto registry = AZ::SettingsRegistry::Get(); + EXPECT_NE(registry, nullptr); + constexpr AZ::SettingsRegistryInterface::FixedValueString bootstrapKey{ + AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey + }; + constexpr AZ::SettingsRegistryInterface::FixedValueString projectPathKey{ bootstrapKey + "/project_path" }; + registry->Set(projectPathKey, "AutomatedTesting"); + AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); + + // Force the branch token into settings registry before starting the application manager. + // This avoids writing the asset_processor.setreg file which can cause fileIO errors. + const AZ::IO::FixedMaxPathString enginePath = AZ::Utils::GetEnginePath(); + constexpr AZ::SettingsRegistryInterface::FixedValueString branchTokenKey{ bootstrapKey + "/assetProcessor_branch_token" }; + AZStd::string token; + AZ::StringFunc::AssetPath::CalculateBranchToken(enginePath.c_str(), token); + registry->Set(branchTokenKey, token.c_str()); + + auto status = m_batchApplicationManager->BeforeRun(); + ASSERT_EQ(status, ApplicationManager::BeforeRunStatus::Status_Success); m_batchApplicationManager->m_platformConfiguration = new PlatformConfiguration(); m_batchApplicationManager->InitAssetProcessorManager(); @@ -159,21 +177,25 @@ namespace AssetProcessorMessagesTests ASSERT_TRUE(result); }); - - } void TearDown() override { - QEventLoop eventLoop; + if (m_batchApplicationManager->m_connectionManager) + { + QEventLoop eventLoop; - QObject::connect(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ReadyToQuit, &eventLoop, &QEventLoop::quit); + QObject::connect(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ReadyToQuit, &eventLoop, &QEventLoop::quit); - m_batchApplicationManager->m_connectionManager->QuitRequested(); + m_batchApplicationManager->m_connectionManager->QuitRequested(); - eventLoop.exec(); + eventLoop.exec(); + } - m_assetSystemComponent->Deactivate(); + if (m_assetSystemComponent) + { + m_assetSystemComponent->Deactivate(); + } m_batchApplicationManager->Destroy(); } diff --git a/Code/Tools/AssetProcessor/native/utilities/ApplicationManager.cpp b/Code/Tools/AssetProcessor/native/utilities/ApplicationManager.cpp index c237f4801e..a024ce6c7a 100644 --- a/Code/Tools/AssetProcessor/native/utilities/ApplicationManager.cpp +++ b/Code/Tools/AssetProcessor/native/utilities/ApplicationManager.cpp @@ -505,6 +505,14 @@ bool ApplicationManager::StartAZFramework() AzFramework::Application::Descriptor appDescriptor; AZ::ComponentApplication::StartupParameters params; + QDir projectPath{ AssetUtilities::ComputeProjectPath() }; + if (!projectPath.exists("project.json")) + { + AZStd::string errorMsg = AZStd::string::format("Path '%s' is not a valid project path.", projectPath.path().toUtf8().constData()); + AssetProcessor::MessageInfoBus::Broadcast(&AssetProcessor::MessageInfoBus::Events::OnErrorMessage, errorMsg.c_str()); + return false; + } + QString projectName = AssetUtilities::ComputeProjectName(); // Prevent loading of gems in the Create method of the ComponentApplication @@ -520,7 +528,6 @@ bool ApplicationManager::StartAZFramework() //Registering all the Components m_frameworkApp.RegisterComponentDescriptor(AzFramework::LogComponent::CreateDescriptor()); - Reflect(); const AzFramework::CommandLine* commandLine = nullptr; diff --git a/Code/Tools/AssetProcessor/native/utilities/GUIApplicationManager.cpp b/Code/Tools/AssetProcessor/native/utilities/GUIApplicationManager.cpp index 40d3bd3caa..c3ff22a39e 100644 --- a/Code/Tools/AssetProcessor/native/utilities/GUIApplicationManager.cpp +++ b/Code/Tools/AssetProcessor/native/utilities/GUIApplicationManager.cpp @@ -95,6 +95,8 @@ GUIApplicationManager::~GUIApplicationManager() ApplicationManager::BeforeRunStatus GUIApplicationManager::BeforeRun() { + AssetProcessor::MessageInfoBus::Handler::BusConnect(); + ApplicationManager::BeforeRunStatus status = ApplicationManagerBase::BeforeRun(); if (status != ApplicationManager::BeforeRunStatus::Status_Success) { @@ -109,7 +111,6 @@ ApplicationManager::BeforeRunStatus GUIApplicationManager::BeforeRun() #if defined(EXTERNAL_CRASH_REPORTING) CrashHandler::ToolsCrashHandler::InitCrashHandler("AssetProcessor", projectAssetRoot.absolutePath().toStdString()); #endif - AssetProcessor::MessageInfoBus::Handler::BusConnect(); // we have to monitor both the cache folder and the database file and restart AP if either of them gets deleted // It is important to note that we are monitoring the parent folder and not the actual cache folder itself since @@ -436,98 +437,7 @@ bool GUIApplicationManager::OnError(const char* /*window*/, const char* message) connection = Qt::QueuedConnection; } - if (m_isCurrentlyLoadingGems) - { - // if something goes wrong during gem initialization, this is a special case and we need to be extra helpful. - const char* userSettingsFile = "_WAF_/user_settings.options"; - const char* defaultSettingsFile = "_WAF_/default_settings.json"; - - QDir engineRoot; - AssetUtilities::ComputeEngineRoot(engineRoot); - - QString settingsPath = engineRoot.absoluteFilePath(userSettingsFile); - QString friendlyErrorMessage; - bool usingDefaults = false; - - if (QFile::exists(settingsPath)) - { - QSettings loader(settingsPath, QSettings::IniFormat); - QVariant settingValue = loader.value("Game Projects/enabled_game_projects"); - QStringList compiledProjects = settingValue.toStringList(); - - if (compiledProjects.isEmpty()) - { - QByteArray byteArray; - QFile jsonFile; - jsonFile.setFileName(engineRoot.absoluteFilePath(defaultSettingsFile)); - jsonFile.open(QIODevice::ReadOnly | QIODevice::Text); - byteArray = jsonFile.readAll(); - jsonFile.close(); - - QJsonObject settingsObject = QJsonDocument::fromJson(byteArray).object(); - QJsonArray projectsArray = settingsObject["Game Projects"].toArray(); - - if (!projectsArray.isEmpty()) - { - auto projectObject = projectsArray[0].toObject(); - QString projects = projectObject["default_value"].toString(); - - if (!projects.isEmpty()) - { - compiledProjects = projects.split(','); - usingDefaults = true; - } - } - } - - for (int i = 0; i < compiledProjects.size(); ++i) - { - compiledProjects[i] = compiledProjects[i].trimmed(); - } - - QString enabledProject = AssetUtilities::ComputeProjectName(); - - if (!compiledProjects.contains(enabledProject)) - { - QString projectSourceLine; - - if (usingDefaults) - { - projectSourceLine = QString("The currently compiled projects according to the defaults in %1 are '%2'").arg(defaultSettingsFile); - } - else - { - projectSourceLine = QString("The currently compiled projects according to %1 are '%2'").arg(userSettingsFile); - } - - projectSourceLine = projectSourceLine.arg(compiledProjects.join(", ")); - friendlyErrorMessage = QString("An error occurred while loading gems.\n" - "The enabled game project is not in the list of compiled projects.\n" - "Please configure the enabled project to be compiled and rebuild or change the enabled project.\n" - "The currently enabled game project (from bootstrap.cfg or /%4 command-line parameter) is '%1'.\n" - "%2\n" - "Full error text:\n" - "%3" - ).arg(enabledProject).arg(projectSourceLine).arg(message).arg(AssetUtilities::ProjectPathOverrideParameter); - } - } - - if (friendlyErrorMessage.isEmpty()) - { - friendlyErrorMessage = QString("An error occurred while loading gems.\n" - "This can happen when new gems are added to a project, but those gems need to be built in order to function.\n" - "This can also happen when switching to a different project, one which uses gems which are not yet built.\n" - "To continue, please build the current project before attempting to run Asset Processor again.\n\n" - "Full error text:\n" - "%1").arg(message); - } - QMetaObject::invokeMethod(this, "ShowMessageBox", connection, Q_ARG(QString, QString("Error")), Q_ARG(QString, friendlyErrorMessage), Q_ARG(bool, true)); - } - else - { - QMetaObject::invokeMethod(this, "ShowMessageBox", connection, Q_ARG(QString, QString("Error")), Q_ARG(QString, QString(message)), Q_ARG(bool, true)); - } - + QMetaObject::invokeMethod(this, "ShowMessageBox", connection, Q_ARG(QString, QString("Error")), Q_ARG(QString, QString(message)), Q_ARG(bool, true)); return true; } From 60c286dafa5be8cb188a117d7455f25325318374 Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Wed, 20 Oct 2021 12:56:30 -0700 Subject: [PATCH 09/27] LYN-7483 + LYN-7052 | Correctly initialize and refresh Prefab Focus Mode handler. (#4718) * Initialize the PrefabFocusHandler on context reset, to also cover the case of a new level being created on the welcome screen. Relax checks/restrictions on refreshes to cover cases where an instance is reused by the Prefab EOS. Refresh the breadcrumbs when a container is renamed and when a change is propagated to the instances to ensure the correct names are displayed. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Rename m_isInitialized to m_initialized in PrefabFocusHandler Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Use find_if to detect when a container entity in the focus path has been renamed. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Renaming and commenting variables in PrefabFocusHandler. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Undo minor naming change Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Replace lazy initialization and have the UI side initialize the Editor calls in PrefabFocusHandler. This should prevent issues with focus mode trying to access these interfaces in non-editor applications. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> --- .../PrefabEditorEntityOwnershipService.cpp | 2 +- .../Prefab/PrefabFocusHandler.cpp | 138 +++++++++++------- .../Prefab/PrefabFocusHandler.h | 24 ++- .../Prefab/PrefabFocusInterface.h | 5 + .../UI/Prefab/PrefabIntegrationManager.cpp | 5 + 5 files changed, 113 insertions(+), 61 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp index 7db507e751..67b5d99011 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp @@ -53,7 +53,7 @@ namespace AzToolsFramework AZ_Assert(m_loaderInterface != nullptr, "Couldn't get prefab loader interface, it's a requirement for PrefabEntityOwnership system to work"); - m_rootInstance = AZStd::unique_ptr(m_prefabSystemComponent->CreatePrefab({}, {}, "NewLevel.prefab")); + m_rootInstance = AZStd::unique_ptr(m_prefabSystemComponent->CreatePrefab({}, {}, "newLevel.prefab")); m_sliceOwnershipService.BusConnect(m_entityContextId); m_sliceOwnershipService.m_shouldAssertForLegacySlicesUsage = m_shouldAssertForLegacySlicesUsage; m_editorSliceOwnershipService.BusConnect(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp index a79b9eb73d..09b5745a90 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp @@ -28,7 +28,9 @@ namespace AzToolsFramework::Prefab "Instance Entity Mapper Interface could not be found. " "Check that it is being correctly initialized."); + EditorEntityInfoNotificationBus::Handler::BusConnect(); EditorEntityContextNotificationBus::Handler::BusConnect(); + PrefabPublicNotificationBus::Handler::BusConnect(); AZ::Interface::Register(this); AZ::Interface::Register(this); } @@ -37,10 +39,12 @@ namespace AzToolsFramework::Prefab { AZ::Interface::Unregister(this); AZ::Interface::Unregister(this); + PrefabPublicNotificationBus::Handler::BusDisconnect(); EditorEntityContextNotificationBus::Handler::BusDisconnect(); + EditorEntityInfoNotificationBus::Handler::BusDisconnect(); } - void PrefabFocusHandler::Initialize() + void PrefabFocusHandler::InitializeEditorInterfaces() { m_containerEntityInterface = AZ::Interface::Get(); AZ_Assert( @@ -55,13 +59,6 @@ namespace AzToolsFramework::Prefab "Prefab - PrefabFocusHandler - " "Focus Mode Interface could not be found. " "Check that it is being correctly initialized."); - - m_instanceEntityMapperInterface = AZ::Interface::Get(); - AZ_Assert( - m_instanceEntityMapperInterface, - "Prefab - PrefabFocusHandler - " - "Instance Entity Mapper Interface could not be found. " - "Check that it is being correctly initialized."); } PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId) @@ -90,12 +87,12 @@ namespace AzToolsFramework::Prefab PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index) { - if (index < 0 || index >= m_instanceFocusVector.size()) + if (index < 0 || index >= m_instanceFocusHierarchy.size()) { return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex.")); } - InstanceOptionalReference focusedInstance = m_instanceFocusVector[index]; + InstanceOptionalReference focusedInstance = m_instanceFocusHierarchy[index]; FocusOnOwningPrefab(focusedInstance->get().GetContainerEntityId()); @@ -134,42 +131,38 @@ namespace AzToolsFramework::Prefab return AZ::Failure(AZStd::string("Prefab Focus Handler: invalid instance to focus on.")); } - if (!m_isInitialized) + // Close all container entities in the old path. + CloseInstanceContainers(m_instanceFocusHierarchy); + + m_focusedInstance = focusedInstance; + m_focusedTemplateId = focusedInstance->get().GetTemplateId(); + + AZ::EntityId containerEntityId; + + if (focusedInstance->get().GetParentInstance() != AZStd::nullopt) { - Initialize(); + containerEntityId = focusedInstance->get().GetContainerEntityId(); } - - if (!m_focusedInstance.has_value() || &m_focusedInstance->get() != &focusedInstance->get()) + else { - // Close all container entities in the old path - CloseInstanceContainers(m_instanceFocusVector); - - m_focusedInstance = focusedInstance; - m_focusedTemplateId = focusedInstance->get().GetTemplateId(); - - AZ::EntityId containerEntityId; - - if (focusedInstance->get().GetParentInstance() != AZStd::nullopt) - { - containerEntityId = focusedInstance->get().GetContainerEntityId(); - } - else - { - containerEntityId = AZ::EntityId(); - } + containerEntityId = AZ::EntityId(); + } - // Focus on the descendants of the container entity + // Focus on the descendants of the container entity in the Editor, if the interface is initialized. + if (m_focusModeInterface) + { m_focusModeInterface->SetFocusRoot(containerEntityId); - - // Refresh path variables - RefreshInstanceFocusList(); - - // Open all container entities in the new path - OpenInstanceContainers(m_instanceFocusVector); - - PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged); } + // Refresh path variables. + RefreshInstanceFocusList(); + RefreshInstanceFocusPath(); + + // Open all container entities in the new path. + OpenInstanceContainers(m_instanceFocusHierarchy); + + PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged); + return AZ::Success(); } @@ -220,49 +213,80 @@ namespace AzToolsFramework::Prefab const int PrefabFocusHandler::GetPrefabFocusPathLength([[maybe_unused]] AzFramework::EntityContextId entityContextId) const { - return aznumeric_cast(m_instanceFocusVector.size()); + return aznumeric_cast(m_instanceFocusHierarchy.size()); } - void PrefabFocusHandler::OnEntityStreamLoadSuccess() + void PrefabFocusHandler::OnContextReset() { - if (!m_isInitialized) - { - Initialize(); - } - // Clear the old focus vector - m_instanceFocusVector.clear(); + m_instanceFocusHierarchy.clear(); // Focus on the root prefab (AZ::EntityId() will default to it) FocusOnPrefabInstanceOwningEntityId(AZ::EntityId()); } + void PrefabFocusHandler::OnEntityInfoUpdatedName(AZ::EntityId entityId, [[maybe_unused]]const AZStd::string& name) + { + // Determine if the entityId is the container for any of the instances in the vector + auto result = AZStd::find_if( + m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end(), + [entityId](const InstanceOptionalReference& instance) + { + return (instance->get().GetContainerEntityId() == entityId); + } + ); + + if (result != m_instanceFocusHierarchy.end()) + { + // Refresh the path and notify changes. + RefreshInstanceFocusPath(); + PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged); + } + } + + void PrefabFocusHandler::OnPrefabInstancePropagationEnd() + { + // Refresh the path and notify changes in case propagation updated any container names. + RefreshInstanceFocusPath(); + PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged); + } + void PrefabFocusHandler::RefreshInstanceFocusList() { - m_instanceFocusVector.clear(); - m_instanceFocusPath.clear(); + m_instanceFocusHierarchy.clear(); AZStd::list instanceFocusList; - // Use a support list to easily push front while traversing the prefab hierarchy InstanceOptionalReference currentInstance = m_focusedInstance; while (currentInstance.has_value()) { - instanceFocusList.push_front(currentInstance); + m_instanceFocusHierarchy.emplace_back(currentInstance); currentInstance = currentInstance->get().GetParentInstance(); } - // Populate internals using the support list - for (auto& instance : instanceFocusList) + // Invert the vector, since we need the top instance to be at index 0 + AZStd::reverse(m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end()); + } + + void PrefabFocusHandler::RefreshInstanceFocusPath() + { + m_instanceFocusPath.clear(); + + for (const InstanceOptionalReference& instance : m_instanceFocusHierarchy) { m_instanceFocusPath.Append(instance->get().GetContainerEntity()->get().GetName()); - m_instanceFocusVector.emplace_back(instance); } } void PrefabFocusHandler::OpenInstanceContainers(const AZStd::vector& instances) const { + // If this is called outside the Editor, this interface won't be initialized. + if (!m_containerEntityInterface) + { + return; + } + for (const InstanceOptionalReference& instance : instances) { if (instance.has_value()) @@ -274,6 +298,12 @@ namespace AzToolsFramework::Prefab void PrefabFocusHandler::CloseInstanceContainers(const AZStd::vector& instances) const { + // If this is called outside the Editor, this interface won't be initialized. + if (!m_containerEntityInterface) + { + return; + } + for (const InstanceOptionalReference& instance : instances) { if (instance.has_value()) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h index 80b7a6859c..6a71365d8e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h @@ -11,9 +11,11 @@ #include #include +#include #include #include #include +#include #include namespace AzToolsFramework @@ -30,7 +32,9 @@ namespace AzToolsFramework::Prefab class PrefabFocusHandler final : private PrefabFocusInterface , private PrefabFocusPublicInterface + , private PrefabPublicNotificationBus::Handler , private EditorEntityContextNotificationBus::Handler + , private EditorEntityInfoNotificationBus::Handler { public: AZ_CLASS_ALLOCATOR(PrefabFocusHandler, AZ::SystemAllocator, 0); @@ -38,9 +42,8 @@ namespace AzToolsFramework::Prefab PrefabFocusHandler(); ~PrefabFocusHandler(); - void Initialize(); - // PrefabFocusInterface overrides ... + void InitializeEditorInterfaces() override; PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) override; TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const override; InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const override; @@ -54,25 +57,34 @@ namespace AzToolsFramework::Prefab const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const override; // EditorEntityContextNotificationBus overrides ... - void OnEntityStreamLoadSuccess() override; + void OnContextReset() override; + + // EditorEntityInfoNotificationBus overrides ... + void OnEntityInfoUpdatedName(AZ::EntityId entityId, const AZStd::string& name) override; + + // PrefabPublicNotifications overrides ... + void OnPrefabInstancePropagationEnd(); private: PrefabFocusOperationResult FocusOnPrefabInstance(InstanceOptionalReference focusedInstance); void RefreshInstanceFocusList(); + void RefreshInstanceFocusPath(); void OpenInstanceContainers(const AZStd::vector& instances) const; void CloseInstanceContainers(const AZStd::vector& instances) const; + //! The instance the editor is currently focusing on. InstanceOptionalReference m_focusedInstance; + //! The templateId of the focused instance. TemplateId m_focusedTemplateId; - AZStd::vector m_instanceFocusVector; + //! The list of instances going from the root (index 0) to the focused instance. + AZStd::vector m_instanceFocusHierarchy; + //! A path containing the names of the containers in the instance focus hierarchy, separated with a /. AZ::IO::Path m_instanceFocusPath; ContainerEntityInterface* m_containerEntityInterface = nullptr; FocusModeInterface* m_focusModeInterface = nullptr; InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr; - - bool m_isInitialized = false; }; } // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h index 25c83b89bc..287cbbaf96 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h @@ -26,6 +26,11 @@ namespace AzToolsFramework::Prefab public: AZ_RTTI(PrefabFocusInterface, "{F3CFA37B-5FD8-436A-9C30-60EB54E350E1}"); + //! Initializes the editor interfaces for Prefab Focus mode. + //! If this is not called on initialization, the Prefab Focus Mode functions will still work + //! but won't trigger the Editor APIs to visualize focus mode on the UI. + virtual void InitializeEditorInterfaces() = 0; + //! Set the focused prefab instance to the owning instance of the entityId provided. //! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on. virtual PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) = 0; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index 526912cf29..bef08466ae 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -135,6 +136,10 @@ namespace AzToolsFramework return; } + // Initialize Editor functionality for the Prefab Focus Handler + auto prefabFocusInterface = AZ::Interface::Get(); + prefabFocusInterface->InitializeEditorInterfaces(); + EditorContextMenuBus::Handler::BusConnect(); EditorEventsBus::Handler::BusConnect(); PrefabInstanceContainerNotificationBus::Handler::BusConnect(); From c1948bf94ee057bc63a3ae10a8269391c3f30b0e Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Wed, 20 Oct 2021 15:04:21 -0500 Subject: [PATCH 10/27] Treat invalid p4 configuration as a warning for the scene settings save action so the processing popup will get the job results. Signed-off-by: Chris Galvan --- .../Plugins/EditorCommon/SaveUtilities/AsyncSaveRunner.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Code/Editor/Plugins/EditorCommon/SaveUtilities/AsyncSaveRunner.cpp b/Code/Editor/Plugins/EditorCommon/SaveUtilities/AsyncSaveRunner.cpp index 0b514513ca..d05074c483 100644 --- a/Code/Editor/Plugins/EditorCommon/SaveUtilities/AsyncSaveRunner.cpp +++ b/Code/Editor/Plugins/EditorCommon/SaveUtilities/AsyncSaveRunner.cpp @@ -76,6 +76,7 @@ namespace AZ else if (info.m_status == AzToolsFramework::SourceControlStatus::SCS_ProviderIsDown) { message = "Failed to put entries/dependencies into source control as the provider is not available.\n"; + reportAsWarning = true; } else if (info.m_status == AzToolsFramework::SourceControlStatus::SCS_CertificateInvalid) { From 8e797982a5fe5792b39ec6e80c52cdc905796799 Mon Sep 17 00:00:00 2001 From: Vincent Liu <5900509+onecent1101@users.noreply.github.com> Date: Wed, 20 Oct 2021 13:11:12 -0700 Subject: [PATCH 11/27] [LYN-7530] Fix matchmaking request type typo and add more matchmaking notifications (#4774) * [LYN-7530] Fix matchmaking request type typo and add more matchmaking notifications Signed-off-by: onecent1101 --- .../Matchmaking/IMatchmakingRequests.h | 29 ++++- .../Matchmaking/MatchmakingNotifications.h | 44 +++----- ...s.h => AWSGameLiftMatchmakingRequestBus.h} | 67 +---------- .../Include/Request/AWSGameLiftRequestBus.h | 51 +++++++++ .../Request/AWSGameLiftSessionRequestBus.h | 38 +++++++ .../AWSGameLiftClientLocalTicketTracker.cpp | 16 ++- .../AWSGameLiftClientLocalTicketTracker.h | 2 +- .../Source/AWSGameLiftClientManager.cpp | 9 ++ .../Source/AWSGameLiftClientManager.h | 30 ++++- .../AWSGameLiftClientSystemComponent.cpp | 35 +++--- .../Request/IAWSGameLiftInternalRequests.h | 2 +- ...WSGameLiftClientLocalTicketTrackerTest.cpp | 104 ++++++++++-------- .../Tests/AWSGameLiftClientManagerTest.cpp | 6 + .../Tests/AWSGameLiftClientMocks.h | 39 +++++-- .../awsgamelift_client_files.cmake | 4 +- ...quests.h => AWSGameLiftServerRequestBus.h} | 4 +- .../Source/AWSGameLiftServerManager.h | 2 +- .../awsgamelift_server_files.cmake | 2 +- 18 files changed, 303 insertions(+), 181 deletions(-) rename Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/{IAWSGameLiftRequests.h => AWSGameLiftMatchmakingRequestBus.h} (53%) create mode 100644 Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftRequestBus.h create mode 100644 Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSessionRequestBus.h rename Gems/AWSGameLift/Code/AWSGameLiftServer/Include/Request/{IAWSGameLiftServerRequests.h => AWSGameLiftServerRequestBus.h} (97%) diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h b/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h index 22b65f8340..c657e0edc7 100644 --- a/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h @@ -43,7 +43,7 @@ namespace AzFramework class IMatchmakingAsyncRequests { public: - AZ_RTTI(ISessionAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}"); + AZ_RTTI(IMatchmakingAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}"); IMatchmakingAsyncRequests() = default; virtual ~IMatchmakingAsyncRequests() = default; @@ -60,4 +60,31 @@ namespace AzFramework // @param stopMatchmakingRequest The request of StopMatchmaking operation virtual void StopMatchmakingAsync(const StopMatchmakingRequest& stopMatchmakingRequest) = 0; }; + + //! MatchmakingAsyncRequestNotifications + //! The notifications correspond to matchmaking async requests + class MatchmakingAsyncRequestNotifications + : public AZ::EBusTraits + { + public: + // Safeguard handler for multi-threaded use case + using MutexType = AZStd::recursive_mutex; + + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + + // OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes + virtual void OnAcceptMatchAsyncComplete() = 0; + + // OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes + // @param matchmakingTicketId The unique identifier for the matchmaking ticket + virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0; + + // OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes + virtual void OnStopMatchmakingAsyncComplete() = 0; + }; + using MatchmakingAsyncRequestNotificationBus = AZ::EBus; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h index ad61971a11..aa19b94b4a 100644 --- a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h @@ -13,36 +13,10 @@ namespace AzFramework { - //! MatchmakingAsyncRequestNotifications - //! The notifications correspond to matchmaking async requests - class MatchmakingAsyncRequestNotifications - : public AZ::EBusTraits - { - public: - // Safeguard handler for multi-threaded use case - using MutexType = AZStd::recursive_mutex; - - ////////////////////////////////////////////////////////////////////////// - // EBusTraits overrides - static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; - static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; - ////////////////////////////////////////////////////////////////////////// - - // OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes - virtual void OnAcceptMatchAsyncComplete() = 0; - - // OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes - // @param matchmakingTicketId The unique identifier for the matchmaking ticket - virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0; - - // OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes - virtual void OnStopMatchmakingAsyncComplete() = 0; - }; - using MatchmakingAsyncRequestNotificationBus = AZ::EBus; - //! MatchmakingNotifications //! The matchmaking notifications to listen for performing required operations - class MatchAcceptanceNotifications + //! based on matchmaking ticket event + class MatchmakingNotifications : public AZ::EBusTraits { public: @@ -55,8 +29,18 @@ namespace AzFramework static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; ////////////////////////////////////////////////////////////////////////// - // OnMatchAcceptance is fired when DescribeMatchmaking ticket status is REQUIRES_ACCEPTANCE + // OnMatchAcceptance is fired when match is found and pending on acceptance + // Use this notification to accept found match virtual void OnMatchAcceptance() = 0; + + // OnMatchComplete is fired when match is complete + virtual void OnMatchComplete() = 0; + + // OnMatchError is fired when match is processed with error + virtual void OnMatchError() = 0; + + // OnMatchFailure is fired when match is failed to complete + virtual void OnMatchFailure() = 0; }; - using MatchAcceptanceNotificationBus = AZ::EBus; + using MatchmakingNotificationBus = AZ::EBus; } // namespace AzFramework diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/IAWSGameLiftRequests.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftMatchmakingRequestBus.h similarity index 53% rename from Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/IAWSGameLiftRequests.h rename to Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftMatchmakingRequestBus.h index c14ef559b2..8ab215d741 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/IAWSGameLiftRequests.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftMatchmakingRequestBus.h @@ -5,75 +5,16 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ - + #pragma once #include -#include #include #include -#include namespace AWSGameLift { - //! IAWSGameLiftRequests - //! GameLift Gem interfaces to configure client manager - class IAWSGameLiftRequests - { - public: - AZ_RTTI(IAWSGameLiftRequests, "{494167AD-1185-4AF3-8BF9-C8C37FC9C199}"); - - IAWSGameLiftRequests() = default; - virtual ~IAWSGameLiftRequests() = default; - - //! ConfigureGameLiftClient - //! Configure GameLift client to interact with Amazon GameLift service - //! @param region Specifies the AWS region to use - //! @return True if client configuration succeeds, false otherwise - virtual bool ConfigureGameLiftClient(const AZStd::string& region) = 0; - - //! CreatePlayerId - //! Create a new, random ID number for every player in every new game session. - //! @param includeBrackets Whether includes brackets in player id - //! @param includeDashes Whether includes dashes in player id - //! @return The player id to use in game session - virtual AZStd::string CreatePlayerId(bool includeBrackets, bool includeDashes) = 0; - }; - - // IAWSGameLiftRequests EBus wrapper for scripting - class AWSGameLiftRequests - : public AZ::EBusTraits - { - public: - using MutexType = AZStd::recursive_mutex; - static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; - static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; - }; - using AWSGameLiftRequestBus = AZ::EBus; - - // ISessionAsyncRequests EBus wrapper for scripting - class AWSGameLiftSessionAsyncRequests - : public AZ::EBusTraits - { - public: - using MutexType = AZStd::recursive_mutex; - static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; - static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; - }; - using AWSGameLiftSessionAsyncRequestBus = AZ::EBus; - - // ISessionRequests EBus wrapper for scripting - class AWSGameLiftSessionRequests - : public AZ::EBusTraits - { - public: - using MutexType = AZStd::recursive_mutex; - static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; - static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; - }; - using AWSGameLiftSessionRequestBus = AZ::EBus; - - // IMatchmakingAsyncRequests EBus wrapper for scripting + // IMatchmakingAsyncRequests EBus wrapper class AWSGameLiftMatchmakingAsyncRequests : public AZ::EBusTraits { @@ -84,7 +25,7 @@ namespace AWSGameLift }; using AWSGameLiftMatchmakingAsyncRequestBus = AZ::EBus; - // IMatchmakingRequests EBus wrapper for scripting + // IMatchmakingRequests EBus wrapper class AWSGameLiftMatchmakingRequests : public AZ::EBusTraits { @@ -121,7 +62,7 @@ namespace AWSGameLift virtual void StopPolling() = 0; }; - // IAWSGameLiftMatchmakingEventRequests EBus wrapper for scripting + // IAWSGameLiftMatchmakingEventRequests EBus wrapper class AWSGameLiftMatchmakingEventRequests : public AZ::EBusTraits { diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftRequestBus.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftRequestBus.h new file mode 100644 index 0000000000..e1951d1e61 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftRequestBus.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +namespace AWSGameLift +{ + //! IAWSGameLiftRequests + //! GameLift Gem interfaces to configure GameLift client and other help functions, + //! like creating random GameLift player id + class IAWSGameLiftRequests + { + public: + AZ_RTTI(IAWSGameLiftRequests, "{494167AD-1185-4AF3-8BF9-C8C37FC9C199}"); + + IAWSGameLiftRequests() = default; + virtual ~IAWSGameLiftRequests() = default; + + //! ConfigureGameLiftClient + //! Configure GameLift client to interact with Amazon GameLift service + //! @param region Specifies the AWS region to use + //! @return True if client configuration succeeds, false otherwise + virtual bool ConfigureGameLiftClient(const AZStd::string& region) = 0; + + //! CreatePlayerId + //! Create a new, random ID number for every player in every new game session. + //! @param includeBrackets Whether includes brackets in player id + //! @param includeDashes Whether includes dashes in player id + //! @return The player id to use in game session + virtual AZStd::string CreatePlayerId(bool includeBrackets, bool includeDashes) = 0; + }; + + // IAWSGameLiftRequests EBus wrapper + class AWSGameLiftRequests + : public AZ::EBusTraits + { + public: + using MutexType = AZStd::recursive_mutex; + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + }; + using AWSGameLiftRequestBus = AZ::EBus; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSessionRequestBus.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSessionRequestBus.h new file mode 100644 index 0000000000..c99509ca3f --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSessionRequestBus.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +namespace AWSGameLift +{ + // ISessionAsyncRequests EBus wrapper + class AWSGameLiftSessionAsyncRequests + : public AZ::EBusTraits + { + public: + using MutexType = AZStd::recursive_mutex; + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + }; + using AWSGameLiftSessionAsyncRequestBus = AZ::EBus; + + // ISessionRequests EBus wrapper + class AWSGameLiftSessionRequests + : public AZ::EBusTraits + { + public: + using MutexType = AZStd::recursive_mutex; + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + }; + using AWSGameLiftSessionRequestBus = AZ::EBus; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.cpp index cc2f84cb35..2e978402cd 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.cpp +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.cpp @@ -97,6 +97,7 @@ namespace AWSGameLift AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, "Matchmaking ticket %s is complete.", ticket.GetTicketId().c_str()); RequestPlayerJoinMatch(ticket, playerId); + AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchComplete); m_status = TicketTrackerStatus::Idle; return; } @@ -104,25 +105,28 @@ namespace AWSGameLift ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::FAILED || ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::CANCELLED) { - AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, "Matchmaking ticket %s is not complete, %s", - ticket.GetTicketId().c_str(), ticket.GetStatusReason().c_str()); + AZ_Warning(AWSGameLiftClientLocalTicketTrackerName, false, "Matchmaking ticket %s is not complete, %s", + ticket.GetTicketId().c_str(), ticket.GetStatusMessage().c_str()); + AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchFailure); m_status = TicketTrackerStatus::Idle; return; } else if (ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::REQUIRES_ACCEPTANCE) { - // broadcast acceptance requires to player - AzFramework::MatchAcceptanceNotificationBus::Broadcast(&AzFramework::MatchAcceptanceNotifications::OnMatchAcceptance); + AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, "Matchmaking ticket %s is pending on acceptance, %s.", + ticket.GetTicketId().c_str(), ticket.GetStatusMessage().c_str()); + AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchAcceptance); } else { AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, "Matchmaking ticket %s is processing, %s.", - ticket.GetTicketId().c_str(), ticket.GetStatusReason().c_str()); + ticket.GetTicketId().c_str(), ticket.GetStatusMessage().c_str()); } } else { AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, "Unable to find expected ticket with id %s", ticketId.c_str()); + AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchError); } } else @@ -130,11 +134,13 @@ namespace AWSGameLift AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, AWSGameLiftErrorMessageTemplate, describeMatchmakingOutcome.GetError().GetExceptionName().c_str(), describeMatchmakingOutcome.GetError().GetMessage().c_str()); + AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchError); } } else { AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, AWSGameLiftClientMissingErrorMessage); + AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchError); } m_waitEvent.try_acquire_for(AZStd::chrono::milliseconds(m_pollingPeriodInMS)); } diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.h index 04bdd71c85..9fd7f76e1d 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.h @@ -12,7 +12,7 @@ #include #include -#include +#include #include diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.cpp index 224f16481e..4ee2d31ebf 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.cpp +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -75,7 +76,15 @@ namespace AWSGameLift bool AWSGameLiftClientManager::ConfigureGameLiftClient(const AZStd::string& region) { AZ::Interface::Get()->SetGameLiftClient(nullptr); + Aws::Client::ClientConfiguration clientConfig; + AWSCore::AwsApiJobConfig* defaultConfig = nullptr; + AWSCore::AWSCoreRequestBus::BroadcastResult(defaultConfig, &AWSCore::AWSCoreRequests::GetDefaultConfig); + if (defaultConfig) + { + clientConfig = defaultConfig->GetClientConfiguration(); + } + // Set up client endpoint or region AZStd::string localEndpoint = ""; #if defined(AWSGAMELIFT_DEV) diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.h index 1f32b69f75..8a0c91c36d 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.h @@ -8,10 +8,13 @@ #pragma once +#include #include #include -#include +#include +#include +#include namespace AWSGameLift { @@ -23,22 +26,37 @@ namespace AWSGameLift struct AWSGameLiftStartMatchmakingRequest; struct AWSGameLiftStopMatchmakingRequest; - // MatchAcceptanceNotificationBus EBus handler for scripting - class AWSGameLiftMatchAcceptanceNotificationBusHandler - : public AzFramework::MatchAcceptanceNotificationBus::Handler + // MatchmakingNotificationBus EBus handler for scripting + class AWSGameLiftMatchmakingNotificationBusHandler + : public AzFramework::MatchmakingNotificationBus::Handler , public AZ::BehaviorEBusHandler { public: AZ_EBUS_BEHAVIOR_BINDER( - AWSGameLiftMatchAcceptanceNotificationBusHandler, + AWSGameLiftMatchmakingNotificationBusHandler, "{CBE057D3-F5CE-46D3-B02D-8A6A1446B169}", AZ::SystemAllocator, - OnMatchAcceptance); + OnMatchAcceptance, OnMatchComplete, OnMatchError, OnMatchFailure); void OnMatchAcceptance() override { Call(FN_OnMatchAcceptance); } + + void OnMatchComplete() override + { + Call(FN_OnMatchComplete); + } + + void OnMatchError() override + { + Call(FN_OnMatchError); + } + + void OnMatchFailure() override + { + Call(FN_OnMatchFailure); + } }; // MatchmakingAsyncRequestNotificationBus EBus handler for scripting diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.cpp index 981c1599c9..d256c6c33c 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.cpp +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.cpp @@ -65,13 +65,6 @@ namespace AWSGameLift ->Event("CreatePlayerId", &AWSGameLiftRequestBus::Events::CreatePlayerId, { { { "IncludeBrackets", "" }, { "IncludeDashes", "" } } }); - - behaviorContext->EBus("AWSGameLiftMatchmakingEventRequestBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") - ->Event("StartPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StartPolling, - { { { "TicketId", "" }, - { "PlayerId", "" } } }) - ->Event("StopPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StopPolling); } } @@ -128,7 +121,7 @@ namespace AWSGameLift if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { behaviorContext->EBus("AWSGameLiftMatchmakingAsyncRequestBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking") ->Event("AcceptMatchAsync", &AWSGameLiftMatchmakingAsyncRequestBus::Events::AcceptMatchAsync, { { { "AcceptMatchRequest", "" } } }) ->Event("StartMatchmakingAsync", &AWSGameLiftMatchmakingAsyncRequestBus::Events::StartMatchmakingAsync, @@ -137,20 +130,28 @@ namespace AWSGameLift { { { "StopMatchmakingRequest", "" } } }); behaviorContext->EBus("AWSGameLiftMatchmakingAsyncRequestNotificationBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking") ->Handler(); behaviorContext->EBus("AWSGameLiftMatchmakingRequestBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") - ->Event("AcceptMatch", &AWSGameLiftMatchmakingRequestBus::Events::AcceptMatch, { { { "AcceptMatchRequest", "" } } }) + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking") + ->Event("AcceptMatch", &AWSGameLiftMatchmakingRequestBus::Events::AcceptMatch, + { { { "AcceptMatchRequest", "" } } }) ->Event("StartMatchmaking", &AWSGameLiftMatchmakingRequestBus::Events::StartMatchmaking, { { { "StartMatchmakingRequest", "" } } }) ->Event("StopMatchmaking", &AWSGameLiftMatchmakingRequestBus::Events::StopMatchmaking, { { { "StopMatchmakingRequest", "" } } }); - behaviorContext->EBus("AWSGameLiftMatchAcceptanceNotificationBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") - ->Handler(); + behaviorContext->EBus("AWSGameLiftMatchmakingEventRequestBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking") + ->Event("StartPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StartPolling, + { { { "TicketId", "" }, + { "PlayerId", "" } } }) + ->Event("StopPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StopPolling); + + behaviorContext->EBus("AWSGameLiftMatchmakingNotificationBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking") + ->Handler(); } } @@ -166,7 +167,7 @@ namespace AWSGameLift if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { behaviorContext->EBus("AWSGameLiftSessionAsyncRequestBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Session") ->Event("CreateSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::CreateSessionAsync, { { { "CreateSessionRequest", "" } } }) ->Event("JoinSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::JoinSessionAsync, { { { "JoinSessionRequest", "" } } }) @@ -175,11 +176,11 @@ namespace AWSGameLift ->Event("LeaveSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::LeaveSessionAsync); behaviorContext->EBus("AWSGameLiftSessionAsyncRequestNotificationBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Session") ->Handler(); behaviorContext->EBus("AWSGameLiftSessionRequestBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Session") ->Event("CreateSession", &AWSGameLiftSessionRequestBus::Events::CreateSession, { { { "CreateSessionRequest", "" } } }) ->Event("JoinSession", &AWSGameLiftSessionRequestBus::Events::JoinSession, { { { "JoinSessionRequest", "" } } }) ->Event("SearchSessions", &AWSGameLiftSessionRequestBus::Events::SearchSessions, { { { "SearchSessionsRequest", "" } } }) diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/IAWSGameLiftInternalRequests.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/IAWSGameLiftInternalRequests.h index dbac9a798a..7e17f085b3 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/IAWSGameLiftInternalRequests.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/IAWSGameLiftInternalRequests.h @@ -20,7 +20,7 @@ namespace Aws namespace AWSGameLift { - //! IAWSGameLiftRequests + //! IAWSGameLiftInternalRequests //! GameLift Gem internal interface which is used to fetch gem global GameLift client class IAWSGameLiftInternalRequests { diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientLocalTicketTrackerTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientLocalTicketTrackerTest.cpp index 4dc4dd85f6..be72de555e 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientLocalTicketTrackerTest.cpp +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientLocalTicketTrackerTest.cpp @@ -82,27 +82,12 @@ protected: m_gameliftClientMockPtr.reset(); } - void WaitForProcessFinish(uint64_t expectedNum) + void WaitForProcessFinish(AZStd::function processFinishCondition) { int processingTime = 0; while (processingTime < TEST_WAIT_MAXIMUM_TIME_MS) { - if (::UnitTest::TestRunner::Instance().m_numAssertsFailed == expectedNum) - { - AZ_TEST_STOP_TRACE_SUPPRESSION(expectedNum); - return; - } - AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(TEST_WAIT_BUFFER_TIME_MS)); - processingTime += TEST_WAIT_BUFFER_TIME_MS; - } - } - - void WaitForProcessFinish() - { - int processingTime = 0; - while (processingTime < TEST_WAIT_MAXIMUM_TIME_MS) - { - if (m_gameliftClientTicketTracker->IsTrackerIdle()) + if (processFinishCondition()) { return; } @@ -119,19 +104,27 @@ public: TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithoutClientSetup_GetExpectedErrors) { AZ::Interface::Get()->SetGameLiftClient(nullptr); + + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; AZ_TEST_START_TRACE_SUPPRESSION; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; }); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1); ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); } TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_MultipleCallsWithoutClientSetup_GetExpectedErrors) { AZ::Interface::Get()->SetGameLiftClient(nullptr); + + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; AZ_TEST_START_TRACE_SUPPRESSION; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; }); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1); ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -144,9 +137,12 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButWithFailedOu .Times(1) .WillOnce(::testing::Return(outcome)); + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; AZ_TEST_START_TRACE_SUPPRESSION; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; }); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1); ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -161,9 +157,12 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithMoreThanOne .Times(1) .WillOnce(::testing::Return(outcome)); + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; AZ_TEST_START_TRACE_SUPPRESSION; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; }); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1); ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -189,13 +188,15 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithCompleteSta .Times(1) .WillOnce(::testing::Return(outcome)); - SessionHandlingClientRequestsMock handlerMock; - EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_)) + SessionHandlingClientRequestsMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, RequestPlayerJoinSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(true)); + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(); + WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); }); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -217,9 +218,12 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButNoPlayerSess .Times(1) .WillOnce(::testing::Return(outcome)); + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; AZ_TEST_START_TRACE_SUPPRESSION; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); }); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -245,14 +249,17 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButFailedToJoin .Times(1) .WillOnce(::testing::Return(outcome)); - SessionHandlingClientRequestsMock handlerMock; - EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_)) + SessionHandlingClientRequestsMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, RequestPlayerJoinSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(false)); + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; AZ_TEST_START_TRACE_SUPPRESSION; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); }); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -269,9 +276,10 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButTicketTimeOu .Times(1) .WillOnce(::testing::Return(outcome)); - AZ_TEST_START_TRACE_SUPPRESSION; + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); }); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchFailure == 1); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -288,9 +296,10 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButTicketFailed .Times(1) .WillOnce(::testing::Return(outcome)); - AZ_TEST_START_TRACE_SUPPRESSION; + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); }); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchFailure == 1); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -307,9 +316,10 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButTicketCancel .Times(1) .WillOnce(::testing::Return(outcome)); - AZ_TEST_START_TRACE_SUPPRESSION; + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); }); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchFailure == 1); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -342,13 +352,15 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallAndTicketComple .WillOnce(::testing::Return(outcome1)) .WillOnce(::testing::Return(outcome2)); - SessionHandlingClientRequestsMock handlerMock; - EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_)) + SessionHandlingClientRequestsMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, RequestPlayerJoinSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(true)); + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(); + WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); }); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -365,7 +377,9 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_RequiresAcceptanceA connectionInfo.SetIpAddress("DummyIpAddress"); connectionInfo.SetPort(123); connectionInfo.AddMatchedPlayerSessions( - Aws::GameLift::Model::MatchedPlayerSession().WithPlayerId("player1").WithPlayerSessionId("playersession1")); + Aws::GameLift::Model::MatchedPlayerSession() + .WithPlayerId("player1") + .WithPlayerSessionId("playersession1")); Aws::GameLift::Model::MatchmakingTicket ticket2; ticket2.SetStatus(Aws::GameLift::Model::MatchmakingConfigurationStatus::COMPLETED); @@ -379,13 +393,15 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_RequiresAcceptanceA .WillOnce(::testing::Return(outcome1)) .WillOnce(::testing::Return(outcome2)); - MatchAcceptanceNotificationsHandlerMock handlerMock1; - EXPECT_CALL(handlerMock1, OnMatchAcceptance()).Times(1); - - SessionHandlingClientRequestsMock handlerMock2; - EXPECT_CALL(handlerMock2, RequestPlayerJoinSession(::testing::_)).Times(1).WillOnce(::testing::Return(true)); + SessionHandlingClientRequestsMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, RequestPlayerJoinSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(true)); + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(); + WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); }); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchAcceptance == 1); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); } diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientManagerTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientManagerTest.cpp index 1c3e8726fd..6ce0cb8f64 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientManagerTest.cpp +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientManagerTest.cpp @@ -266,6 +266,8 @@ const char* const AWSGameLiftClientManagerTest::DummyPlayerId = "dummyPlayerId"; TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutRegion_GetFalseAsResult) { + AWSCoreRequestsHandlerMock coreHandlerMock; + EXPECT_CALL(coreHandlerMock, GetDefaultConfig()).Times(1).WillOnce(nullptr); AZ_TEST_START_TRACE_SUPPRESSION; auto result = m_gameliftClientManager->ConfigureGameLiftClient(""); AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message @@ -274,6 +276,8 @@ TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutRegion_G TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutCredential_GetFalseAsResult) { + AWSCoreRequestsHandlerMock coreHandlerMock; + EXPECT_CALL(coreHandlerMock, GetDefaultConfig()).Times(1).WillOnce(nullptr); AWSResourceMappingRequestsHandlerMock handlerMock; EXPECT_CALL(handlerMock, GetDefaultRegion()).Times(1).WillOnce(::testing::Return("us-west-2")); AZ_TEST_START_TRACE_SUPPRESSION; @@ -284,6 +288,8 @@ TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutCredenti TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithRegionAndCredential_GetTrueAsResult) { + AWSCoreRequestsHandlerMock coreHandlerMock; + EXPECT_CALL(coreHandlerMock, GetDefaultConfig()).Times(1).WillOnce(nullptr); AWSCredentialRequestsHandlerMock handlerMock; EXPECT_CALL(handlerMock, GetCredentialsProvider()) .Times(1) diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientMocks.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientMocks.h index 01afec5c3f..d685f61d30 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientMocks.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientMocks.h @@ -9,9 +9,9 @@ #pragma once #include -#include #include #include +#include #include #include @@ -76,21 +76,44 @@ public: MOCK_METHOD0(OnStopMatchmakingAsyncComplete, void()); }; -class MatchAcceptanceNotificationsHandlerMock - : public AzFramework::MatchAcceptanceNotificationBus::Handler +class MatchmakingNotificationsHandlerMock + : public AzFramework::MatchmakingNotificationBus::Handler { public: - MatchAcceptanceNotificationsHandlerMock() + MatchmakingNotificationsHandlerMock() { - AzFramework::MatchAcceptanceNotificationBus::Handler::BusConnect(); + AzFramework::MatchmakingNotificationBus::Handler::BusConnect(); } - ~MatchAcceptanceNotificationsHandlerMock() + ~MatchmakingNotificationsHandlerMock() { - AzFramework::MatchAcceptanceNotificationBus::Handler::BusDisconnect(); + AzFramework::MatchmakingNotificationBus::Handler::BusDisconnect(); } - MOCK_METHOD0(OnMatchAcceptance, void()); + void OnMatchAcceptance() override + { + ++m_numMatchAcceptance; + } + + void OnMatchComplete() override + { + ++m_numMatchComplete; + } + + void OnMatchError() override + { + ++m_numMatchError; + } + + void OnMatchFailure() override + { + ++m_numMatchFailure; + } + + int m_numMatchAcceptance = 0; + int m_numMatchComplete = 0; + int m_numMatchError = 0; + int m_numMatchFailure = 0; }; class SessionAsyncRequestNotificationsHandlerMock diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_files.cmake b/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_files.cmake index 629d1596cf..fe22e0c65c 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_files.cmake +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_files.cmake @@ -17,7 +17,9 @@ set(FILES Include/Request/AWSGameLiftSearchSessionsRequest.h Include/Request/AWSGameLiftStartMatchmakingRequest.h Include/Request/AWSGameLiftStopMatchmakingRequest.h - Include/Request/IAWSGameLiftRequests.h + Include/Request/AWSGameLiftRequestBus.h + Include/Request/AWSGameLiftSessionRequestBus.h + Include/Request/AWSGameLiftMatchmakingRequestBus.h Source/Activity/AWSGameLiftActivityUtils.cpp Source/Activity/AWSGameLiftActivityUtils.h Source/Activity/AWSGameLiftAcceptMatchActivity.cpp diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/Include/Request/IAWSGameLiftServerRequests.h b/Gems/AWSGameLift/Code/AWSGameLiftServer/Include/Request/AWSGameLiftServerRequestBus.h similarity index 97% rename from Gems/AWSGameLift/Code/AWSGameLiftServer/Include/Request/IAWSGameLiftServerRequests.h rename to Gems/AWSGameLift/Code/AWSGameLiftServer/Include/Request/AWSGameLiftServerRequestBus.h index 777086e633..27096b9fe8 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftServer/Include/Request/IAWSGameLiftServerRequests.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/Include/Request/AWSGameLiftServerRequestBus.h @@ -5,7 +5,7 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ - + #pragma once #include @@ -45,7 +45,7 @@ namespace AWSGameLift virtual bool StopMatchBackfill(const AZStd::string& ticketId) = 0; }; - // IAWSGameLiftServerRequests EBus wrapper for scripting + // IAWSGameLiftServerRequests EBus wrapper class AWSGameLiftServerRequests : public AZ::EBusTraits { diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerManager.h b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerManager.h index ee21751fe9..6e7ce4e005 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerManager.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerManager.h @@ -20,7 +20,7 @@ #include #include -#include +#include namespace AWSGameLift { diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_files.cmake b/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_files.cmake index 9039c9943e..70dfd38fc3 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_files.cmake +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_files.cmake @@ -10,7 +10,7 @@ set(FILES ../AWSGameLiftCommon/Include/AWSGameLiftPlayer.h ../AWSGameLiftCommon/Source/AWSGameLiftPlayer.cpp ../AWSGameLiftCommon/Source/AWSGameLiftSessionConstants.h - Include/Request/IAWSGameLiftServerRequests.h + Include/Request/AWSGameLiftServerRequestBus.h Source/AWSGameLiftServerManager.cpp Source/AWSGameLiftServerManager.h Source/AWSGameLiftServerSystemComponent.cpp From 6566ff1fcb9b7e96a42706baf62a2aa7815087f0 Mon Sep 17 00:00:00 2001 From: Guthrie Adams Date: Wed, 20 Oct 2021 16:22:34 -0500 Subject: [PATCH 12/27] Fixing material system component mac release build Signed-off-by: Guthrie Adams --- .../Code/Source/Material/EditorMaterialSystemComponent.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialSystemComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialSystemComponent.cpp index 746b1ff7e5..e53010d747 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialSystemComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialSystemComponent.cpp @@ -194,6 +194,8 @@ namespace AZ AZ::RPI::AssetUtils::GetAssetIdForProductPath(DefaultLightingPresetPath), propertyOverrides), [entityId, materialAssignmentId]() { + AZ_UNUSED(entityId); + AZ_UNUSED(materialAssignmentId); AZ_Warning( "EditorMaterialSystemComponent", false, "RenderMaterialPreview capture failed for entity %s slot %s.", entityId.ToString().c_str(), materialAssignmentId.ToString().c_str()); From 27a535eaf472d7c891504af963b1c4460d59086a Mon Sep 17 00:00:00 2001 From: Steve Pham <82231385+spham-amzn@users.noreply.github.com> Date: Wed, 20 Oct 2021 14:34:30 -0700 Subject: [PATCH 13/27] Linux Fixes for Launching the Material Editor (#4808) - Prevent P4 thread to run if we cannot detect the P4 command to begin with - Add a trait to disable calling the parent ComponentApplication::Destroy(), instead calling _exit() to skip the module unloading on exit Signed-off-by: Steve Pham --- .../SourceControl/PerforceComponent.cpp | 23 +++++++++++++++---- .../SourceControl/PerforceComponent.h | 2 ++ .../AtomToolsFramework/Code/CMakeLists.txt | 4 ++++ .../Application/AtomToolsApplication.cpp | 4 ++++ .../Linux/AtomToolsFramework_Traits_Linux.h | 15 ++++++++++++ .../AtomToolsFramework_Traits_Platform.h | 10 ++++++++ .../Platform/Linux/platform_linux_files.cmake | 12 ++++++++++ .../Mac/AtomToolsFramework_Traits_Mac.h | 15 ++++++++++++ .../Mac/AtomToolsFramework_Traits_Platform.h | 10 ++++++++ .../Platform/Mac/platform_mac_files.cmake | 12 ++++++++++ .../AtomToolsFramework_Traits_Platform.h | 10 ++++++++ .../AtomToolsFramework_Traits_Windows.h | 15 ++++++++++++ .../Windows/platform_windows_files.cmake | 12 ++++++++++ 13 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Linux/AtomToolsFramework_Traits_Linux.h create mode 100644 Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Linux/AtomToolsFramework_Traits_Platform.h create mode 100644 Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Linux/platform_linux_files.cmake create mode 100644 Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Mac/AtomToolsFramework_Traits_Mac.h create mode 100644 Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Mac/AtomToolsFramework_Traits_Platform.h create mode 100644 Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Mac/platform_mac_files.cmake create mode 100644 Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Windows/AtomToolsFramework_Traits_Platform.h create mode 100644 Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Windows/AtomToolsFramework_Traits_Windows.h create mode 100644 Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Windows/platform_windows_files.cmake diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.cpp index 3bd12b1ad3..acb2cd4b1c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.cpp @@ -20,6 +20,8 @@ #include #include +#include + namespace AzToolsFramework { namespace @@ -75,9 +77,17 @@ namespace AzToolsFramework m_resolveKey = true; m_testTrust = false; + // set up signals before we start thread. m_shutdownThreadSignal = false; - m_WorkerThread = AZStd::thread(AZStd::bind(&PerforceComponent::ThreadWorker, this)); + + // Check to see if the 'p4' command is available at the command line + int p4VersionExitCode = QProcess::execute("p4", QStringList{ "-V" }); + m_p4ApplicationDetected = (p4VersionExitCode == 0); + if (m_p4ApplicationDetected) + { + m_WorkerThread = AZStd::thread(AZStd::bind(&PerforceComponent::ThreadWorker, this)); + } SourceControlConnectionRequestBus::Handler::BusConnect(); SourceControlCommandBus::Handler::BusConnect(); @@ -88,10 +98,13 @@ namespace AzToolsFramework SourceControlCommandBus::Handler::BusDisconnect(); SourceControlConnectionRequestBus::Handler::BusDisconnect(); - m_shutdownThreadSignal = true; // tell the thread to die. - m_WorkerSemaphore.release(1); // wake up the thread so that it sees the signal - m_WorkerThread.join(); // wait for the thread to finish. - m_WorkerThread = AZStd::thread(); + if (m_p4ApplicationDetected) + { + m_shutdownThreadSignal = true; // tell the thread to die. + m_WorkerSemaphore.release(1); // wake up the thread so that it sees the signal + m_WorkerThread.join(); // wait for the thread to finish. + m_WorkerThread = AZStd::thread(); + } SetConnection(nullptr); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.h index 858d9c0103..e5ccf7ad29 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.h @@ -260,5 +260,7 @@ namespace AzToolsFramework AZStd::atomic_bool m_validConnection; SourceControlState m_connectionState; + + bool m_p4ApplicationDetected { false }; }; } // namespace AzToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/CMakeLists.txt b/Gems/Atom/Tools/AtomToolsFramework/Code/CMakeLists.txt index ea5b65be7c..e64c9b80e7 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/CMakeLists.txt +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/CMakeLists.txt @@ -10,6 +10,8 @@ if(NOT PAL_TRAIT_BUILD_HOST_TOOLS) return() endif() +ly_get_list_relative_pal_filename(pal_source_dir ${CMAKE_CURRENT_LIST_DIR}/Source/Platform/${PAL_PLATFORM_NAME}) + ly_add_target( NAME AtomToolsFramework.Static STATIC NAMESPACE Gem @@ -18,9 +20,11 @@ ly_add_target( AUTORCC FILES_CMAKE atomtoolsframework_files.cmake + ${pal_source_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake INCLUDE_DIRECTORIES PRIVATE Source + ${pal_source_dir} PUBLIC Include BUILD_DEPENDENCIES diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp index 3fae2ee7a3..58b07d8351 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp @@ -216,7 +216,11 @@ namespace AtomToolsFramework AtomToolsMainWindowNotificationBus::Handler::BusDisconnect(); AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::StartDisconnectingAssetProcessor); +#if AZ_TRAIT_ATOMTOOLSFRAMEWORK_SKIP_APP_DESTROY + _exit(0); +#else Base::Destroy(); +#endif } AZStd::vector AtomToolsApplication::GetCriticalAssetFilters() const diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Linux/AtomToolsFramework_Traits_Linux.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Linux/AtomToolsFramework_Traits_Linux.h new file mode 100644 index 0000000000..2f4c787fbe --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Linux/AtomToolsFramework_Traits_Linux.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +// On some platforms, there is an issue with environment variables that are removed before some objects are deallocated (during the process of +// ComponentApplication::Destroy). Until all of the shutdown issues are solved, the following trait will skip the parent ::Destroy() and exit +// the application as soon as possible if set to true. +// (Tracked by GHI - 4806) +#define AZ_TRAIT_ATOMTOOLSFRAMEWORK_SKIP_APP_DESTROY true + diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Linux/AtomToolsFramework_Traits_Platform.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Linux/AtomToolsFramework_Traits_Platform.h new file mode 100644 index 0000000000..8101a49a5a --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Linux/AtomToolsFramework_Traits_Platform.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Linux/platform_linux_files.cmake b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Linux/platform_linux_files.cmake new file mode 100644 index 0000000000..957ef8663e --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Linux/platform_linux_files.cmake @@ -0,0 +1,12 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + AtomToolsFramework_Traits_Platform.h + AtomToolsFramework_Traits_Linux.h +) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Mac/AtomToolsFramework_Traits_Mac.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Mac/AtomToolsFramework_Traits_Mac.h new file mode 100644 index 0000000000..3ca246c797 --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Mac/AtomToolsFramework_Traits_Mac.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +// On some platforms, there is an issue with environment variables that are removed before some objects are deallocated (during the process of +// ComponentApplication::Destroy). Until all of the shutdown issues are solved, the following trait will skip the parent ::Destroy() and exit +// the application as soon as possible if set to true. +// (Tracked by GHI - 4806) +#define AZ_TRAIT_ATOMTOOLSFRAMEWORK_SKIP_APP_DESTROY false + diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Mac/AtomToolsFramework_Traits_Platform.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Mac/AtomToolsFramework_Traits_Platform.h new file mode 100644 index 0000000000..6d8c8d7e32 --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Mac/AtomToolsFramework_Traits_Platform.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Mac/platform_mac_files.cmake b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Mac/platform_mac_files.cmake new file mode 100644 index 0000000000..13cc6886ef --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Mac/platform_mac_files.cmake @@ -0,0 +1,12 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + AtomToolsFramework_Traits_Platform.h + AtomToolsFramework_Traits_Mac.h +) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Windows/AtomToolsFramework_Traits_Platform.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Windows/AtomToolsFramework_Traits_Platform.h new file mode 100644 index 0000000000..ac82be7874 --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Windows/AtomToolsFramework_Traits_Platform.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Windows/AtomToolsFramework_Traits_Windows.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Windows/AtomToolsFramework_Traits_Windows.h new file mode 100644 index 0000000000..3ca246c797 --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Windows/AtomToolsFramework_Traits_Windows.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +// On some platforms, there is an issue with environment variables that are removed before some objects are deallocated (during the process of +// ComponentApplication::Destroy). Until all of the shutdown issues are solved, the following trait will skip the parent ::Destroy() and exit +// the application as soon as possible if set to true. +// (Tracked by GHI - 4806) +#define AZ_TRAIT_ATOMTOOLSFRAMEWORK_SKIP_APP_DESTROY false + diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Windows/platform_windows_files.cmake b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Windows/platform_windows_files.cmake new file mode 100644 index 0000000000..e2a2113bf9 --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Platform/Windows/platform_windows_files.cmake @@ -0,0 +1,12 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + AtomToolsFramework_Traits_Platform.h + AtomToolsFramework_Traits_Windows.h +) From c2ec18dc0ead9e4b5ef39be84308cdf2064174d6 Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Wed, 20 Oct 2021 14:41:35 -0700 Subject: [PATCH 14/27] Remove prefab WIP checks to make focus mode the default (and only) prefab editing workflow in the editor. (#4840) Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> --- .../UI/Prefab/PrefabIntegrationManager.cpp | 19 ++++--------------- .../UI/Prefab/PrefabUiHandler.cpp | 11 ++--------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index bef08466ae..65f7b0cdfb 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -255,7 +255,7 @@ namespace AzToolsFramework if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity)) { // Edit Prefab - if (prefabWipFeaturesEnabled && !s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity)) + if (!s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity)) { QAction* editAction = menu->addAction(QObject::tr("Edit Prefab")); editAction->setToolTip(QObject::tr("Edit the prefab in focus mode.")); @@ -1159,25 +1159,14 @@ namespace AzToolsFramework { s_editorEntityUiInterface->RegisterEntity(entityId, m_prefabUiHandler.GetHandlerId()); - bool prefabWipFeaturesEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult( - prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); - - if (prefabWipFeaturesEnabled) - { - // Register entity as a container - s_containerEntityInterface->RegisterEntityAsContainer(entityId); - } + // Register entity as a container + s_containerEntityInterface->RegisterEntityAsContainer(entityId); } } void PrefabIntegrationManager::OnPrefabComponentDeactivate(AZ::EntityId entityId) { - bool prefabWipFeaturesEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult( - prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); - - if (prefabWipFeaturesEnabled && !s_prefabPublicInterface->IsLevelInstanceContainerEntity(entityId)) + if (!s_prefabPublicInterface->IsLevelInstanceContainerEntity(entityId)) { // Unregister entity as a container s_containerEntityInterface->UnregisterEntityAsContainer(entityId); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp index ccad85e32b..7c2c65d204 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp @@ -316,14 +316,7 @@ namespace AzToolsFramework void PrefabUiHandler::OnDoubleClick(AZ::EntityId entityId) const { - bool prefabWipFeaturesEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult( - prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); - - if (prefabWipFeaturesEnabled) - { - // Focus on this prefab - m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId); - } + // Focus on this prefab + m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId); } } From 74d74050f2cd2f1c54fdfa2a9303c29d300adf34 Mon Sep 17 00:00:00 2001 From: Steve Pham <82231385+spham-amzn@users.noreply.github.com> Date: Wed, 20 Oct 2021 18:54:27 -0700 Subject: [PATCH 15/27] Fix unused variable error in release linux builds (#4846) Signed-off-by: Steve Pham --- .../Platform/Common/Xcb/AzFramework/XcbNativeWindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.cpp b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.cpp index af5b3af3d8..79a6333612 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.cpp +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.cpp @@ -308,7 +308,7 @@ namespace AzFramework event.data.data32[2] = 0; event.data.data32[3] = 1; event.data.data32[4] = 0; - xcb_void_cookie_t xcbCheckResult = xcb_send_event( + [[maybe_unused]] xcb_void_cookie_t xcbCheckResult = xcb_send_event( m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char*)&event); AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set _NET_WM_STATE_FULLSCREEN"); @@ -333,7 +333,7 @@ namespace AzFramework event.data.data32[2] = 0; event.data.data32[3] = 0; event.data.data32[4] = 0; - xcb_void_cookie_t xcbCheckResult = xcb_send_event( + [[maybe_unused]] xcb_void_cookie_t xcbCheckResult = xcb_send_event( m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char*)&event); AZ_Assert( From d78aa5bf905231ed954066a22b69fa1ec9772f34 Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Thu, 21 Oct 2021 10:55:32 -0500 Subject: [PATCH 16/27] Removed legacy ColorGradientCtrl. Signed-off-by: Chris Galvan --- Code/Editor/Controls/ColorGradientCtrl.cpp | 923 ------------------ Code/Editor/Controls/ColorGradientCtrl.h | 167 ---- .../ReflectedPropertyControl/PropertyCtrl.cpp | 1 - .../PropertyMiscCtrl.cpp | 27 - .../PropertyMiscCtrl.h | 14 - Code/Editor/editor_lib_files.cmake | 2 - 6 files changed, 1134 deletions(-) delete mode 100644 Code/Editor/Controls/ColorGradientCtrl.cpp delete mode 100644 Code/Editor/Controls/ColorGradientCtrl.h diff --git a/Code/Editor/Controls/ColorGradientCtrl.cpp b/Code/Editor/Controls/ColorGradientCtrl.cpp deleted file mode 100644 index 446e5810c5..0000000000 --- a/Code/Editor/Controls/ColorGradientCtrl.cpp +++ /dev/null @@ -1,923 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - - -#include "EditorDefs.h" - -#include "ColorGradientCtrl.h" - -// Qt -#include -#include - -// AzQtComponents -#include - - -#define MIN_TIME_EPSILON 0.01f - -////////////////////////////////////////////////////////////////////////// -CColorGradientCtrl::CColorGradientCtrl(QWidget* parent) - : QWidget(parent) -{ - m_nActiveKey = -1; - m_nHitKeyIndex = -1; - m_nKeyDrawRadius = 3; - m_bTracking = false; - m_pSpline = nullptr; - m_fMinTime = -1; - m_fMaxTime = 1; - m_fMinValue = -1; - m_fMaxValue = 1; - m_fTooltipScaleX = 1; - m_fTooltipScaleY = 1; - m_bNoTimeMarker = true; - m_bLockFirstLastKey = false; - m_bNoZoom = true; - - ClearSelection(); - - m_bSelectedKeys.reserve(0); - - m_fTimeMarker = -10; - - m_grid.zoom.x = 100; - - setMouseTracking(true); -} - -CColorGradientCtrl::~CColorGradientCtrl() -{ -} - - -///////////////////////////////////////////////////////////////////////////// -// QColorGradientCtrl message handlers - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::resizeEvent(QResizeEvent* event) -{ - QWidget::resizeEvent(event); - - QRect rc(QPoint(0, 0), event->size()); - m_rcGradient = rc; - m_rcGradient.setHeight(m_rcGradient.height() - 11); - //m_rcGradient.DeflateRect(4,4); - - m_grid.rect = m_rcGradient; - if (m_bNoZoom) - { - m_grid.zoom.x = static_cast(m_grid.rect.width()); - } - - m_rcKeys = rc; - m_rcKeys.setTop(m_rcKeys.bottom() - 10); -} - - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::SetZoom(float fZoom) -{ - m_grid.zoom.x = fZoom; -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::SetOrigin(float fOffset) -{ - m_grid.origin.x = fOffset; -} - -////////////////////////////////////////////////////////////////////////// -QPoint CColorGradientCtrl::KeyToPoint(int nKey) -{ - if (nKey >= 0) - { - return TimeToPoint(m_pSpline->GetKeyTime(nKey)); - } - return QPoint(0, 0); -} - -////////////////////////////////////////////////////////////////////////// -QPoint CColorGradientCtrl::TimeToPoint(float time) -{ - return QPoint(m_grid.WorldToClient(Vec2(time, 0)).x(), m_rcGradient.height() / 2); -} - -////////////////////////////////////////////////////////////////////////// -AZ::Color CColorGradientCtrl::TimeToColor(float time) -{ - ISplineInterpolator::ValueType val; - m_pSpline->Interpolate(time, val); - const AZ::Color col = ValueToColor(val); - return col; -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::PointToTimeValue(QPoint point, float& time, ISplineInterpolator::ValueType& val) -{ - time = XOfsToTime(point.x()); - ColorToValue(TimeToColor(time), val); -} - -////////////////////////////////////////////////////////////////////////// -float CColorGradientCtrl::XOfsToTime(int x) -{ - return m_grid.ClientToWorld(QPoint(x, 0)).x; -} - -////////////////////////////////////////////////////////////////////////// -QPoint CColorGradientCtrl::XOfsToPoint(int x) -{ - return TimeToPoint(XOfsToTime(x)); -} - -////////////////////////////////////////////////////////////////////////// -AZ::Color CColorGradientCtrl::XOfsToColor(int x) -{ - return TimeToColor(XOfsToTime(x)); -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::paintEvent(QPaintEvent* e) -{ - QPainter painter(this); - - QRect rcClient = rect(); - - if (m_pSpline) - { - m_bSelectedKeys.resize(m_pSpline->GetKeyCount()); - } - { - if (!isEnabled()) - { - painter.setBrush(palette().button()); - painter.drawRect(rcClient); - return; - } - - ////////////////////////////////////////////////////////////////////////// - // Fill keys backgound. - ////////////////////////////////////////////////////////////////////////// - QRect rcKeys = m_rcKeys.intersected(e->rect()); - painter.setBrush(palette().button()); - painter.drawRect(rcKeys); - ////////////////////////////////////////////////////////////////////////// - - //Draw Keys and Curve - if (m_pSpline) - { - DrawGradient(e, &painter); - DrawKeys(e, &painter); - } - } -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::DrawGradient(QPaintEvent* e, QPainter* painter) -{ - //Draw Curve - // create and select a thick, white pen - painter->setPen(QPen(QColor(128, 255, 128), 1, Qt::SolidLine)); - - const QRect rcClip = e->rect().intersected(m_rcGradient); - const int right = rcClip.left() + rcClip.width(); - for (int x = rcClip.left(); x < right; x++) - { - const AZ::Color col = XOfsToColor(x); - QPen pen(QColor(col.GetR8(), col.GetG8(), col.GetR8(), col.GetA8()), 1, Qt::SolidLine); - painter->setPen(pen); - painter->drawLine(x, m_rcGradient.top(), x, m_rcGradient.top() + m_rcGradient.height()); - } -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::DrawKeys(QPaintEvent* e, QPainter* painter) -{ - if (!m_pSpline) - { - return; - } - - // create and select a white pen - painter->setPen(QPen(QColor(0, 0, 0), 1, Qt::SolidLine)); - - QRect rcClip = e->rect(); - - m_bSelectedKeys.resize(m_pSpline->GetKeyCount()); - - for (int i = 0; i < m_pSpline->GetKeyCount(); i++) - { - float time = m_pSpline->GetKeyTime(i); - QPoint pt = TimeToPoint(time); - - if (pt.x() < rcClip.left() - 8 || pt.x() > rcClip.left() + rcClip.width() + 8) - { - continue; - } - - const AZ::Color clr = TimeToColor(time); - QBrush brush(QColor(clr.GetR8(), clr.GetG8(), clr.GetB8(), clr.GetA8())); - painter->setBrush(brush); - - // Find the midpoints of the top, right, left, and bottom - // of the client area. They will be the vertices of our polygon. - QPoint pts[3]; - pts[0].rx() = pt.x(); - pts[0].ry() = m_rcKeys.top() + 1; - pts[1].rx() = pt.x() - 5; - pts[1].ry() = m_rcKeys.top() + 8; - pts[2].rx() = pt.x() + 5; - pts[2].ry() = m_rcKeys.top() + 8; - painter->drawPolygon(pts, 3); - - if (m_bSelectedKeys[i]) - { - QPen pen(QColor(200, 0, 0), 1, Qt::SolidLine); - QPen oldPen = painter->pen(); - painter->setPen(pen); - painter->drawPolygon(pts, 3); - painter->setPen(oldPen); - } - } - - if (!m_bNoTimeMarker) - { - QPen timePen(QColor(255, 0, 255), 1, Qt::SolidLine); - painter->setPen(timePen); - QPoint pt = TimeToPoint(m_fTimeMarker); - painter->drawLine(pt.x(), m_rcGradient.top() + 1, pt.x(), m_rcGradient.bottom() - 1); - } -} - -void CColorGradientCtrl::UpdateTooltip(QPoint pos) -{ - if (m_nHitKeyIndex >= 0) - { - float time = m_pSpline->GetKeyTime(m_nHitKeyIndex); - ISplineInterpolator::ValueType val; - m_pSpline->GetKeyValue(m_nHitKeyIndex, val); - - AZ::Color col = TimeToColor(time); - int cont_s = (m_pSpline->GetKeyFlags(m_nHitKeyIndex) >> SPLINE_KEY_TANGENT_IN_SHIFT) & SPLINE_KEY_TANGENT_LINEAR ? 1 : 2; - int cont_d = (m_pSpline->GetKeyFlags(m_nHitKeyIndex) >> SPLINE_KEY_TANGENT_OUT_SHIFT) & SPLINE_KEY_TANGENT_LINEAR ? 1 : 2; - - QString tipText(tr("%1 : %2,%3,%4 [%5,%6]").arg(time * m_fTooltipScaleX, 0, 'f', 2).arg(col.GetR8()).arg(col.GetG8()).arg(col.GetB8()).arg(cont_s).arg(cont_d)); - const QPoint globalPos = mapToGlobal(pos); - QToolTip::showText(mapToGlobal(pos), tipText, this, QRect(globalPos, QSize(1, 1))); - } -} - -///////////////////////////////////////////////////////////////////////////// -//Mouse Message Handlers -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::mousePressEvent(QMouseEvent* event) -{ - if (event->button() == Qt::LeftButton) - { - OnLButtonDown(event); - } - else if (event->button() == Qt::RightButton) - { - OnRButtonDown(event); - } -} - -void CColorGradientCtrl::OnLButtonDown([[maybe_unused]] QMouseEvent* event) -{ - if (m_bTracking) - { - return; - } - if (!m_pSpline) - { - return; - } - - setFocus(); - - switch (m_hitCode) - { - case HIT_KEY: - StartTracking(); - SetActiveKey(m_nHitKeyIndex); - break; - - /* - case HIT_SPLINE: - { - // Cycle the spline slope of the nearest key. - int flags = m_pSpline->GetKeyFlags(m_nHitKeyIndex); - if (m_nHitKeyDist < 0) - // Toggle left side. - flags ^= SPLINE_KEY_TANGENT_LINEAR << SPLINE_KEY_TANGENT_IN_SHIFT; - if (m_nHitKeyDist > 0) - // Toggle right side. - flags ^= SPLINE_KEY_TANGENT_LINEAR << SPLINE_KEY_TANGENT_OUT_SHIFT; - m_pSpline->SetKeyFlags(m_nHitKeyIndex, flags); - m_pSpline->Update(); - - SetActiveKey(-1); - SendNotifyEvent( CLRGRDN_CHANGE ); - if (m_updateCallback) - m_updateCallback(this); - break; - } - */ - - case HIT_NOTHING: - SetActiveKey(-1); - break; - } - update(); -} - - - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::OnRButtonDown([[maybe_unused]] QMouseEvent* event) -{ -} - - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::mouseDoubleClickEvent(QMouseEvent* event) -{ - if (!m_pSpline) - { - return; - } - - if (event->button() != Qt::LeftButton) - { - return; - } - - switch (m_hitCode) - { - case HIT_SPLINE: - { - int iIndex = InsertKey(event->pos()); - SetActiveKey(iIndex); - EditKey(iIndex); - - update(); - } - break; - case HIT_KEY: - { - EditKey(m_nHitKeyIndex); - } - break; - } -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::mouseMoveEvent(QMouseEvent* event) -{ - if (!m_pSpline) - { - return; - } - - if (!m_bTracking) - { - switch (HitTest(event->pos())) - { - case HIT_SPLINE: - { - setCursor(CMFCUtils::LoadCursor(IDC_ARRWHITE)); - } break; - case HIT_KEY: - { - setCursor(CMFCUtils::LoadCursor(IDC_ARRBLCK)); - } break; - default: - break; - } - } - - if (m_bTracking) - { - TrackKey(event->pos()); - } - - if (m_bTracking || m_nHitKeyIndex >= 0) - { - UpdateTooltip(event->pos()); - } - else - { - QToolTip::hideText(); - } -} - -void CColorGradientCtrl::mouseReleaseEvent(QMouseEvent* event) -{ - if (event->button() == Qt::LeftButton) - { - OnLButtonUp(event); - } - else if (event->button() == Qt::RightButton) - { - OnRButtonUp(event); - } -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::OnLButtonUp(QMouseEvent* event) -{ - if (!m_pSpline) - { - return; - } - - if (m_bTracking) - { - StopTracking(event->pos()); - } -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::OnRButtonUp([[maybe_unused]] QMouseEvent* event) -{ - if (!m_pSpline) - { - return; - } -} - -///////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::SetActiveKey(int nIndex) -{ - ClearSelection(); - - //Activate New Key - if (nIndex >= 0) - { - m_bSelectedKeys[nIndex] = true; - } - m_nActiveKey = nIndex; - update(); - - SendNotifyEvent(CLRGRDN_ACTIVE_KEY_CHANGE); -} - -///////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::SetSpline(ISplineInterpolator* pSpline, bool bRedraw) -{ - if (pSpline != m_pSpline) - { - //if (pSpline && pSpline->GetNumDimensions() != 3) - //return; - m_pSpline = pSpline; - m_nActiveKey = -1; - } - - ClearSelection(); - - if (bRedraw) - { - update(); - } -} - -////////////////////////////////////////////////////////////////////////// -ISplineInterpolator* CColorGradientCtrl::GetSpline() -{ - return m_pSpline; -} - -///////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::keyPressEvent(QKeyEvent* event) -{ - bool bProcessed = false; - - if (m_nActiveKey != -1 && m_pSpline) - { - switch (event->key()) - { - case Qt::Key_Delete: - { - RemoveKey(m_nActiveKey); - bProcessed = true; - } break; - case Qt::Key_Up: - { - CUndo undo("Move Spline Key"); - QPoint point = KeyToPoint(m_nActiveKey); - point.rx() -= 1; - SendNotifyEvent(CLRGRDN_BEFORE_CHANGE); - TrackKey(point); - bProcessed = true; - } break; - case Qt::Key_Down: - { - CUndo undo("Move Spline Key"); - QPoint point = KeyToPoint(m_nActiveKey); - point.rx() += 1; - SendNotifyEvent(CLRGRDN_BEFORE_CHANGE); - TrackKey(point); - bProcessed = true; - } break; - case Qt::Key_Left: - { - CUndo undo("Move Spline Key"); - QPoint point = KeyToPoint(m_nActiveKey); - point.rx() -= 1; - SendNotifyEvent(CLRGRDN_BEFORE_CHANGE); - TrackKey(point); - bProcessed = true; - } break; - case Qt::Key_Right: - { - CUndo undo("Move Spline Key"); - QPoint point = KeyToPoint(m_nActiveKey); - point.rx() += 1; - SendNotifyEvent(CLRGRDN_BEFORE_CHANGE); - TrackKey(point); - bProcessed = true; - } break; - - default: - break; //do nothing - } - - update(); - } - - event->setAccepted(bProcessed); -} - -////////////////////////////////////////////////////////////////////////////// -CColorGradientCtrl::EHitCode CColorGradientCtrl::HitTest(QPoint point) -{ - if (!m_pSpline) - { - return HIT_NOTHING; - } - - ISplineInterpolator::ValueType val; - float time; - PointToTimeValue(point, time, val); - - QRect rc = rect(); - - m_nHitKeyIndex = -1; - - if (rc.contains(point)) - { - m_nHitKeyDist = 0xFFFF; - m_hitCode = HIT_SPLINE; - - for (int i = 0; i < m_pSpline->GetKeyCount(); i++) - { - QPoint splinePt = TimeToPoint(m_pSpline->GetKeyTime(i)); - if (abs(point.x() - splinePt.x()) < abs(m_nHitKeyDist)) - { - m_nHitKeyIndex = i; - m_nHitKeyDist = point.x() - splinePt.x(); - } - } - if (abs(m_nHitKeyDist) < 4) - { - m_hitCode = HIT_KEY; - } - } - else - { - m_hitCode = HIT_NOTHING; - } - - return m_hitCode; -} - -/////////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::StartTracking() -{ - m_bTracking = true; - - GetIEditor()->BeginUndo(); - SendNotifyEvent(CLRGRDN_BEFORE_CHANGE); - - setCursor(CMFCUtils::LoadCursor(IDC_ARRBLCKCROSS)); -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::TrackKey(QPoint point) -{ - if (point.x() < m_rcGradient.left() || point.y() > m_rcGradient.right()) - { - return; - } - - int nKey = m_nHitKeyIndex; - - if (nKey >= 0) - { - ISplineInterpolator::ValueType val; - float time; - PointToTimeValue(point, time, val); - - // Clamp to min/max time. - if (time < m_fMinTime || time > m_fMaxTime) - { - return; - } - - int i; - for (i = 0; i < m_pSpline->GetKeyCount(); i++) - { - // Switch to next key. - if ((m_pSpline->GetKeyTime(i) < time && i > nKey) || - (m_pSpline->GetKeyTime(i) > time && i < nKey)) - { - m_pSpline->SetKeyTime(nKey, time); - m_pSpline->Update(); - SetActiveKey(i); - m_nHitKeyIndex = i; - return; - } - } - - if (!m_bLockFirstLastKey || (nKey != 0 && nKey != m_pSpline->GetKeyCount() - 1)) - { - m_pSpline->SetKeyTime(nKey, time); - m_pSpline->Update(); - } - - SendNotifyEvent(CLRGRDN_CHANGE); - if (m_updateCallback) - { - m_updateCallback(this); - } - - update(); - } -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::StopTracking(QPoint point) -{ - if (!m_bTracking) - { - return; - } - - GetIEditor()->AcceptUndo("Spline Move"); - - if (m_nHitKeyIndex >= 0) - { - QRect rc = rect(); - rc = rc.marginsAdded(QMargins(100, 100, 100, 100)); - if (!rc.contains(point)) - { - RemoveKey(m_nHitKeyIndex); - } - } - - m_bTracking = false; -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::EditKey(int nKey) -{ - if (!m_pSpline) - { - return; - } - - if (nKey < 0 || nKey >= m_pSpline->GetKeyCount()) - { - return; - } - - SetActiveKey(nKey); - - ISplineInterpolator::ValueType val; - m_pSpline->GetKeyValue(nKey, val); - - SendNotifyEvent(CLRGRDN_BEFORE_CHANGE); - - AzQtComponents::ColorPicker dlg(AzQtComponents::ColorPicker::Configuration::RGB); - dlg.setCurrentColor(ValueToColor(val)); - dlg.setSelectedColor(ValueToColor(val)); - connect(&dlg, &AzQtComponents::ColorPicker::currentColorChanged, this, &CColorGradientCtrl::OnKeyColorChanged); - if (dlg.exec() == QDialog::Accepted) - { - CUndo undo("Modify Gradient Color"); - OnKeyColorChanged(dlg.selectedColor()); - } - else - { - OnKeyColorChanged(ValueToColor(val)); - } -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::OnKeyColorChanged(const AZ::Color& color) -{ - int nKey = m_nActiveKey; - if (!m_pSpline) - { - return; - } - if (nKey < 0 || nKey >= m_pSpline->GetKeyCount()) - { - return; - } - - ISplineInterpolator::ValueType val; - ColorToValue(color, val); - m_pSpline->SetKeyValue(nKey, val); - update(); - - if (m_bLockFirstLastKey) - { - if (nKey == 0) - { - m_pSpline->SetKeyValue(m_pSpline->GetKeyCount() - 1, val); - } - else if (nKey == m_pSpline->GetKeyCount() - 1) - { - m_pSpline->SetKeyValue(0, val); - } - } - m_pSpline->Update(); - SendNotifyEvent(CLRGRDN_CHANGE); - if (m_updateCallback) - { - m_updateCallback(this); - } - - GetIEditor()->UpdateViews(eRedrawViewports); -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::RemoveKey(int nKey) -{ - if (!m_pSpline) - { - return; - } - if (m_bLockFirstLastKey) - { - if (nKey == 0 || nKey == m_pSpline->GetKeyCount() - 1) - { - return; - } - } - - CUndo undo("Remove Spline Key"); - - SendNotifyEvent(CLRGRDN_BEFORE_CHANGE); - m_nActiveKey = -1; - m_nHitKeyIndex = -1; - if (m_pSpline) - { - m_pSpline->RemoveKey(nKey); - m_pSpline->Update(); - } - SendNotifyEvent(CLRGRDN_CHANGE); - if (m_updateCallback) - { - m_updateCallback(this); - } - - update(); -} - -////////////////////////////////////////////////////////////////////////// -int CColorGradientCtrl::InsertKey(QPoint point) -{ - CUndo undo("Spline Insert Key"); - - ISplineInterpolator::ValueType val; - - float time; - PointToTimeValue(point, time, val); - - if (time < m_fMinTime || time > m_fMaxTime) - { - return -1; - } - - int i; - for (i = 0; i < m_pSpline->GetKeyCount(); i++) - { - // Skip if any key already have time that is very close. - if (fabs(m_pSpline->GetKeyTime(i) - time) < MIN_TIME_EPSILON) - { - return i; - } - } - - SendNotifyEvent(CLRGRDN_BEFORE_CHANGE); - - m_pSpline->InsertKey(time, val); - m_pSpline->Interpolate(time, val); - ClearSelection(); - update(); - - SendNotifyEvent(CLRGRDN_CHANGE); - if (m_updateCallback) - { - m_updateCallback(this); - } - - for (i = 0; i < m_pSpline->GetKeyCount(); i++) - { - // Find key with added time. - if (m_pSpline->GetKeyTime(i) == time) - { - return i; - } - } - - return -1; -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::ClearSelection() -{ - m_nActiveKey = -1; - if (m_pSpline) - { - m_bSelectedKeys.resize(m_pSpline->GetKeyCount()); - } - for (int i = 0; i < (int)m_bSelectedKeys.size(); i++) - { - m_bSelectedKeys[i] = false; - } -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::SetTimeMarker(float fTime) -{ - if (!m_pSpline) - { - return; - } - - { - QPoint pt = TimeToPoint(m_fTimeMarker); - QRect rc = QRect(pt.x(), m_rcGradient.top(), 0, m_rcGradient.bottom() - m_rcGradient.top()).normalized(); - rc += QMargins(1, 0, 1, 0); - update(rc); - } - { - QPoint pt = TimeToPoint(fTime); - QRect rc = QRect(pt.x(), m_rcGradient.top(), 0, m_rcGradient.bottom() - m_rcGradient.top()).normalized(); - rc += QMargins(1, 0, 1, 0); - update(rc); - } - m_fTimeMarker = fTime; -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::SendNotifyEvent(int nEvent) -{ - switch (nEvent) - { - case CLRGRDN_BEFORE_CHANGE: - emit beforeChange(); - break; - case CLRGRDN_CHANGE: - emit change(); - break; - case CLRGRDN_ACTIVE_KEY_CHANGE: - emit activeKeyChange(); - break; - } -} - -////////////////////////////////////////////////////////////////////////// -AZ::Color CColorGradientCtrl::ValueToColor(ISplineInterpolator::ValueType val) -{ - const AZ::Color color(val[0], val[1], val[2], 1.0); - return color.LinearToGamma(); -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::ColorToValue(const AZ::Color& col, ISplineInterpolator::ValueType& val) -{ - const AZ::Color colLin = col.GammaToLinear(); - val[0] = colLin.GetR(); - val[1] = colLin.GetG(); - val[2] = colLin.GetB(); - val[3] = 0; -} - -void CColorGradientCtrl::SetNoTimeMarker(bool noTimeMarker) -{ - m_bNoTimeMarker = noTimeMarker; - update(); -} - - -#include diff --git a/Code/Editor/Controls/ColorGradientCtrl.h b/Code/Editor/Controls/ColorGradientCtrl.h deleted file mode 100644 index bb1a83b0c1..0000000000 --- a/Code/Editor/Controls/ColorGradientCtrl.h +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - - -#ifndef CRYINCLUDE_EDITOR_CONTROLS_COLORGRADIENTCTRL_H -#define CRYINCLUDE_EDITOR_CONTROLS_COLORGRADIENTCTRL_H -#pragma once - -#if !defined(Q_MOC_RUN) -#include -#include -#include "Controls/WndGridHelper.h" -#endif - -namespace AZ -{ - class Color; -} - -// Notify event sent when spline is being modified. -#define CLRGRDN_CHANGE (0x0001) -// Notify event sent just before when spline is modified. -#define CLRGRDN_BEFORE_CHANGE (0x0002) -// Notify event sent when the active key changes -#define CLRGRDN_ACTIVE_KEY_CHANGE (0x0003) - -////////////////////////////////////////////////////////////////////////// -// Spline control. -////////////////////////////////////////////////////////////////////////// -class CColorGradientCtrl - : public QWidget -{ - Q_OBJECT -public: - CColorGradientCtrl(QWidget* parent = nullptr); - virtual ~CColorGradientCtrl(); - - //Key functions - int GetActiveKey() { return m_nActiveKey; }; - void SetActiveKey(int nIndex); - int InsertKey(QPoint point); - - // Turns on/off zooming and scroll support. - void SetNoZoom([[maybe_unused]] bool bNoZoom) { m_bNoZoom = false; }; - - void SetTimeRange(float tmin, float tmax) { m_fMinTime = tmin; m_fMaxTime = tmax; } - void SetValueRange(float tmin, float tmax) { m_fMinValue = tmin; m_fMaxValue = tmax; } - void SetTooltipValueScale(float x, float y) { m_fTooltipScaleX = x; m_fTooltipScaleY = y; }; - // Lock value of first and last key to be the same. - void LockFirstAndLastKeys(bool bLock) { m_bLockFirstLastKey = bLock; } - - void SetSpline(ISplineInterpolator* pSpline, bool bRedraw = false); - ISplineInterpolator* GetSpline(); - - void SetTimeMarker(float fTime); - - // Zoom in pixels per time unit. - void SetZoom(float fZoom); - void SetOrigin(float fOffset); - - typedef AZStd::function UpdateCallback; - void SetUpdateCallback(const UpdateCallback& cb) { m_updateCallback = cb; }; - - void SetNoTimeMarker(bool noTimeMarker); - -signals: - void change(); - void beforeChange(); - void activeKeyChange(); - -protected: - enum EHitCode - { - HIT_NOTHING, - HIT_KEY, - HIT_SPLINE, - }; - - void paintEvent(QPaintEvent* e) override; - void resizeEvent(QResizeEvent* event) override; - void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void OnLButtonDown(QMouseEvent* event); - void mouseMoveEvent(QMouseEvent* event) override; - void OnLButtonUp(QMouseEvent* event); - void OnRButtonUp(QMouseEvent* event); - void mouseDoubleClickEvent(QMouseEvent* event) override; - void OnRButtonDown(QMouseEvent* event); - void keyPressEvent(QKeyEvent* event) override; - - // Drawing functions - void DrawGradient(QPaintEvent* e, QPainter* painter); - void DrawKeys(QPaintEvent* e, QPainter* painter); - void UpdateTooltip(QPoint pos); - - EHitCode HitTest(QPoint point); - - //Tracking support helper functions - void StartTracking(); - void TrackKey(QPoint point); - void StopTracking(QPoint point); - void RemoveKey(int nKey); - void EditKey(int nKey); - - QPoint KeyToPoint(int nKey); - QPoint TimeToPoint(float time); - void PointToTimeValue(QPoint point, float& time, ISplineInterpolator::ValueType& val); - float XOfsToTime(int x); - QPoint XOfsToPoint(int x); - - AZ::Color XOfsToColor(int x); - AZ::Color TimeToColor(float time); - - void ClearSelection(); - - void SendNotifyEvent(int nEvent); - - AZ::Color ValueToColor(ISplineInterpolator::ValueType val); - void ColorToValue(const AZ::Color& col, ISplineInterpolator::ValueType& val); - - -private: - void OnKeyColorChanged(const AZ::Color& color); - -private: - ISplineInterpolator* m_pSpline; - - bool m_bNoZoom; - - QRect m_rcClipRect; - QRect m_rcGradient; - QRect m_rcKeys; - - QPoint m_hitPoint; - EHitCode m_hitCode; - int m_nHitKeyIndex; - int m_nHitKeyDist; - QPoint m_curvePoint; - - float m_fTimeMarker; - - int m_nActiveKey; - int m_nKeyDrawRadius; - - bool m_bTracking; - - float m_fMinTime, m_fMaxTime; - float m_fMinValue, m_fMaxValue; - float m_fTooltipScaleX, m_fTooltipScaleY; - - bool m_bLockFirstLastKey; - - bool m_bNoTimeMarker; - - std::vector m_bSelectedKeys; - - UpdateCallback m_updateCallback; - - CWndGridHelper m_grid; -}; - -#endif // CRYINCLUDE_EDITOR_CONTROLS_COLORGRADIENTCTRL_H diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyCtrl.cpp b/Code/Editor/Controls/ReflectedPropertyControl/PropertyCtrl.cpp index c228fbda09..68c4acb95e 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyCtrl.cpp +++ b/Code/Editor/Controls/ReflectedPropertyControl/PropertyCtrl.cpp @@ -27,7 +27,6 @@ void RegisterReflectedVarHandlers() EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew LocalStringPropertyHandler()); EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew LightAnimationPropertyHandler()); EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew UserPopupWidgetHandler()); - EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew ColorCurveHandler()); EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew FloatCurveHandler()); EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew MotionPropertyWidgetHandler()); } diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.cpp b/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.cpp index b9f7e12e95..2278f55d95 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.cpp +++ b/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.cpp @@ -170,30 +170,3 @@ bool FloatCurveHandler::ReadValuesIntoGUI([[maybe_unused]] size_t index, CSpline GUI->SetSpline(reinterpret_cast(instance.m_spline)); return false; } - - -QWidget* ColorCurveHandler::CreateGUI(QWidget *pParent) -{ - CColorGradientCtrl* gradientCtrl = new CColorGradientCtrl(pParent); - //connect(gradientCtrl, &CColorGradientCtrl::change, [gradientCtrl]() - //{ - // EBUS_EVENT(AzToolsFramework::PropertyEditorGUIMessages::Bus, RequestWrite, gradientCtrl); - //}); - gradientCtrl->SetTimeRange(0, 1); - gradientCtrl->setFixedHeight(36); - return gradientCtrl; - -} - -void ColorCurveHandler::ConsumeAttribute(CColorGradientCtrl*, AZ::u32, AzToolsFramework::PropertyAttributeReader*, const char*) -{} - -void ColorCurveHandler::WriteGUIValuesIntoProperty([[maybe_unused]] size_t index, [[maybe_unused]] CColorGradientCtrl* GUI, [[maybe_unused]] property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node) -{} - -bool ColorCurveHandler::ReadValuesIntoGUI([[maybe_unused]] size_t index, CColorGradientCtrl* GUI, const property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node) -{ - GUI->SetSpline(reinterpret_cast(instance.m_spline)); - return false; -} - diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h b/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h index e63a974c91..5ec24b679d 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h +++ b/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h @@ -16,7 +16,6 @@ #include #include "ReflectedVar.h" #include "Util/VariablePropertyType.h" -#include "Controls/ColorGradientCtrl.h" #include "Controls/SplineCtrl.h" #include #endif @@ -82,17 +81,4 @@ public: void OnSplineChange(CSplineCtrl*); }; -class ColorCurveHandler : public QObject, public AzToolsFramework::PropertyHandler < CReflectedVarSpline, CColorGradientCtrl> -{ -public: - AZ_CLASS_ALLOCATOR(ColorCurveHandler, AZ::SystemAllocator, 0); - bool IsDefaultHandler() const override { return false; } - QWidget* CreateGUI(QWidget *pParent) override; - - AZ::u32 GetHandlerName(void) const override { return AZ_CRC("ePropertyColorCurve", 0xa30da4ec); } - - void ConsumeAttribute(CColorGradientCtrl* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName) override; - void WriteGUIValuesIntoProperty(size_t index, CColorGradientCtrl* GUI, property_t& instance, AzToolsFramework::InstanceDataNode* node) override; - bool ReadValuesIntoGUI(size_t index, CColorGradientCtrl* GUI, const property_t& instance, AzToolsFramework::InstanceDataNode* node) override; -}; #endif // CRYINCLUDE_EDITOR_UTILS_PROPERTYMISCCTRL_H diff --git a/Code/Editor/editor_lib_files.cmake b/Code/Editor/editor_lib_files.cmake index 031ad26d76..47b69765ba 100644 --- a/Code/Editor/editor_lib_files.cmake +++ b/Code/Editor/editor_lib_files.cmake @@ -330,8 +330,6 @@ set(FILES Commands/CommandManager.h Controls/BitmapToolTip.cpp Controls/BitmapToolTip.h - Controls/ColorGradientCtrl.cpp - Controls/ColorGradientCtrl.h Controls/ConsoleSCB.cpp Controls/ConsoleSCB.h Controls/ConsoleSCB.ui From a33ab6712549c11256d5ecba4a9c004860add9c4 Mon Sep 17 00:00:00 2001 From: Michael Pollind Date: Thu, 21 Oct 2021 08:58:14 -0700 Subject: [PATCH 17/27] bugfix: handle moving files for inode-watch for AssetProcessor (#4656) (#4809) Signed-off-by: Michael Pollind --- .../Platform/Linux/native/FileWatcher/FileWatcher_linux.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Code/Tools/AssetProcessor/Platform/Linux/native/FileWatcher/FileWatcher_linux.cpp b/Code/Tools/AssetProcessor/Platform/Linux/native/FileWatcher/FileWatcher_linux.cpp index ea458ba826..2d3e671999 100644 --- a/Code/Tools/AssetProcessor/Platform/Linux/native/FileWatcher/FileWatcher_linux.cpp +++ b/Code/Tools/AssetProcessor/Platform/Linux/native/FileWatcher/FileWatcher_linux.cpp @@ -72,7 +72,7 @@ struct FolderRootWatch::PlatformImplementation // Add the folder to watch and track it int watchHandle = inotify_add_watch(m_iNotifyHandle, cleanPath.toUtf8().constData(), - IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY); + IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY | IN_MOVE); if (!m_handleToFolderMapLock.tryLock(s_handleToFolderMapLockTimeout)) { @@ -95,7 +95,7 @@ struct FolderRootWatch::PlatformImplementation int watchHandle = inotify_add_watch(m_iNotifyHandle, dirName.toUtf8().constData(), - IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY); + IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY | IN_MOVE); if (!m_handleToFolderMapLock.tryLock(s_handleToFolderMapLockTimeout)) { From 7a14a21377537a47cb392f3f888892b726c2b36f Mon Sep 17 00:00:00 2001 From: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> Date: Thu, 21 Oct 2021 10:59:42 -0500 Subject: [PATCH 18/27] Fixed create-project/create-gem to use relative paths directly (#4811) * Fixed create-project/create-gem to use relative paths directly It was previously making relative paths, relative to the default_projects_folder and default_gems_folders Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Removed uppercase of "Templates" param in get_registered call. That method looks up the restricted template directory location based on the lowercase key of "templates" Updated argparse doc to fix typos or clarify which directories a path is relative to. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> --- scripts/o3de/o3de/engine_template.py | 86 ++++++++++++---------------- 1 file changed, 38 insertions(+), 48 deletions(-) diff --git a/scripts/o3de/o3de/engine_template.py b/scripts/o3de/o3de/engine_template.py index cba7539aaa..e93f972301 100755 --- a/scripts/o3de/o3de/engine_template.py +++ b/scripts/o3de/o3de/engine_template.py @@ -407,15 +407,12 @@ def create_template(source_path: pathlib.Path, source_name = os.path.basename(source_path) sanitized_source_name = utils.sanitize_identifier_for_cpp(source_name) - # if no template path, error + # if no template path, use default_templates_folder path if not template_path: - logger.info(f'Template path empty. Using source name {source_name}') - template_path = pathlib.Path(source_name) - if not template_path.is_absolute(): default_templates_folder = manifest.get_registered(default_folder='templates') - template_path = default_templates_folder / template_path - logger.info(f'Template path not a full path. Using default templates folder {template_path}') - if not force and template_path.is_dir(): + template_path = default_templates_folder / source_name + logger.info(f'Template path empty. Using default templates folder {template_path}') + if not force and template_path.is_dir() and len(list(template_path.iterdir())): logger.error(f'Template path {template_path} already exists.') return 1 @@ -1105,7 +1102,7 @@ def create_from_template(destination_path: pathlib.Path, logger.error(f'Could not find the template {template_name}=>{template_path}') return 1 - # the template.json should be in the template_path, make sure it's there a nd valid + # the template.json should be in the template_path, make sure it is valid template_json = template_path / 'template.json' if not validation.valid_o3de_template_json(template_json): logger.error(f'Template json {template_path} is invalid.') @@ -1254,7 +1251,7 @@ def create_from_template(destination_path: pathlib.Path, # destination restricted path elif destination_restricted_path: if os.path.isabs(destination_restricted_path): - restricted_default_path = manifest.get_registered(default='restricted') + restricted_default_path = manifest.get_registered(default_folder='restricted') new_destination_restricted_path = restricted_default_path / destination_restricted_path logger.info(f'{destination_restricted_path} is not a full path, making it relative' f' to default restricted path = {new_destination_restricted_path}') @@ -1346,7 +1343,7 @@ def create_project(project_path: pathlib.Path, Template instantiation specialization that makes all default assumptions for a Project template instantiation, reducing the effort needed in instancing a project :param project_path: the project path, can be absolute or relative to default projects path - :param project_name: the project name, defaults to project_path basename if not provided + :param project_name: the project name, defaults to project_path basename if not provided :param template_path: the path to the template you want to instance, can be absolute or relative to default templates path :param template_name: the name the registered template you want to instance, defaults to DefaultProject, resolves template_path :param project_restricted_path: path to the projects restricted folder, can be absolute or relative to the restricted='projects' @@ -1523,13 +1520,9 @@ def create_project(project_path: pathlib.Path, if not project_path: logger.error('Project path cannot be empty.') return 1 - if not os.path.isabs(project_path): - default_projects_folder = manifest.get_registered(default_folder='projects') - new_project_path = default_projects_folder / project_path - logger.info(f'Project Path {project_path} is not a full path, we must assume its relative' - f' to default projects path = {new_project_path}') - project_path = new_project_path - if not force and os.path.isdir(project_path) and len(os.listdir(project_path)) > 0: + + project_path = project_path.resolve() + if not force and project_path.is_dir() and len(list(project_path.iterdir())): logger.error(f'Project path {project_path} already exists and is not empty.') return 1 elif not os.path.isdir(project_path): @@ -1904,14 +1897,10 @@ def create_gem(gem_path: pathlib.Path, if not gem_path: logger.error('Gem path cannot be empty.') return 1 - if not os.path.isabs(gem_path): - default_gems_folder = manifest.get_registered(default_folder='gems') - new_gem_path = default_gems_folder / gem_path - logger.info(f'Gem Path {gem_path} is not a full path, we must assume its relative' - f' to default gems path = {new_gem_path}') - gem_path = new_gem_path - if not force and os.path.isdir(gem_path): - logger.error(f'Gem path {gem_path} already exists.') + + gem_path = gem_path.resolve() + if not force and gem_path.is_dir() and len(list(gem_path.iterdir())): + logger.error(f'Gem path {gem_path} already exists and is not empty.') return 1 else: os.makedirs(gem_path, exist_ok=force) @@ -1936,16 +1925,18 @@ def create_gem(gem_path: pathlib.Path, # gem restricted path elif gem_restricted_path: if not os.path.isabs(gem_restricted_path): - default_gems_restricted_folder = manifest.get_registered(restricted_name='gems') - new_gem_restricted_path = default_gems_restricted_folder /gem_restricted_path - logger.info(f'Gem restricted path {gem_restricted_path} is not a full path, we must assume its' - f' relative to default gems restricted path = {new_gem_restricted_path}') - gem_restricted_path = new_gem_restricted_path - elif template_restricted_path: + gem_restricted_default_path = manifest.get_registered(restricted_name='gems') + if gem_restricted_default_path: + new_gem_restricted_path = gem_restricted_default_path / gem_restricted_path + logger.info(f'Gem restricted path {gem_restricted_path} is not a full path, we must assume its' + f' relative to default gems restricted path = {new_gem_restricted_path}') + gem_restricted_path = new_gem_restricted_path + else: gem_restricted_default_path = manifest.get_registered(restricted_name='gems') - logger.info(f'--gem-restricted-path is not specified, using default gem restricted path / gem name' - f' = {gem_restricted_default_path}') - gem_restricted_path = gem_restricted_default_path + if gem_restricted_default_path: + logger.info(f'--gem-restricted-path is not specified, using default / ' + f' = {gem_restricted_default_path}') + gem_restricted_path = gem_restricted_default_path / gem_name # gem restricted relative if not gem_restricted_platform_relative_path: @@ -1964,7 +1955,7 @@ def create_gem(gem_path: pathlib.Path, replacements.append(("${NameUpper}", gem_name.upper())) replacements.append(("${NameLower}", gem_name.lower())) replacements.append(("${SanitizedCppName}", sanitized_cpp_name)) - + # module id is a uuid with { and - if module_id: @@ -2244,14 +2235,14 @@ def add_args(subparsers) -> None: create_from_template_subparser = subparsers.add_parser('create-from-template') create_from_template_subparser.add_argument('-dp', '--destination-path', type=pathlib.Path, required=True, help='The path to where you want the template instantiated,' - ' can be absolute or dev root relative.' + ' can be absolute or relative to the current working directory.' 'Ex. C:/o3de/Test' 'Test = ') group = create_from_template_subparser.add_mutually_exclusive_group(required=True) group.add_argument('-tp', '--template-path', type=pathlib.Path, required=False, help='The path to the template you want to instantiate, can be absolute' - ' or dev root/Templates relative.' + ' or relative to the current working directory.' 'Ex. C:/o3de/Template/TestTemplate' 'TestTemplate = ') group.add_argument('-tn', '--template-name', type=str, required=False, @@ -2327,7 +2318,7 @@ def add_args(subparsers) -> None: create_project_subparser = subparsers.add_parser('create-project') create_project_subparser.add_argument('-pp', '--project-path', type=pathlib.Path, required=True, help='The location of the project you wish to create from the template,' - ' can be an absolute path or dev root relative.' + ' can be an absolute path or relative to the current working directory.' ' Ex. C:/o3de/TestProject' ' TestProject = if --project-name not provided') create_project_subparser.add_argument('-pn', '--project-name', type=str, required=False, @@ -2349,8 +2340,8 @@ def add_args(subparsers) -> None: group = create_project_subparser.add_mutually_exclusive_group(required=False) group.add_argument('-prp', '--project-restricted-path', type=pathlib.Path, required=False, default=None, - help='path to the projects restricted folder, can be absolute or relative' - ' to the restricted="projects"') + help='path to the projects restricted folder, can be absolute or relative to' + ' the default restricted projects directory') group.add_argument('-prn', '--project-restricted-name', type=str, required=False, default=None, help='The name of the registered projects restricted path. If supplied this will resolve' @@ -2360,7 +2351,7 @@ def add_args(subparsers) -> None: group.add_argument('-trp', '--template-restricted-path', type=pathlib.Path, required=False, default=None, help='The templates restricted path can be absolute or relative to' - ' restricted="templates"') + 'the default restricted templates directory') group.add_argument('-trn', '--template-restricted-name', type=str, required=False, default=None, help='The name of the registered templates restricted path. If supplied this will resolve' @@ -2423,7 +2414,7 @@ def add_args(subparsers) -> None: # creation of a gem from a template (like create from template but makes gem assumptions) create_gem_subparser = subparsers.add_parser('create-gem') create_gem_subparser.add_argument('-gp', '--gem-path', type=pathlib.Path, required=True, - help='The gem path, can be absolute or relative to default gems path') + help='The gem path, can be absolute or relative to the current working directory') create_gem_subparser.add_argument('-gn', '--gem-name', type=str, help='The name to use when substituting the ${Name} placeholder for the gem,' ' must be alphanumeric, ' @@ -2444,19 +2435,18 @@ def add_args(subparsers) -> None: group = create_gem_subparser.add_mutually_exclusive_group(required=False) group.add_argument('-grp', '--gem-restricted-path', type=pathlib.Path, required=False, default=None, - help='The path to the gem restricted to write to folder if any, can be' - 'absolute or dev root relative, default is dev root/restricted.') + help='The gem restricted path, can be absolute or relative to' + ' the default restricted gems directory') group.add_argument('-grn', '--gem-restricted-name', type=str, required=False, default=None, - help='The path to the gem restricted to write to folder if any, can be' - 'absolute or dev root relative, default is dev root/restricted. If supplied' - ' this will resolve the --gem-restricted-path.') + help='The name of the gem to look up the gem restricted path if any.' + 'If supplied this will resolve the --gem-restricted-path.') group = create_gem_subparser.add_mutually_exclusive_group(required=False) group.add_argument('-trp', '--template-restricted-path', type=pathlib.Path, required=False, default=None, help='The templates restricted path, can be absolute or relative to' - ' the restricted="templates"') + ' the default restricted templates directory') group.add_argument('-trn', '--template-restricted-name', type=str, required=False, default=None, help='The name of the registered templates restricted path. If supplied' From ab86c9961e48c03b3180dac323ba118f6dfe6fe9 Mon Sep 17 00:00:00 2001 From: Chris Burel Date: Thu, 21 Oct 2021 09:09:56 -0700 Subject: [PATCH 19/27] [Linux] Fix deadlock when running `LaunchProcess()` from a thread (#4833) `LaunchProcess()` on Linux works by calling `fork` then `execvpe`. `fork` is used to copy a running process, generating a new child process. The new child starts running from the location where the parent was running, from whatever thread from the parent called `fork`. The child process only gets one thread, however. If a different thread in the parent process had locked a mutex, that mutex is also locked in the child process. Since that separate thread is not present in the child, the mutex remains locked in the child, with no way to unlock it. So it is important that as little work as possible happens between the call to `fork` and to `execvpe`. Previously, this code was trying to report an error that may have occurred from calling `execvpe`. It was doing that by calling `AZ_TracePrintf`. That function does lots of things, including trying to make an EBus call, which looks up a variable in the `AZ::Environment` instance, which has a global mutex. If there was some other thread that had that mutex locked when the `fork` call was made, the subprocess would deadlock, and the parent process would also deadlock waiting for the child to finish. This solves that issue by removing the call to `AZ_TracePrintf` from the subprocess code path. Instead, the parent process sets up a pipe for the child process to write to in case the call to `execvpe` fails (the self-pipe trick). The parent then reads from that pipe. If it reads no data, `execvpe` worked and there's no error. If it does read data, the data to be read is the errno from the failed `execvpe` call made by the child. The parent can then use `strerror()` to report the error. Fixes #4702. Signed-off-by: Chris Burel --- .../AzFramework/Process/ProcessWatcher.cpp | 4 +- .../Process/ProcessWatcher_Linux.cpp | 95 ++++++++++++++----- 2 files changed, 73 insertions(+), 26 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.cpp b/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.cpp index 3ba7ed7223..798b1fe99f 100644 --- a/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.cpp +++ b/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.cpp @@ -22,7 +22,7 @@ namespace AzFramework AZStd::scoped_ptr pWatcher(LaunchProcess(processLaunchInfo, communicationType)); if (!pWatcher) { - AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str()); + AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str()); return false; } else @@ -31,7 +31,7 @@ namespace AzFramework ProcessCommunicator* pCommunicator = pWatcher->GetCommunicator(); if (!pCommunicator || !pCommunicator->IsValid()) { - AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str()); + AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str()); return false; } else diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp index d2cd681012..51a8545443 100644 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp @@ -91,31 +91,42 @@ namespace AzFramework return processId == 0; } - /*! Executes a command in the child process after the fork operation has been executed. - * This function will never return. If the execvp command fails this will call _exit with - * the errno value as the return value since continuing execution after a execvp command - * is invalid (it will be running the parent's code and in its address space and will - * cause many issues). + /*! Executes a command in the child process after the fork operation + * has been executed. This function will never return. If the execvpe + * command fails this will call _exit since continuing execution after + * a execvpe command is invalid (it will be running the parent's code + * and in its address space and will cause many issues). + * + * This function runs after a `fork()` call. `fork()` creates a copy of + * the current process, including the current state of the process's + * memory, at the time the call is made. However, it only creates a + * copy of the one thread that called `fork()`. This means that if any + * mutexes are locked by other threads at the time of `fork()`, those + * mutexes will remain locked in the child process, with no way to + * unlock them. So this function needs to ensure that it does as little + * work as possible. * * \param commandAndArgs - Array of strings that has the command to execute in index 0 with any args for the command following. Last element must be a null pointer. - * \param envionrmentVariables - Array of strings that contains environment variables that command should use. Last element must be a null pointer. + * \param environmentVariables - Array of strings that contains environment variables that command should use. Last element must be a null pointer. * \param processLaunchInfo - struct containing information about luanching the command * \param startupInfo - struct containing information needed to startup the command + * \param errorPipe - a pipe file descriptor used to communicate a failed execvpe call's error code to the parent process */ - void ExecuteCommandAsChild(char** commandAndArgs, char** environmentVariables, const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, StartupInfo& startupInfo) + [[noreturn]] static void ExecuteCommandAsChild(char** commandAndArgs, char** environmentVariables, const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, StartupInfo& startupInfo, const AZStd::array& errorPipe) { + close(errorPipe[0]); + if (!processLaunchInfo.m_workingDirectory.empty()) { int res = chdir(processLaunchInfo.m_workingDirectory.c_str()); if (res != 0) { - std::cerr << strerror(errno) << std::endl; - AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to change the launched process' directory to '%s'.", processLaunchInfo.m_workingDirectory.c_str()); + write(errorPipe[1], &errno, sizeof(int)); // We *have* to _exit as we are the child process and simply // returning at this point would mean we would start running // the code from our parent process and that will just wreck // havoc. - _exit(errno); + _exit(0); } } @@ -135,15 +146,17 @@ namespace AzFramework startupInfo.SetupHandlesForChildProcess(); - execve(commandAndArgs[0], commandAndArgs, environmentVariables); + execvpe(commandAndArgs[0], commandAndArgs, environmentVariables); + const int errval = errno; - // If we get here then execve failed to run the requested program and + // If we get here then execvpe failed to run the requested program and // we have an error. In this case we need to exit the child process - // to stop it from continuing to run as a clone of the parent - AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process %s : errno = %s ", commandAndArgs[0], strerror(errno)); - std::cerr << strerror(errno) << std::endl; + // to stop it from continuing to run as a clone of the parent. + // Communicate the error code back to the parent via a pipe for the + // parent to read. + write(errorPipe[1], &errval, sizeof(errval)); - _exit(errno); + _exit(0); } } @@ -212,9 +225,8 @@ namespace AzFramework AZStd::string outputString; bool inQuotes = false; - for (size_t pos = 0; pos < processLaunchInfo.m_commandlineParameters.size(); ++pos) + for (const char currentChar : processLaunchInfo.m_commandlineParameters) { - char currentChar = processLaunchInfo.m_commandlineParameters[pos]; if (currentChar == '"') { inQuotes = !inQuotes; @@ -231,7 +243,7 @@ namespace AzFramework outputString.push_back(currentChar); } } - + if (!outputString.empty()) { commandTokens.push_back(outputString); @@ -249,10 +261,10 @@ namespace AzFramework return false; } - // Because of the way execve is defined we need to copy the strings from + // Because of the way execvpe is defined we need to copy the strings from // AZ::string (using c_str() returns a const char*) into a non-const char* - // Need to add one more as exec requires the array's last element to be a null pointer + // Need to add one more as execvpe requires the array's last element to be a null pointer char** commandAndArgs = new char*[commandTokens.size() + 1]; for (int i = 0; i < commandTokens.size(); ++i) { @@ -275,7 +287,7 @@ namespace AzFramework azstrcat(environmentVariable.get(), envVarString.size() + 1, envVarString.c_str()); environmentVariablesVector.emplace_back(environmentVariable.get()); } - // Adding one more as exec expects the array to have a nullptr as the last element + // Adding one more as execvpe expects the array to have a nullptr as the last element environmentVariablesVector.emplace_back(nullptr); environmentVariables = environmentVariablesVector.data(); } @@ -288,15 +300,50 @@ namespace AzFramework AZ_Assert(environmentVariables, "Environment variables for current process not available\n"); } + // Set up a pipe to communicate the error code from the subprocess's execvpe call + AZStd::array childErrorPipeFds{}; + pipe(childErrorPipeFds.data()); + + // This configures the write end of the pipe to close on calls to `exec` + fcntl(childErrorPipeFds[1], F_SETFD, fcntl(childErrorPipeFds[1], F_GETFD) | FD_CLOEXEC); + pid_t child_pid = fork(); if (IsIdChildProcess(child_pid)) { - ExecuteCommandAsChild(commandAndArgs, environmentVariables, processLaunchInfo, processData.m_startupInfo); + ExecuteCommandAsChild(commandAndArgs, environmentVariables, processLaunchInfo, processData.m_startupInfo, childErrorPipeFds); } - processData.m_childProcessId = child_pid; // Close these handles as they are only to be used by the child process processData.m_startupInfo.CloseAllHandles(); + close(childErrorPipeFds[1]); + + { + int errorCodeFromChild = 0; + int count = 0; + // Read from the error pipe. + // * If the child's call to execvpe succeeded, then the pipe will + // be closed due to setting FD_CLOEXEC on the write end of the + // pipe. `read()` will return 0. + // * If the child's call to execvpe failed, the child will have + // written the error code to the pipe. `read()` will return >0, and + // the data to be read is the error code from execvpe. + while ((count = read(childErrorPipeFds[0], &errorCodeFromChild, sizeof(errorCodeFromChild))) == -1) + { + if (errno != EAGAIN && errno != EINTR) + { + break; + } + } + if (count) + { + AZ_TracePrintf("Process Watcher", "ProcessLauncher::LaunchProcess: Unable to launch process %s : errno = %s\n", commandAndArgs[0], strerror(errorCodeFromChild)); + processData.m_childProcessIsDone = true; + child_pid = -1; + } + } + close(childErrorPipeFds[0]); + + processData.m_childProcessId = child_pid; for (int i = 0; i < commandTokens.size(); i++) { From c871224daee0e5ef6296047593fd390c93353efd Mon Sep 17 00:00:00 2001 From: SJ Date: Thu, 21 Oct 2021 09:13:43 -0700 Subject: [PATCH 20/27] Support for importing Json files (#4609) * Initial support for importing Json files within other Json files Signed-off-by: amzn-sj * Add some test cases for testing/iterating on the Json import work. Fix MacOS AzTestRunner module loading bug. Signed-off-by: amzn-sj * The import resolver can take the allocator as a parameter to Load/StoreImports() instead of storing a copy. Signed-off-by: amzn-sj * Fix assert Signed-off-by: amzn-sj * Some rework of the JsonImport feature. Base test cases pass. More complex test cases need to be added. Signed-off-by: amzn-sj * 1. Add test case for testing nested imports. 2. Initialize rapidjson value to fix assert. 3. Fix bug found in merge patch creation. Signed-off-by: amzn-sj * 1. Update the Resolver class member functions to return proper result codes. 2. Add the wrapper functions for resolving/restoring imports to the JsonSerialization class. 3. Add new test case. Signed-off-by: amzn-sj * Add test cases for import + patches. Fix bug found when patching import. Rename test cases. Signed-off-by: amzn-sj * 1. Add ApplyPatch() function to BaseJsonImporter. 2. Move patch logic out of ResolveImport() and into ApplyPatch() 3. Get rid of the custom RestoreImport implementation in the tests since it was the same as the base version. 4. Add test case for patching nested imports. 5. Update merge patch outcome reporting logic to work for nested object patches. Signed-off-by: amzn-sj * 1. Add a CreatePatch() function to BaseJsonImporter to match the ApplyPatch() function. 2. Reorganize some responsibilities between RestoreImports(), RestoreImport() and CreatePatch() to make ResolveImports() and RestoreImports() more symmetrical. Signed-off-by: amzn-sj * Combine result code in code path where we add empty object to path Signed-off-by: amzn-sj * Add test case for inserting a new import into an existing object. Signed-off-by: amzn-sj * Use == instead of Compare() for comparing file paths. Signed-off-by: amzn-sj * Address some PR feedback. Signed-off-by: amzn-sj * Address additional PR feedback Signed-off-by: amzn-sj * Add missing includes to fix non-unity build Signed-off-by: amzn-sj * Fix build error. Address additional feedback. Signed-off-by: amzn-sj --- .../Serialization/Json/JsonImporter.cpp | 254 +++++++++++ .../AzCore/Serialization/Json/JsonImporter.h | 108 +++++ .../AzCore/Serialization/Json/JsonMerger.cpp | 19 +- .../Serialization/Json/JsonSerialization.cpp | 60 ++- .../Serialization/Json/JsonSerialization.h | 27 +- .../Json/JsonSerializationResult.cpp | 3 + .../Json/JsonSerializationResult.h | 3 +- .../AzCore/AzCore/azcore_files.cmake | 2 + .../Json/TestCases_Importing.cpp | 413 ++++++++++++++++++ .../AzCore/Tests/azcoretests_files.cmake | 1 + .../AzTest/Platform/Mac/Platform_Mac.cpp | 12 +- 11 files changed, 886 insertions(+), 16 deletions(-) create mode 100644 Code/Framework/AzCore/AzCore/Serialization/Json/JsonImporter.cpp create mode 100644 Code/Framework/AzCore/AzCore/Serialization/Json/JsonImporter.h create mode 100644 Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Importing.cpp diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonImporter.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonImporter.cpp new file mode 100644 index 0000000000..2c856fb4b0 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonImporter.cpp @@ -0,0 +1,254 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +namespace AZ +{ + JsonSerializationResult::ResultCode JsonImportResolver::ResolveNestedImports(rapidjson::Value& jsonDoc, + rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack, + JsonImportSettings& settings, const AZ::IO::FixedMaxPath& importPath, StackedString& element) + { + using namespace JsonSerializationResult; + + for (auto& path : importPathStack) + { + if (importPath == path) + { + return settings.m_reporting( + AZStd::string::format("'%s' was already imported in this chain. This indicates a cyclic dependency.", importPath.c_str()), + ResultCode(Tasks::Import, Outcomes::Catastrophic), element); + } + } + + importPathStack.push_back(importPath); + AZ::StackedString importElement(AZ::StackedString::Format::JsonPointer); + JsonImportSettings nestedImportSettings; + nestedImportSettings.m_importer = settings.m_importer; + nestedImportSettings.m_reporting = settings.m_reporting; + nestedImportSettings.m_resolveFlags = ImportTracking::Dependencies; + ResultCode result = ResolveImports(jsonDoc, allocator, importPathStack, nestedImportSettings, importElement); + importPathStack.pop_back(); + + if (result.GetOutcome() == Outcomes::Catastrophic) + { + return result; + } + + return ResultCode(Tasks::Import, Outcomes::Success); + } + + JsonSerializationResult::ResultCode JsonImportResolver::ResolveImports(rapidjson::Value& jsonDoc, + rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack, + JsonImportSettings& settings, StackedString& element) + { + using namespace JsonSerializationResult; + + if (jsonDoc.IsObject()) + { + for (auto& field : jsonDoc.GetObject()) + { + if(strncmp(field.name.GetString(), JsonSerialization::ImportDirectiveIdentifier, field.name.GetStringLength()) == 0) + { + const rapidjson::Value& importDirective = field.value; + AZ::IO::FixedMaxPath importAbsPath = importPathStack.back(); + importAbsPath.RemoveFilename(); + AZStd::string importName; + if (importDirective.IsObject()) + { + auto filenameField = importDirective.FindMember("filename"); + if (filenameField != importDirective.MemberEnd()) + { + importName = AZStd::string(filenameField->value.GetString(), filenameField->value.GetStringLength()); + } + } + else + { + importName = AZStd::string(importDirective.GetString(), importDirective.GetStringLength()); + } + importAbsPath.Append(importName); + + rapidjson::Value patch; + ResultCode resolveResult = settings.m_importer->ResolveImport(&jsonDoc, patch, importDirective, importAbsPath, allocator); + if (resolveResult.GetOutcome() == Outcomes::Catastrophic) + { + return resolveResult; + } + + if ((settings.m_resolveFlags & ImportTracking::Imports) == ImportTracking::Imports) + { + rapidjson::Pointer path(element.Get().data(), element.Get().size()); + settings.m_importer->AddImportDirective(path, importName); + } + if ((settings.m_resolveFlags & ImportTracking::Dependencies) == ImportTracking::Dependencies) + { + settings.m_importer->AddImportedFile(importAbsPath.String()); + } + + ResultCode result = ResolveNestedImports(jsonDoc, allocator, importPathStack, settings, importAbsPath, element); + if (result.GetOutcome() == Outcomes::Catastrophic) + { + return result; + } + settings.m_importer->ApplyPatch(jsonDoc, patch, allocator); + } + else if (field.value.IsObject() || field.value.IsArray()) + { + ScopedStackedString entryName(element, AZStd::string_view(field.name.GetString(), field.name.GetStringLength())); + ResultCode result = ResolveImports(field.value, allocator, importPathStack, settings, element); + if (result.GetOutcome() == Outcomes::Catastrophic) + { + return result; + } + } + } + } + else if(jsonDoc.IsArray()) + { + int index = 0; + for (rapidjson::Value::ValueIterator elem = jsonDoc.Begin(); elem != jsonDoc.End(); ++elem, ++index) + { + if (!elem->IsObject() && !elem->IsArray()) + { + continue; + } + ScopedStackedString entryName(element, index); + ResultCode result = ResolveImports(*elem, allocator, importPathStack, settings, element); + if (result.GetOutcome() == Outcomes::Catastrophic) + { + return result; + } + } + } + + return ResultCode(Tasks::Import, Outcomes::Success); + } + + JsonSerializationResult::ResultCode JsonImportResolver::RestoreImports(rapidjson::Value& jsonDoc, + rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings) + { + using namespace JsonSerializationResult; + + if (jsonDoc.IsObject() || jsonDoc.IsArray()) + { + const BaseJsonImporter::ImportDirectivesList& importDirectives = settings.m_importer->GetImportDirectives(); + for (auto& import : importDirectives) + { + rapidjson::Pointer importPtr = import.first; + rapidjson::Value* currentValue = importPtr.Get(jsonDoc); + + rapidjson::Value importedValue(rapidjson::kObjectType); + importedValue.AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), rapidjson::StringRef(import.second.c_str()), allocator); + ResultCode resolveResult = JsonSerialization::ResolveImports(importedValue, allocator, settings); + if (resolveResult.GetOutcome() == Outcomes::Catastrophic) + { + return resolveResult; + } + + rapidjson::Value patch; + settings.m_importer->CreatePatch(patch, importedValue, *currentValue, allocator); + settings.m_importer->RestoreImport(currentValue, patch, allocator, import.second); + } + } + + return ResultCode(Tasks::Import, Outcomes::Success); + } + + JsonSerializationResult::ResultCode BaseJsonImporter::ResolveImport(rapidjson::Value* importPtr, + rapidjson::Value& patch, const rapidjson::Value& importDirective, + const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator) + { + using namespace JsonSerializationResult; + + auto importedObject = JsonSerializationUtils::ReadJsonFile(importedFilePath.Native()); + if (importedObject.IsSuccess()) + { + rapidjson::Value& importedDoc = importedObject.GetValue(); + + if (importDirective.IsObject()) + { + auto patchField = importDirective.FindMember("patch"); + if (patchField != importDirective.MemberEnd()) + { + patch.CopyFrom(patchField->value, allocator); + } + } + + importPtr->CopyFrom(importedDoc, allocator); + } + else + { + return ResultCode(Tasks::Import, Outcomes::Catastrophic); + } + + return ResultCode(Tasks::Import, Outcomes::Success); + } + + JsonSerializationResult::ResultCode BaseJsonImporter::RestoreImport(rapidjson::Value* importPtr, + rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator, const AZStd::string& importFilename) + { + using namespace JsonSerializationResult; + + importPtr->SetObject(); + if ((patch.IsObject() && patch.MemberCount() > 0) || (patch.IsArray() && !patch.Empty())) + { + rapidjson::Value importDirective(rapidjson::kObjectType); + importDirective.AddMember(rapidjson::StringRef("filename"), rapidjson::StringRef(importFilename.c_str()), allocator); + importDirective.AddMember(rapidjson::StringRef("patch"), patch, allocator); + importPtr->AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), importDirective, allocator); + } + else + { + importPtr->AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), rapidjson::StringRef(importFilename.c_str()), allocator); + } + + return ResultCode(Tasks::Import, Outcomes::Success); + } + + JsonSerializationResult::ResultCode BaseJsonImporter::ApplyPatch(rapidjson::Value& target, + const rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator) + { + using namespace JsonSerializationResult; + + if ((patch.IsObject() && patch.MemberCount() > 0) || (patch.IsArray() && !patch.Empty())) + { + return AZ::JsonSerialization::ApplyPatch(target, allocator, patch, JsonMergeApproach::JsonMergePatch); + } + + return ResultCode(Tasks::Import, Outcomes::Success); + } + + JsonSerializationResult::ResultCode BaseJsonImporter::CreatePatch(rapidjson::Value& patch, + const rapidjson::Value& source, const rapidjson::Value& target, + rapidjson::Document::AllocatorType& allocator) + { + return JsonSerialization::CreatePatch(patch, allocator, source, target, JsonMergeApproach::JsonMergePatch); + } + + void BaseJsonImporter::AddImportDirective(const rapidjson::Pointer& jsonPtr, AZStd::string importFile) + { + m_importDirectives.emplace_back(jsonPtr, AZStd::move(importFile)); + } + + void BaseJsonImporter::AddImportedFile(AZStd::string importedFile) + { + m_importedFiles.insert(AZStd::move(importedFile)); + } + + const BaseJsonImporter::ImportDirectivesList& BaseJsonImporter::GetImportDirectives() + { + return m_importDirectives; + } + + const BaseJsonImporter::ImportedFilesList& BaseJsonImporter::GetImportedFiles() + { + return m_importedFiles; + } +} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonImporter.h b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonImporter.h new file mode 100644 index 0000000000..ef53e265d8 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonImporter.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AZ +{ + struct JsonImportSettings; + + class BaseJsonImporter + { + public: + AZ_RTTI(BaseJsonImporter, "{7B225807-7B43-430F-8B11-C794DCF5ACA5}"); + + using ImportDirectivesList = AZStd::vector>; + using ImportedFilesList = AZStd::unordered_set; + + virtual JsonSerializationResult::ResultCode ResolveImport(rapidjson::Value* importPtr, + rapidjson::Value& patch, const rapidjson::Value& importDirective, + const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator); + + virtual JsonSerializationResult::ResultCode RestoreImport(rapidjson::Value* importPtr, + rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator, + const AZStd::string& importFilename); + + virtual JsonSerializationResult::ResultCode ApplyPatch(rapidjson::Value& target, + const rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator); + + virtual JsonSerializationResult::ResultCode CreatePatch(rapidjson::Value& patch, + const rapidjson::Value& source, const rapidjson::Value& target, + rapidjson::Document::AllocatorType& allocator); + + void AddImportDirective(const rapidjson::Pointer& jsonPtr, AZStd::string importFile); + const ImportDirectivesList& GetImportDirectives(); + + void AddImportedFile(AZStd::string importedFile); + const ImportedFilesList& GetImportedFiles(); + + virtual ~BaseJsonImporter() = default; + + protected: + + ImportDirectivesList m_importDirectives; + ImportedFilesList m_importedFiles; + }; + + enum class ImportTracking : AZ::u8 + { + None = 0, + Dependencies = (1<<0), + Imports = (1<<1), + All = (Dependencies | Imports) + }; + AZ_DEFINE_ENUM_BITWISE_OPERATORS(ImportTracking); + + class JsonImportResolver final + { + public: + + using ImportPathStack = AZStd::vector; + + JsonImportResolver() = delete; + JsonImportResolver& operator=(const JsonImportResolver& rhs) = delete; + JsonImportResolver& operator=(JsonImportResolver&& rhs) = delete; + JsonImportResolver(const JsonImportResolver& rhs) = delete; + JsonImportResolver(JsonImportResolver&& rhs) = delete; + ~JsonImportResolver() = delete; + + static JsonSerializationResult::ResultCode ResolveImports(rapidjson::Value& jsonDoc, + rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack, + JsonImportSettings& settings, StackedString& element); + + static JsonSerializationResult::ResultCode RestoreImports(rapidjson::Value& jsonDoc, + rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings); + + private: + + static JsonSerializationResult::ResultCode ResolveNestedImports(rapidjson::Value& jsonDoc, + rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack, + JsonImportSettings& settings, const AZ::IO::FixedMaxPath& importPath, StackedString& element); + }; + + + struct JsonImportSettings final + { + JsonSerializationResult::JsonIssueCallback m_reporting; + + BaseJsonImporter* m_importer = nullptr; + + ImportTracking m_resolveFlags = ImportTracking::All; + + AZ::IO::FixedMaxPath m_loadedJsonPath; + }; +} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp index 45549b8078..a9b2d2fefa 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp @@ -706,7 +706,7 @@ namespace AZ rapidjson::Value(rapidjson::kNullType), field.value, element, settings); } - if (result.GetOutcome() == Outcomes::Success) + if (result.GetOutcome() == Outcomes::Success || result.GetOutcome() == Outcomes::PartialDefaults) { rapidjson::Value name; name.CopyFrom(field.name, allocator, true); @@ -717,6 +717,10 @@ namespace AZ { return result; } + else + { + resultCode.Combine(result); + } } // Do an extra pass to find all the fields that are removed. @@ -751,7 +755,7 @@ namespace AZ rapidjson::Value value; ResultCode result = CreateMergePatchInternal(value, allocator, rapidjson::Value(rapidjson::kNullType), field.value, element, settings); - if (result.GetOutcome() == Outcomes::Success) + if (result.GetOutcome() == Outcomes::Success || result.GetOutcome() == Outcomes::PartialDefaults) { rapidjson::Value name; name.CopyFrom(field.name, allocator, true); @@ -762,11 +766,20 @@ namespace AZ { return result; } + else + { + resultCode.Combine(result); + } + } + + if (target.MemberCount() == 0) + { + resultCode.Combine(settings.m_reporting("Added empty object to JSON Merge Patch.", + ResultCode(Tasks::CreatePatch, Outcomes::Success), element)); } } patch = AZStd::move(resultValue); - resultCode.Combine(ResultCode(Tasks::CreatePatch, Outcomes::Success)); return resultCode; } else diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerialization.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerialization.cpp index bc07f684f6..db76e46f2b 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerialization.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerialization.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -19,11 +20,6 @@ namespace AZ { - const char* JsonSerialization::TypeIdFieldIdentifier = "$type"; - const char* JsonSerialization::DefaultStringIdentifier = "{}"; - const char* JsonSerialization::KeyFieldIdentifier = "Key"; - const char* JsonSerialization::ValueFieldIdentifier = "Value"; - namespace JsonSerializationInternal { template @@ -394,6 +390,60 @@ namespace AZ } } + JsonSerializationResult::ResultCode JsonSerialization::ResolveImports( + rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings) + { + using namespace JsonSerializationResult; + + if (settings.m_importer == nullptr) + { + AZ_Assert(false, "Importer object needs to be provided"); + return ResultCode(Tasks::Import, Outcomes::Catastrophic); + } + + AZStd::string scratchBuffer; + auto issueReportingCallback = [&scratchBuffer](AZStd::string_view message, ResultCode result, AZStd::string_view target) -> ResultCode + { + return JsonSerialization::DefaultIssueReporter(scratchBuffer, message, result, target); + }; + if (!settings.m_reporting) + { + settings.m_reporting = issueReportingCallback; + } + + JsonImportResolver::ImportPathStack importPathStack; + importPathStack.push_back(settings.m_loadedJsonPath); + StackedString element(StackedString::Format::JsonPointer); + + return JsonImportResolver::ResolveImports(jsonDoc, allocator, importPathStack, settings, element); + } + + JsonSerializationResult::ResultCode JsonSerialization::RestoreImports( + rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings) + { + using namespace JsonSerializationResult; + + if (settings.m_importer == nullptr) + { + AZ_Assert(false, "Importer object needs to be provided"); + return ResultCode(Tasks::Import, Outcomes::Catastrophic); + } + + AZStd::string scratchBuffer; + auto issueReportingCallback = [&scratchBuffer](AZStd::string_view message, ResultCode result, AZStd::string_view target) -> ResultCode + { + return JsonSerialization::DefaultIssueReporter(scratchBuffer, message, result, target); + }; + if (!settings.m_reporting) + { + settings.m_reporting = issueReportingCallback; + } + + settings.m_resolveFlags = ImportTracking::None; + + return JsonImportResolver::RestoreImports(jsonDoc, allocator, settings); + } + JsonSerializationResult::ResultCode JsonSerialization::DefaultIssueReporter(AZStd::string& scratchBuffer, AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view path) { diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerialization.h b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerialization.h index c85847ac78..d961953a1d 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerialization.h +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerialization.h @@ -18,6 +18,8 @@ namespace AZ { class BaseJsonSerializer; + + struct JsonImportSettings; enum class JsonMergeApproach { @@ -51,10 +53,11 @@ namespace AZ class JsonSerialization final { public: - static const char* TypeIdFieldIdentifier; - static const char* DefaultStringIdentifier; - static const char* KeyFieldIdentifier; - static const char* ValueFieldIdentifier; + static constexpr const char* TypeIdFieldIdentifier = "$type"; + static constexpr const char* DefaultStringIdentifier = "{}"; + static constexpr const char* KeyFieldIdentifier = "Key"; + static constexpr const char* ValueFieldIdentifier = "Value"; + static constexpr const char* ImportDirectiveIdentifier = "$import"; //! Merges two json values together by applying "patch" to "target" using the selected merge algorithm. //! This version of ApplyPatch is destructive to "target". If the patch can't be correctly applied it will @@ -284,6 +287,22 @@ namespace AZ //! @return An enum containing less, equal or greater. In case of an error, the value for the enum will "error". static JsonSerializerCompareResult Compare(const rapidjson::Value& lhs, const rapidjson::Value& rhs); + //! Resolves all import directives, including nested imports, in the given document. An importer object needs to be passed + //! in through the settings. + //! @param jsonDoc The json document in which to resolve imports. + //! @param allocator The allocator associated with the json document. + //! @param settings Additional settings that control the way the imports are resolved. + static JsonSerializationResult::ResultCode ResolveImports( + rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings); + + //! Restores all import directives that were present in the json document. The same importer object that was + //! passed into ResolveImports through the settings needs to be passed here through settings as well. + //! @param jsonDoc The json document in which to restore imports. + //! @param allocator The allocator associated with the json document. + //! @param settings Additional settings that control the way the imports are restored. + static JsonSerializationResult::ResultCode RestoreImports( + rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings); + private: JsonSerialization() = delete; ~JsonSerialization() = delete; diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.cpp index 7e84aced7b..822c1c43d5 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.cpp @@ -69,6 +69,9 @@ namespace AZ case Tasks::CreatePatch: target.append("a create patch operation "); break; + case Tasks::Import: + target.append("an import operation"); + break; default: target.append("an unknown operation "); break; diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.h b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.h index 8590971a1c..204c40b8ca 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.h +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.h @@ -32,7 +32,8 @@ namespace AZ ReadField, //!< Task to read a field from JSON to a value. WriteValue, //!< Task to write a value to a JSON field. Merge, //!< Task to merge two JSON values/documents together. - CreatePatch //!< Task to create a patch to transform one value/document to another. + CreatePatch, //!< Task to create a patch to transform one value/document to another. + Import //!< Task to import a JSON document. }; //! Describes how the task was processed. diff --git a/Code/Framework/AzCore/AzCore/azcore_files.cmake b/Code/Framework/AzCore/AzCore/azcore_files.cmake index 4d95ddf098..41229429f2 100644 --- a/Code/Framework/AzCore/AzCore/azcore_files.cmake +++ b/Code/Framework/AzCore/AzCore/azcore_files.cmake @@ -522,6 +522,8 @@ set(FILES Serialization/Json/IntSerializer.cpp Serialization/Json/JsonDeserializer.h Serialization/Json/JsonDeserializer.cpp + Serialization/Json/JsonImporter.cpp + Serialization/Json/JsonImporter.h Serialization/Json/JsonMerger.h Serialization/Json/JsonMerger.cpp Serialization/Json/JsonSerialization.h diff --git a/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Importing.cpp b/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Importing.cpp new file mode 100644 index 0000000000..5121efa5e3 --- /dev/null +++ b/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Importing.cpp @@ -0,0 +1,413 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +namespace JsonSerializationTests +{ + class JsonImportingTests; + + class JsonImporterCustom + : public AZ::BaseJsonImporter + { + public: + AZ_RTTI(JsonImporterCustom, "{003F5896-71E0-4A50-A14F-08C319B06AD0}"); + + + AZ::JsonSerializationResult::ResultCode ResolveImport(rapidjson::Value* importPtr, + rapidjson::Value& patch, const rapidjson::Value& importDirective, + const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator) override; + + JsonImporterCustom(JsonImportingTests* tests) + { + testClass = tests; + } + + private: + JsonImportingTests* testClass; + }; + + class JsonImportingTests + : public BaseJsonSerializerFixture + { + public: + void SetUp() override + { + BaseJsonSerializerFixture::SetUp(); + } + + void TearDown() override + { + BaseJsonSerializerFixture::TearDown(); + } + + void GetTestDocument(const AZStd::string& docName, rapidjson::Document& out) + { + const char *objectJson = R"({ + "field_1" : "value_1", + "field_2" : "value_2", + "field_3" : "value_3" + })"; + + const char *arrayJson = R"([ + { "element_1" : "value_1" }, + { "element_2" : "value_2" }, + { "element_3" : "value_3" } + ])"; + + const char *nestedImportJson = R"({ + "desc" : "Nested Import", + "obj" : {"$import" : "object.json"} + })"; + + const char *nestedImportCycle1Json = R"({ + "desc" : "Nested Import Cycle 1", + "obj" : {"$import" : "nested_import_c2.json"} + })"; + + const char *nestedImportCycle2Json = R"({ + "desc" : "Nested Import Cycle 2", + "obj" : {"$import" : "nested_import_c1.json"} + })"; + + if (docName.compare("object.json") == 0) + { + out.Parse(objectJson); + ASSERT_FALSE(out.HasParseError()); + } + else if (docName.compare("array.json") == 0) + { + out.Parse(arrayJson); + ASSERT_FALSE(out.HasParseError()); + } + else if (docName.compare("nested_import.json") == 0) + { + out.Parse(nestedImportJson); + ASSERT_FALSE(out.HasParseError()); + } + else if (docName.compare("nested_import_c1.json") == 0) + { + out.Parse(nestedImportCycle1Json); + ASSERT_FALSE(out.HasParseError()); + } + else if (docName.compare("nested_import_c2.json") == 0) + { + out.Parse(nestedImportCycle2Json); + ASSERT_FALSE(out.HasParseError()); + } + } + + protected: + void TestImportLoadStore(const char* input, const char* expectedImportedValue) + { + m_jsonDocument->Parse(input); + ASSERT_FALSE(m_jsonDocument->HasParseError()); + + JsonImporterCustom* importerObj = new JsonImporterCustom(this); + + rapidjson::Document expectedOutcome; + expectedOutcome.Parse(expectedImportedValue); + ASSERT_FALSE(expectedOutcome.HasParseError()); + + TestResolveImports(importerObj); + + Expect_DocStrEq(m_jsonDocument->GetObject(), expectedOutcome.GetObject()); + + rapidjson::Document originalInput; + originalInput.Parse(input); + ASSERT_FALSE(originalInput.HasParseError()); + + TestRestoreImports(importerObj); + + Expect_DocStrEq(m_jsonDocument->GetObject(), originalInput.GetObject()); + + m_jsonDocument->SetObject(); + delete importerObj; + } + + void TestImportCycle(const char* input) + { + m_jsonDocument->Parse(input); + ASSERT_FALSE(m_jsonDocument->HasParseError()); + + JsonImporterCustom* importerObj = new JsonImporterCustom(this); + + AZ::JsonSerializationResult::ResultCode result = TestResolveImports(importerObj); + + EXPECT_EQ(result.GetOutcome(), AZ::JsonSerializationResult::Outcomes::Catastrophic); + + m_jsonDocument->SetObject(); + delete importerObj; + } + + void TestInsertNewImport(const char* input, const char* expectedRestoredValue) + { + m_jsonDocument->Parse(input); + ASSERT_FALSE(m_jsonDocument->HasParseError()); + + JsonImporterCustom* importerObj = new JsonImporterCustom(this); + + TestResolveImports(importerObj); + + importerObj->AddImportDirective(rapidjson::Pointer("/object_2"), "object.json"); + + rapidjson::Document expectedOutput; + expectedOutput.Parse(expectedRestoredValue); + ASSERT_FALSE(expectedOutput.HasParseError()); + + TestRestoreImports(importerObj); + + Expect_DocStrEq(m_jsonDocument->GetObject(), expectedOutput.GetObject()); + + m_jsonDocument->SetObject(); + delete importerObj; + } + + AZ::JsonSerializationResult::ResultCode TestResolveImports(JsonImporterCustom* importerObj) + { + AZ::JsonImportSettings settings; + settings.m_importer = importerObj; + + return AZ::JsonSerialization::ResolveImports(m_jsonDocument->GetObject(), m_jsonDocument->GetAllocator(), settings); + } + + AZ::JsonSerializationResult::ResultCode TestRestoreImports(JsonImporterCustom* importerObj) + { + AZ::JsonImportSettings settings; + settings.m_importer = importerObj; + + return AZ::JsonSerialization::RestoreImports(m_jsonDocument->GetObject(), m_jsonDocument->GetAllocator(), settings); + } + }; + + AZ::JsonSerializationResult::ResultCode JsonImporterCustom::ResolveImport(rapidjson::Value* importPtr, + rapidjson::Value& patch, const rapidjson::Value& importDirective, const AZ::IO::FixedMaxPath& importedFilePath, + rapidjson::Document::AllocatorType& allocator) + { + AZ::JsonSerializationResult::ResultCode resultCode(AZ::JsonSerializationResult::Tasks::Import); + + rapidjson::Document importedDoc; + testClass->GetTestDocument(importedFilePath.String(), importedDoc); + + if (importDirective.IsObject()) + { + auto patchField = importDirective.FindMember("patch"); + if (patchField != importDirective.MemberEnd()) + { + patch.CopyFrom(patchField->value, allocator); + } + } + + importPtr->CopyFrom(importedDoc, allocator); + + return resultCode; + } + + // Test Cases + + TEST_F(JsonImportingTests, ImportSimpleObjectTest) + { + const char* inputFile = R"( + { + "name" : "simple_object_import", + "object": {"$import" : "object.json"} + } + )"; + + const char* expectedOutput = R"( + { + "name" : "simple_object_import", + "object": { + "field_1" : "value_1", + "field_2" : "value_2", + "field_3" : "value_3" + } + } + )"; + + TestImportLoadStore(inputFile, expectedOutput); + } + + TEST_F(JsonImportingTests, ImportSimpleObjectPatchTest) + { + const char* inputFile = R"( + { + "name" : "simple_object_import", + "object": { + "$import" : { + "filename" : "object.json", + "patch" : { "field_2" : "patched_value" } + } + } + } + )"; + + const char* expectedOutput = R"( + { + "name" : "simple_object_import", + "object": { + "field_1" : "value_1", + "field_2" : "patched_value", + "field_3" : "value_3" + } + } + )"; + + TestImportLoadStore(inputFile, expectedOutput); + } + + TEST_F(JsonImportingTests, ImportSimpleArrayTest) + { + const char* inputFile = R"( + { + "name" : "simple_array_import", + "object": {"$import" : "array.json"} + } + )"; + + const char* expectedOutput = R"( + { + "name" : "simple_array_import", + "object": [ + { "element_1" : "value_1" }, + { "element_2" : "value_2" }, + { "element_3" : "value_3" } + ] + } + )"; + + TestImportLoadStore(inputFile, expectedOutput); + } + + TEST_F(JsonImportingTests, ImportSimpleArrayPatchTest) + { + const char* inputFile = R"( + { + "name" : "simple_array_import", + "object": { + "$import" : { + "filename" : "array.json", + "patch" : [ { "element_1" : "patched_value" } ] + } + } + } + )"; + + const char* expectedOutput = R"( + { + "name" : "simple_array_import", + "object": [ + { "element_1" : "patched_value" } + ] + } + )"; + + TestImportLoadStore(inputFile, expectedOutput); + } + + TEST_F(JsonImportingTests, NestedImportTest) + { + const char* inputFile = R"( + { + "name" : "nested_import", + "object": {"$import" : "nested_import.json"} + } + )"; + + const char* expectedOutput = R"( + { + "name" : "nested_import", + "object": { + "desc" : "Nested Import", + "obj" : { + "field_1" : "value_1", + "field_2" : "value_2", + "field_3" : "value_3" + } + } + } + )"; + + TestImportLoadStore(inputFile, expectedOutput); + } + + TEST_F(JsonImportingTests, NestedImportPatchTest) + { + const char* inputFile = R"( + { + "name" : "nested_import", + "object": { + "$import" : { + "filename" : "nested_import.json", + "patch" : { "obj" : { "field_3" : "patched_value" } } + } + } + } + )"; + + const char* expectedOutput = R"( + { + "name" : "nested_import", + "object": { + "desc" : "Nested Import", + "obj" : { + "field_1" : "value_1", + "field_2" : "value_2", + "field_3" : "patched_value" + } + } + } + )"; + + TestImportLoadStore(inputFile, expectedOutput); + } + + TEST_F(JsonImportingTests, NestedImportCycleTest) + { + const char* inputFile = R"( + { + "name" : "nested_import_cycle", + "object": {"$import" : "nested_import_c1.json"} + } + )"; + + TestImportCycle(inputFile); + } + + TEST_F(JsonImportingTests, InsertNewImportTest) + { + const char* inputFile = R"( + { + "name" : "simple_object_import", + "object_1": {"$import" : "object.json"}, + "object_2": { + "field_1" : "other_value", + "field_2" : "value_2", + "field_3" : "value_3" + } + } + )"; + + const char* expectedOutput = R"( + { + "name" : "simple_object_import", + "object_1": {"$import" : "object.json"}, + "object_2": { + "$import" : { + "filename" : "object.json", + "patch" : { "field_1" : "other_value" } + } + } + } + )"; + + TestInsertNewImport(inputFile, expectedOutput); + } +} diff --git a/Code/Framework/AzCore/Tests/azcoretests_files.cmake b/Code/Framework/AzCore/Tests/azcoretests_files.cmake index cc6000209f..d39595c45e 100644 --- a/Code/Framework/AzCore/Tests/azcoretests_files.cmake +++ b/Code/Framework/AzCore/Tests/azcoretests_files.cmake @@ -121,6 +121,7 @@ set(FILES Serialization/Json/TestCases_Classes.cpp Serialization/Json/TestCases_Compare.cpp Serialization/Json/TestCases_Enum.cpp + Serialization/Json/TestCases_Importing.cpp Serialization/Json/TestCases_Patching.cpp Serialization/Json/TestCases_Pointers.h Serialization/Json/TestCases_Pointers.cpp diff --git a/Code/Framework/AzTest/AzTest/Platform/Mac/Platform_Mac.cpp b/Code/Framework/AzTest/AzTest/Platform/Mac/Platform_Mac.cpp index 864bc3f52a..7581986950 100644 --- a/Code/Framework/AzTest/AzTest/Platform/Mac/Platform_Mac.cpp +++ b/Code/Framework/AzTest/AzTest/Platform/Mac/Platform_Mac.cpp @@ -7,6 +7,7 @@ */ #include #include +#include #include #include @@ -20,11 +21,16 @@ public: explicit ModuleHandle(const std::string& lib) : m_libHandle(nullptr) { - std::string libext = lib; - if (!AZ::Test::EndsWith(libext, ".dylib")) + AZ::IO::FixedMaxPath libext = AZStd::string_view{ lib.c_str(), lib.size() }; + if (!libext.Stem().Native().starts_with(AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX)) { - libext += ".dylib"; + libext = AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX + libext.Native(); } + if (libext.Extension() != AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION) + { + libext.Native() += AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION; + } + m_libHandle = dlopen(libext.c_str(), RTLD_NOW); const char* error = dlerror(); if (error) From 3a6cc2498b0551c6948d103b3566d0e50dc7229f Mon Sep 17 00:00:00 2001 From: jckand-amzn <82226555+jckand-amzn@users.noreply.github.com> Date: Thu, 21 Oct 2021 11:16:48 -0500 Subject: [PATCH 21/27] Skipping GraphUpdate test due to LC node issue in Debug configuration (#4847) * Skipping GraphUpdate test due to LC node issue in Debug configuration Signed-off-by: jckand-amzn * Reverting test type for GraphUpdates test to EditorSharedTest Signed-off-by: jckand-amzn * Updating xfail reason for GraphUpdates test with GitHub issue link Signed-off-by: jckand-amzn --- .../largeworlds/landscape_canvas/TestSuite_Main_Optimized.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Main_Optimized.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Main_Optimized.py index 0461ff2647..1c3652cae9 100644 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Main_Optimized.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Main_Optimized.py @@ -9,6 +9,7 @@ import os import pytest import ly_test_tools.environment.file_system as file_system +import ly_test_tools._internal.pytest_plugin as internal_plugin from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite @@ -79,6 +80,8 @@ class TestAutomation(EditorTestSuite): class test_LandscapeCanvas_GradientNodes_EntityRemovedOnNodeDelete(EditorSharedTest): from .EditorScripts import GradientNodes_EntityRemovedOnNodeDelete as test_module + @pytest.mark.skipif("debug" == os.path.basename(internal_plugin.build_directory), + reason="https://github.com/o3de/o3de/issues/4872") class test_LandscapeCanvas_GraphUpdates_UpdateComponents(EditorSharedTest): from .EditorScripts import GraphUpdates_UpdateComponents as test_module From e008ba857912eec2093d74b0eaf45a91ae44507e Mon Sep 17 00:00:00 2001 From: Scott Romero <24445312+AMZN-ScottR@users.noreply.github.com> Date: Thu, 21 Oct 2021 10:22:24 -0700 Subject: [PATCH 22/27] [development] Fixed Profiler ImGui module name when building monolithic (#4848) Signed-off-by: AMZN-ScottR <24445312+AMZN-ScottR@users.noreply.github.com> --- Gems/Profiler/Code/Source/ProfilerImGuiModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Profiler/Code/Source/ProfilerImGuiModule.cpp b/Gems/Profiler/Code/Source/ProfilerImGuiModule.cpp index 38cfee2c26..f7a3d4d69d 100644 --- a/Gems/Profiler/Code/Source/ProfilerImGuiModule.cpp +++ b/Gems/Profiler/Code/Source/ProfilerImGuiModule.cpp @@ -46,4 +46,4 @@ namespace Profiler }; }// namespace Profiler -AZ_DECLARE_MODULE_CLASS(Gem_Profiler, Profiler::ProfilerImGuiModule) +AZ_DECLARE_MODULE_CLASS(Gem_ProfilerImGui, Profiler::ProfilerImGuiModule) From 64bbc700fa6be64c6ad572c8b8573e4d882ca69f Mon Sep 17 00:00:00 2001 From: Adi Bar-Lev <82479970+Adi-Amazon@users.noreply.github.com> Date: Thu, 21 Oct 2021 14:29:53 -0400 Subject: [PATCH 23/27] Hair - ShortCut rendering technique and pipeline with Marschner lighting model (#4871) - This rendering technique reduces the memory required per pipeline by 750MB compared to the PPLL technique!! - Using 3 screen buffers representing the closest 3 hair gragments depths. - Going through second geometry pass that disqualify any fragment further than the third depth, the technique blends the three closes shaders hair fragments. - Supports the Marschner lighting model (big change from the original TressFX 4.1 implementation) - The current technique is almost twice the performance of the PPLL but not quite as advacned when come to final visual quality. Remarks: Unlike the PPLL, this technique is still lacking some of the advanced features added to the PPLL such as 1. Back lobe (TT) conseal by depth comparison 2. Thickness dependency in light transfer (mainly TT) 3. Allowing TT transfer for thin separated hair strands (might be supported by default with no distinction) Signed-off-by: Adi-Amazon Signed-off-by: Adi-Amazon Signed-off-by: Adi-Amazon Signed-off-by: Adi-Amazon Co-authored-by: Adi-Amazon --- .../Passes/AtomTressFX_MainPipeline.pass | 6 +- .../Passes/AtomTressFX_PassTemplates.azasset | 23 ++ .../Assets/Passes/HairParentShortCutPass.pass | 391 ++++++++++++++++++ .../HairShortCutGeometryDepthAlpha.pass | 101 +++++ .../Passes/HairShortCutGeometryShading.pass | 155 +++++++ .../Passes/HairShortCutResolveColor.pass | 45 ++ .../Passes/HairShortCutResolveDepth.pass | 37 ++ .../{HairSrgs.azsli => HairComputeSrgs.azsli} | 2 +- .../Assets/Shaders/HairFullScreenUtils.azsli | 60 +++ .../Assets/Shaders/HairLighting.azsli | 17 +- .../Shaders/HairLightingEquations.azsli | 10 +- .../Assets/Shaders/HairRenderingFillPPLL.azsl | 42 +- .../Shaders/HairRenderingResolvePPLL.azsl | 23 +- .../HairShortCutGeometryDepthAlpha.azsl | 131 ++++++ .../HairShortCutGeometryDepthAlpha.shader | 45 ++ .../Shaders/HairShortCutGeometryShading.azsl | 176 ++++++++ .../HairShortCutGeometryShading.shader | 45 ++ .../Shaders/HairShortCutResolveColor.azsl | 63 +++ .../Shaders/HairShortCutResolveColor.shader | 41 ++ .../Shaders/HairShortCutResolveDepth.azsl | 65 +++ .../Shaders/HairShortCutResolveDepth.shader | 37 ++ .../Assets/Shaders/HairSimulationCompute.azsl | 2 +- ....azsli => HairSimulationComputeSrgs.azsli} | 4 +- .../Assets/Shaders/HairStrands.azsli | 37 +- .../Assets/Shaders/HairUtilities.azsli | 37 -- .../Code/Components/HairSystemComponent.cpp | 6 + .../Code/Passes/HairGeometryRasterPass.cpp | 13 +- .../Code/Passes/HairGeometryRasterPass.h | 3 +- .../Code/Passes/HairPPLLResolvePass.cpp | 33 +- .../Code/Passes/HairPPLLResolvePass.h | 13 +- .../Code/Passes/HairParentPass.cpp | 1 - .../HairShortCutGeometryDepthAlphaPass.cpp | 50 +++ .../HairShortCutGeometryDepthAlphaPass.h | 49 +++ .../HairShortCutGeometryShadingPass.cpp | 111 +++++ .../Passes/HairShortCutGeometryShadingPass.h | 69 ++++ .../Code/Rendering/HairFeatureProcessor.cpp | 171 ++++++-- .../Code/Rendering/HairFeatureProcessor.h | 28 +- .../Code/Rendering/HairRenderObject.cpp | 51 ++- .../Code/Rendering/HairRenderObject.h | 31 +- Gems/AtomTressFX/Hair_files.cmake | 56 ++- 40 files changed, 2055 insertions(+), 225 deletions(-) create mode 100644 Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass create mode 100644 Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryDepthAlpha.pass create mode 100644 Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryShading.pass create mode 100644 Gems/AtomTressFX/Assets/Passes/HairShortCutResolveColor.pass create mode 100644 Gems/AtomTressFX/Assets/Passes/HairShortCutResolveDepth.pass rename Gems/AtomTressFX/Assets/Shaders/{HairSrgs.azsli => HairComputeSrgs.azsli} (99%) create mode 100644 Gems/AtomTressFX/Assets/Shaders/HairFullScreenUtils.azsli create mode 100644 Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.azsl create mode 100644 Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.shader create mode 100644 Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.azsl create mode 100644 Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.shader create mode 100644 Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.azsl create mode 100644 Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.shader create mode 100644 Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.azsl create mode 100644 Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.shader rename Gems/AtomTressFX/Assets/Shaders/{HairSimulationSrgs.azsli => HairSimulationComputeSrgs.azsli} (99%) create mode 100644 Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.cpp create mode 100644 Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.h create mode 100644 Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.cpp create mode 100644 Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.h diff --git a/Gems/AtomTressFX/Assets/Passes/AtomTressFX_MainPipeline.pass b/Gems/AtomTressFX/Assets/Passes/AtomTressFX_MainPipeline.pass index 84bb85c3e1..9bfa746bf1 100644 --- a/Gems/AtomTressFX/Assets/Passes/AtomTressFX_MainPipeline.pass +++ b/Gems/AtomTressFX/Assets/Passes/AtomTressFX_MainPipeline.pass @@ -212,7 +212,11 @@ // instead of regular Depth as DepthStencil. Specifically, HairResolvePPLL.pass and the associated // .azsl file will need to be updated. "Name": "HairParentPass", - "TemplateName": "HairParentPassTemplate", + // Note: The following two lines represent the choice of rendering pipeline for the hair. + // You can either choose to use PPLL or ShortCut and accordingly change the flag + // 'm_usePPLLRenderTechnique' in the class 'HairFeatureProcessor.cpp' +// "TemplateName": "HairParentPassTemplate", + "TemplateName": "HairParentShortCutPassTemplate", "Enabled": true, "Connections": [ // Critical to keep DepthLinear as input - used to set the size of the Head PPLL image buffer. diff --git a/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassTemplates.azasset b/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassTemplates.azasset index a287664286..1580a4e1a9 100644 --- a/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassTemplates.azasset +++ b/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassTemplates.azasset @@ -8,6 +8,11 @@ "Name": "HairParentPassTemplate", "Path": "Passes/HairParentPass.pass" }, + { + "Name": "HairParentShortCutPassTemplate", + "Path": "Passes/HairParentShortCutPass.pass" + }, + { "Name": "HairGlobalShapeConstraintsComputePassTemplate", "Path": "Passes/HairGlobalShapeConstraintsCompute.pass" @@ -32,6 +37,7 @@ "Name": "HairUpdateFollowHairComputePassTemplate", "Path": "Passes/HairUpdateFollowHairCompute.pass" }, + { "Name": "HairPPLLRasterPassTemplate", "Path": "Passes/HairFillPPLL.pass" @@ -39,6 +45,23 @@ { "Name": "HairPPLLResolvePassTemplate", "Path": "Passes/HairResolvePPLL.pass" + }, + + { + "Name": "HairShortCutGeometryDepthAlphaPassTemplate", + "Path": "Passes/HairShortCutGeometryDepthAlpha.pass" + }, + { + "Name": "HairShortCutResolveDepthPassTemplate", + "Path": "Passes/HairShortCutResolveDepth.pass" + }, + { + "Name": "HairShortCutGeometryShadingPassTemplate", + "Path": "Passes/HairShortCutGeometryShading.pass" + }, + { + "Name": "HairShortCutResolveColorPassTemplate", + "Path": "Passes/HairShortCutResolveColor.pass" } ] } diff --git a/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass b/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass new file mode 100644 index 0000000000..7322611e2a --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass @@ -0,0 +1,391 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "HairParentShortCutPassTemplate", + "PassClass": "ParentPass", + "Slots": [ + { + "Name": "RenderTargetInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + }, + { // used for copy from MSAA to regular RT + "Name": "RenderTargetInputOnly", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + // This is the depth stencil buffer that is to be used by the fill pass + // to early reject pixels by depth and in the resolve pass to write the + // the hair depth + { + "Name": "Depth", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "DepthStencil" + }, + // Keep DepthLinear as input - used to set the size of the Head PPLL image buffer. + // If DepthLinear is not availbale - connect to another viewport (non MSAA) image. + { + "Name": "DepthLinearInput", + "SlotType": "InputOutput" + }, + { + "Name": "DepthLinear", + "SlotType": "Output" + }, + + // Lights & Shadows resources + { + "Name": "DirectionalShadowmap", + "SlotType": "Input" + }, + { + "Name": "DirectionalESM", + "SlotType": "Input" + }, + { + "Name": "ProjectedShadowmap", + "SlotType": "Input" + }, + { + "Name": "ProjectedESM", + "SlotType": "Input" + }, + { + "Name": "TileLightData", + "SlotType": "Input" + }, + { + "Name": "LightListRemapped", + "SlotType": "Input" + } + ], + "Connections": [ + { + "LocalSlot": "DepthLinear", + "AttachmentRef": { + "Pass": "DepthToDepthLinearPass", + "Attachment": "Output" + } + } + ], + "PassRequests": [ + { + "Name": "HairGlobalShapeConstraintsComputePass", + "TemplateName": "HairGlobalShapeConstraintsComputePassTemplate", + "Enabled": true + }, + { + "Name": "HairCalculateStrandLevelDataComputePass", + "TemplateName": "HairCalculateStrandLevelDataComputePassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairGlobalShapeConstraintsComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + } + ] + }, + { + "Name": "HairVelocityShockPropagationComputePass", + "TemplateName": "HairVelocityShockPropagationComputePassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairCalculateStrandLevelDataComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + } + ] + }, + { + "Name": "HairLocalShapeConstraintsComputePass", + "TemplateName": "HairLocalShapeConstraintsComputePassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairVelocityShockPropagationComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + } + ] + }, + { + "Name": "HairLengthConstraintsWindAndCollisionComputePass", + "TemplateName": "HairLengthConstraintsWindAndCollisionComputePassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairLocalShapeConstraintsComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + } + ] + }, + { + "Name": "HairUpdateFollowHairComputePass", + "TemplateName": "HairUpdateFollowHairComputePassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairLengthConstraintsWindAndCollisionComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + } + ] + }, + + // Render Target Copy from MS to Regular + { + "Name": "RenderTargetCopyPass", + "TemplateName": "FullscreenCopyTemplate", + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "RenderTargetInputOnly" + } + }, + { + "LocalSlot": "Output", + "AttachmentRef": { + "Pass": "This", + "Attachment": "Output" + } + } + ], + "ImageAttachments": [ + { + "Name": "Output", + "SizeSource": { + "Source": { + "Pass": "This", + "Attachment": "Input" + } + }, + "FormatSource": { + "Pass": "This", + "Attachment": "Input" + }, + "GenerateFullMipChain": false + } + ] + }, + + // Rendering Passes + { + "Name": "HairShortCutGeometryDepthAlphaPass", + "TemplateName": "HairShortCutGeometryDepthAlphaPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairUpdateFollowHairComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + }, + { + "LocalSlot": "Depth", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "InverseAlphaRTOutput", + "AttachmentRef": { + "Pass": "This", + "Attachment": "InverseAlphaRTOutput" + } + }, + { + "LocalSlot": "HairDepthsTextureArray", + "AttachmentRef": { + "Pass": "This", + "Attachment": "HairDepthsTextureArray" + } + } + ] + }, + + { + "Name": "HairShortCutResolveDepthPass", + "TemplateName": "HairShortCutResolveDepthPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "Depth", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "HairDepthsTextureArray", + "AttachmentRef": { + "Pass": "HairShortCutGeometryDepthAlphaPass", + "Attachment": "HairDepthsTextureArray" + } + } + ] + }, + + { + "Name": "HairShortCutGeometryShadingPass", + "TemplateName": "HairShortCutGeometryShadingPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "HairColorRenderTarget", + "AttachmentRef": { + "Pass": "This", + "Attachment": "HairColorRenderTarget" + } + }, + { // The final render target - this is MSAA mode RT - would it be cheaper to + // use non-MSAA and then copy? + "LocalSlot": "RenderTargetInputOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "RenderTargetInputOutput" + } + }, + { + "LocalSlot": "DepthLinear", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "DepthLinearInput" + } + }, + { + "LocalSlot": "Depth", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairUpdateFollowHairComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + }, + + // Shadows resources + { + "LocalSlot": "DirectionalShadowmap", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "DirectionalShadowmap" + } + }, + { + "LocalSlot": "DirectionalESM", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "DirectionalESM" + } + }, + { + "LocalSlot": "ProjectedShadowmap", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "ProjectedShadowmap" + } + }, + { + "LocalSlot": "ProjectedESM", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "ProjectedESM" + } + }, + + // Lights Resources + { + "LocalSlot": "TileLightData", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "TileLightData" + } + }, + { + "LocalSlot": "LightListRemapped", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "LightListRemapped" + } + } + ] + }, + + { + "Name": "HairShortCutResolveColorPass", + "TemplateName": "HairShortCutResolveColorPassTemplate", + "Enabled": true, + "Connections": [ + { // The final render target - this is MSAA mode RT - would it be cheaper to + // use non-MSAA and then copy? + "LocalSlot": "RenderTargetInputOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "RenderTargetInputOutput" + } + }, + { + "LocalSlot": "AccumulatedInverseAlpha", + "AttachmentRef": { + "Pass": "HairShortCutGeometryDepthAlphaPass", + "Attachment": "InverseAlphaRTOutput" + } + }, + { + "LocalSlot": "HairColorTexture", + "AttachmentRef": { + "Pass": "HairShortCutGeometryShadingPass", + "Attachment": "HairColorRenderTarget" + } + } + ] + }, + + { + // This pass copies the updated depth buffer (now contains hair depth) to linear depth texture + // for downstream passes to use. This can be optimized even further by writing into the stencil + // buffer pixels that were touched by HairPPLLResolvePass hence preventing depth update unless + // it is hair. + "Name": "DepthToDepthLinearPass", + "TemplateName": "DepthToLinearTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "HairShortCutResolveDepthPass", + "Attachment": "Depth" + } + } + ] + } + + ] + } + } +} + diff --git a/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryDepthAlpha.pass b/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryDepthAlpha.pass new file mode 100644 index 0000000000..559224faa6 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryDepthAlpha.pass @@ -0,0 +1,101 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "HairShortCutGeometryDepthAlphaPassTemplate", + "PassClass": "HairShortCutGeometryDepthAlphaPass", + "Slots": [ + { + "Name": "SkinnedHairSharedBuffer", + "ShaderInputName": "m_skinnedHairSharedBuffer", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { // DepthStencil for early disqualifying the pixel based on depth. No write. + "Name": "Depth", + "SlotType": "Input", + "ScopeAttachmentUsage": "DepthStencil" + }, + { + // the regular render target is blended using inverse alpha to reduce the + // incoming color contribution based on the hair thickness and alpha. + "Name": "InverseAlphaRTOutput", + "SlotType": "Output", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "LoadAction": "Clear", + "ClearValue": { + "Value": [ 1.0, 1.0, 1.0, 1.0 ] + }, + "StoreAction": "Store" + } + }, + { + "Name": "HairDepthsTextureArray", + "SlotType": "Output", + "ScopeAttachmentUsage": "Shader", + "ShaderInputName": "m_RWFragmentDepthsTexture", + "LoadStoreAction": { + "LoadAction": "Clear", + "ClearValue": { // reverse depth order: closer --> 1.0 + "Value": [ 0.0, 0.0, 0.0, 0.0 ] + }, + "StoreAction": "Store" + } + } + ], + "ImageAttachments": [ + { + // This buffer is used as the render target and should be at non-MSAA screen resolution + // to make sure no overwork is done. + "Name": "InverseAlphaRTOutput", + "SizeSource": { + "Source": { + "Pass": "Parent", + "Attachment": "DepthLinear" + } + }, + "ImageDescriptor": { + "Format": "R32_FLOAT", + "SharedQueueMask": "Graphics", + "BindFlags": [ + "Color", + "ShaderRead" + ] + } + }, + { + "Name": "HairDepthsTextureArray", + "SizeSource": { + "Source": { + "Pass": "Parent", + "Attachment": "DepthLinear" + } + }, + "ImageDescriptor": { + "Format": "R32_UINT", + "ArraySize": "3", + "SharedQueueMask": "Graphics", + "BindFlags": [ + "ShaderReadWrite", + "ShaderWrite", + "ShaderRead" + ] + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "HairGeometryDepthAlphaDrawList", + "PipelineViewTag": "MainCamera", + "PassSrgShaderAsset": { + // Looking for it in the Shaders directory relative to the Assets directory + "FilePath": "Shaders/HairShortCutGeometryDepthAlpha.shader" + } + } + } + } +} + diff --git a/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryShading.pass b/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryShading.pass new file mode 100644 index 0000000000..5940f8c549 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryShading.pass @@ -0,0 +1,155 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "HairShortCutGeometryShadingPassTemplate", + "PassClass": "HairShortCutGeometryShadingPass", + "Slots": [ + + { // Temporary color buffer to store the gathered shaded hair color - MSAA + "Name": "HairColorRenderTarget", + "SlotType": "Output", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "LoadAction": "Clear", + "ClearValue": { // reverse depth order: closer --> 1.0 + "Value": [ 0.0, 0.0, 0.0, 0.0 ] + }, + "StoreAction": "Store" + } + }, + + { + // This RT is MSAA - is it cheaper to avoid doing this work and only do a copy at a separate pass? + "Name": "RenderTargetInputOutput", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { // Used to get the transform from screen space to world space. + "Name": "DepthLinear", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { // For comparing the depth to early disqualify but not to write + "Name": "Depth", + "SlotType": "Input", + "ScopeAttachmentUsage": "DepthStencil" + }, + { + "Name": "SkinnedHairSharedBuffer", + "ShaderInputName": "m_skinnedHairSharedBuffer", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + + //------------- Shadowing Resources ------------- + { + "Name": "DirectionalShadowmap", + "ShaderInputName": "m_directionalLightShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "DirectionalESM", + "ShaderInputName": "m_directionalLightExponentialShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ProjectedShadowmap", + "ShaderInputName": "m_projectedShadowmaps", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ProjectedESM", + "ShaderInputName": "m_projectedExponentialShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + + //------------- Lighting Resources ------------- + { + "Name": "BRDFTextureInput", + "ShaderInputName": "m_brdfMap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { + "Name": "TileLightData", + "SlotType": "Input", + "ShaderInputName": "m_tileLightData", + "ScopeAttachmentUsage": "Shader" + }, + { + "Name": "LightListRemapped", + "SlotType": "Input", + "ShaderInputName": "m_lightListRemapped", + "ScopeAttachmentUsage": "Shader" + } + + ], + "ImageAttachments": [ + { + // The shader hair color render target - important to have at a non-MSAA mode + // so that no overwork is done on sampling. + "Name": "HairColorRenderTarget", + "SizeSource": { + "Source": { + "Pass": "Parent", + "Attachment": "RenderTargetInputOutput" + } + }, + "ImageDescriptor": { + "Format": "R16G16B16A16_FLOAT", + "SharedQueueMask": "Graphics", + "BindFlags": [ + "Color", + "ShaderRead" + ] + } + }, + { + "Name": "BRDFTexture", + "Lifetime": "Imported", + "AssetRef": { + "FilePath": "Textures/BRDFTexture.attimage" + } + } + ], + "Connections": [ + { + "LocalSlot": "BRDFTextureInput", + "AttachmentRef": { + "Pass": "This", + "Attachment": "BRDFTexture" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "HairGeometryShadingDrawList", + "PipelineViewTag": "MainCamera", + "PassSrgShaderAsset": { + // Looking for it in the Shaders directory relative to the Assets directory + "FilePath": "Shaders/HairShortCutGeometryShading.shader" + } + } + } + } +} + diff --git a/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveColor.pass b/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveColor.pass new file mode 100644 index 0000000000..efbf993307 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveColor.pass @@ -0,0 +1,45 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "HairShortCutResolveColorPassTemplate", + "PassClass": "FullScreenTriangle", + "Slots": [ + { + // This RT is MSAA - is it cheaper to avoid doing this work and only do a copy at a separate pass? + "Name": "RenderTargetInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "LoadAction": "Load", + "StoreAction": "Store" + } + }, + { + "Name": "HairColorTexture", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderInputName": "m_hairColorTexture" + }, + { + "Name": "AccumulatedInverseAlpha", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderInputName": "m_accumInvAlpha" + } + ], + "Connections": [ + ], + "PassData": { + "$type": "FullscreenTrianglePassData", + "ShaderAsset": { + // Looking for it in the Shaders directory relative to the Assets directory + "FilePath": "Shaders/HairShortCutResolveColor.shader" + } + } + } + } +} + diff --git a/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveDepth.pass b/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveDepth.pass new file mode 100644 index 0000000000..021d711f1f --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveDepth.pass @@ -0,0 +1,37 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "HairShortCutResolveDepthPassTemplate", + "PassClass": "FullScreenTriangle", + "Slots": [ + //------ General Input/Output resources and Render Target ------ + { + "Name": "Depth", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "DepthStencil", + "LoadStoreAction": { + "LoadAction": "Load", + "StoreAction": "Store" + } + }, + { // This holds the K nearset depths. The furthest depth will be taken to be written in the depth buffer. + "Name": "HairDepthsTextureArray", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderInputName": "m_fragmentDepthsTexture" + } + ], + "PassData": { + "$type": "FullscreenTrianglePassData", + "ShaderAsset": { + // Looking for it in the Shaders directory relative to the Assets directory + "FilePath": "Shaders/HairShortCutResolveDepth.shader" + } + } + } + } +} + diff --git a/Gems/AtomTressFX/Assets/Shaders/HairSrgs.azsli b/Gems/AtomTressFX/Assets/Shaders/HairComputeSrgs.azsli similarity index 99% rename from Gems/AtomTressFX/Assets/Shaders/HairSrgs.azsli rename to Gems/AtomTressFX/Assets/Shaders/HairComputeSrgs.azsli index cb0bdc2c6c..a4568588d5 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairSrgs.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairComputeSrgs.azsli @@ -29,7 +29,7 @@ // THE SOFTWARE. // //------------------------------------------------------------------------------ -// File: HairSRGs.azsli +// File: HairComputeSrgs.azsli // // Declarations of SRGs used by the hair shaders. //------------------------------------------------------------------------------ diff --git a/Gems/AtomTressFX/Assets/Shaders/HairFullScreenUtils.azsli b/Gems/AtomTressFX/Assets/Shaders/HairFullScreenUtils.azsli new file mode 100644 index 0000000000..7e57e8102c --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairFullScreenUtils.azsli @@ -0,0 +1,60 @@ +/* +* Modifications Copyright (c) Contributors to the Open 3D Engine Project. +* For complete copyright and license terms please see the LICENSE at the root of this distribution. +* +* SPDX-License-Identifier: (Apache-2.0 OR MIT) AND MIT +* +*/ + +#include +#include +#include + +//============================================================================== +// Generate a fullscreen triangle from pipeline provided vertex id +VSOutput FullScreenVS(VSInput input) +{ + VSOutput OUT; + + float4 posTex = GetVertexPositionAndTexCoords(input.m_vertexID); + + OUT.m_texCoord = float2(posTex.z, posTex.w); // [To Do] - test sign of Y based on original code + OUT.m_position = float4(posTex.xy, 0.0, 1.0); + + return OUT; +} + +//============================================================================== +// Given the depth buffer depth of the current pixel and the fragment XY position, +// reconstruct the NDC. +// screenCoords - from 0.. dimension of the screen of the current pixel +// screenTexture - screen buffer texture representing the same resolution we work in +// sDepth - the depth buffer depth at the fragment location +// NDC - Normalized Device Coordinates = warped screen space ( -1.1, -1..1, 0..1 ) +float3 ScreenPosToNDC( Texture2D screenTexture, float2 screenCoords, float depth ) +{ + uint2 dimensions; + screenTexture.GetDimensions(dimensions.x, dimensions.y); + float2 UV = saturate(screenCoords / dimensions.xy); + + float x = UV.x * 2.0f - 1.0f; + float y = (1.0f - UV.y) * 2.0f - 1.0f; + float3 NDC = float3(x, y, depth); + + return NDC; +} + +// Given the depth buffer depth of the current pixel and the fragment XY position, +// reconstruct the world space position +float3 ScreenPosToWorldPos( + Texture2D screenTexture, float2 screenCoords, float depth, + inout float3 screenPosNDC ) +{ + screenPosNDC = ScreenPosToNDC(screenTexture, screenCoords, depth); + float4 projectedPos = float4(screenPosNDC, 1.0f); // warped projected space [0..1] + float4 positionVS = mul(ViewSrg::m_projectionMatrixInverse, projectedPos); + positionVS /= positionVS.w; // notice the normalization factor - crucial! + float4 positionWS = mul(ViewSrg::m_viewMatrixInverse, positionVS); + + return positionWS.xyz; +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairLighting.azsli b/Gems/AtomTressFX/Assets/Shaders/HairLighting.azsli index dcdcf43243..7beba248f7 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairLighting.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairLighting.azsli @@ -230,7 +230,7 @@ float3 CalculateLighting( return lightingData.diffuseLighting + lightingData.specularLighting; } -float3 TressFXShading(float2 pixelCoord, float depth, float3 vTangentCoverage, float3 baseColor, float thickness, int shaderParamIndex) +float3 TressFXShading(float2 pixelCoord, float depth, float3 tangent, float3 baseColor, float thickness, int shaderParamIndex) { float3 vNDC; // normalized device / screen coordinates: [-1..1, -1..1, 0..1] float3 vPositionWS = ScreenPosToWorldPos(PassSrg::m_linearDepth, pixelCoord, depth, vNDC); @@ -241,9 +241,6 @@ float3 TressFXShading(float2 pixelCoord, float depth, float3 vTangentCoverage, f float3 vViewDirWS = g_vEye - vPositionWS; - // Need to expand the tangent that was compressed to store in the buffer - float3 vTangent = normalize(vTangentCoverage.xyz * 2.f - 1.f); - //---- TressFX original lighting params setting ---- HairShadeParams params; params.m_color = baseColor; @@ -266,11 +263,19 @@ float3 TressFXShading(float2 pixelCoord, float depth, float3 vTangentCoverage, f if (o_hairLightingModel == HairLightingModel::Kajiya) { // This option should be removed and the Kajiya-Kay model should be operated from within // the Atom lighting loop. - accumulatedLight = SimplifiedHairLighting(vTangent, vPositionWS, vViewDirWS, params, vNDC); + accumulatedLight = SimplifiedHairLighting(tangent, vPositionWS, vViewDirWS, params, vNDC); } else { - accumulatedLight = CalculateLighting(screenCoords, vPositionWS, vViewDirWS, vTangent, thickness, params); + accumulatedLight = CalculateLighting(screenCoords, vPositionWS, vViewDirWS, tangent, thickness, params); } return accumulatedLight; } + +float3 TressFXShadingFullScreen(float2 pixelCoord, float depth, float3 compressedTangent, float3 baseColor, float thickness, int shaderParamIndex) +{ + // The tangent that was compressed to store in the PPLL structure + float3 tangent = normalize(compressedTangent.xyz * 2.f - 1.f); + + return TressFXShading(pixelCoord, depth, tangent, baseColor, thickness, shaderParamIndex); +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairLightingEquations.azsli b/Gems/AtomTressFX/Assets/Shaders/HairLightingEquations.azsli index 83ebc04521..4e4245f765 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairLightingEquations.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairLightingEquations.azsli @@ -66,7 +66,7 @@ option bool o_enableAzimuthCoeff = true; float M_R(Surface surface, float Lh, float sinLiPlusSinLr) { float a = 1.0f * surface.cuticleTilt; // Tilt is translate as the mean offset - float b = 0.5 * surface.roughnessA2; // Roughness is used as the standard deviation + float b = 0.5f * surface.roughnessA2; // Roughness is used as the standard deviation // return GaussianNormalized(sinLiPlusSinLr, a, b); // reference return GaussianNormalized(Lh, a, b); @@ -74,8 +74,8 @@ float M_R(Surface surface, float Lh, float sinLiPlusSinLr) float M_TT(Surface surface, float Lh, float sinLiPlusSinLr) { - float a = 1.0 * surface.cuticleTilt; - float b = 0.5 * surface.roughnessA2; + float a = 1.0f * surface.cuticleTilt; + float b = 0.5f * surface.roughnessA2; // return GaussianNormalized(sinLiPlusSinLr, a, b); // reference return GaussianNormalized(Lh, a, b); @@ -83,8 +83,8 @@ float M_TT(Surface surface, float Lh, float sinLiPlusSinLr) float M_TRT(Surface surface, float Lh, float sinLiPlusSinLr) { - float a = 1.5 * surface.cuticleTilt; - float b = 1.0 * surface.roughnessA2; + float a = 1.5f * surface.cuticleTilt; + float b = 1.0f * surface.roughnessA2; // return GaussianNormalized(sinLiPlusSinLr, a, b); // reference return GaussianNormalized(Lh, a, b); diff --git a/Gems/AtomTressFX/Assets/Shaders/HairRenderingFillPPLL.azsl b/Gems/AtomTressFX/Assets/Shaders/HairRenderingFillPPLL.azsl index 82c677faad..02777435f5 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairRenderingFillPPLL.azsl +++ b/Gems/AtomTressFX/Assets/Shaders/HairRenderingFillPPLL.azsl @@ -40,7 +40,8 @@ //! that can change between passes due to the application of skinning, simulation //! and physics affect and is then read by the rendering shaders. ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback -{ //! This shared buffer needs to match the SharedBuffer structure +{ + //! This shared buffer needs to match the SharedBuffer structure //! shared between all draw calls / dispatches for the hair skinning StructuredBuffer m_skinnedHairSharedBuffer; @@ -101,39 +102,8 @@ ShaderResourceGroup HairDynamicDataSrg : SRG_PerObject // space 1 - per instance #define g_GuideHairVertexTangents HairDynamicDataSrg::m_hairVertexTangents //============================================================================== -#include +#include // VS resides here //============================================================================== -//! Hair input structure to Pixel shaders -struct PS_INPUT_HAIR -{ - float4 Position : SV_POSITION; - float4 Tangent : Tangent; - float4 p0p1 : TEXCOORD0; - float4 StrandColor : TEXCOORD1; -}; - -//! Hair Render VS -PS_INPUT_HAIR RenderHairVS(uint vertexId : SV_VertexID) -{ -// uint2 scrSize; -// PassSrg::m_linearDepth.GetDimensions(scrSize.x, scrSize.y); -// TressFXVertex tressfxVert = GetExpandedTressFXVert(vertexId, g_vEye.xyz, float2(scrSize), g_mVP); - - // [To Do] Hair: the above code should replace the existing but requires modifications to - // the function GetExpandedTressFXVert. - // Note that in Atom g_vViewport is aspect ratio and NOT size. - TressFXVertex tressfxVert = GetExpandedTressFXVert(vertexId, g_vEye.xyz, g_vViewport.zw, g_mVP); - - - PS_INPUT_HAIR Output; - - Output.Position = tressfxVert.Position; - Output.Tangent = tressfxVert.Tangent; - Output.p0p1 = tressfxVert.p0p1; - Output.StrandColor = tressfxVert.StrandColor; - - return Output; -} // Allocate a new fragment location in fragment color, depth, and link buffers int AllocateFragment(int2 vScreenAddress) @@ -202,9 +172,9 @@ void PPLLFillPS(PS_INPUT_HAIR input) ////////////////////////////////////////////////////////////////////// // [To Do] Hair: anti aliasing via coverage requires work and is disabled for now - float3 vNDC = ScreenPosToNDC(PassSrg::m_linearDepth, input.Position.xy, input.Position.z); - uint2 dimensions; - PassSrg::m_linearDepth.GetDimensions(dimensions.x, dimensions.y); +// float3 vNDC = ScreenPosToNDC(PassSrg::m_linearDepth, input.Position.xy, input.Position.z); +// uint2 dimensions; +// PassSrg::m_linearDepth.GetDimensions(dimensions.x, dimensions.y); // float coverage = ComputeCoverage(input.p0p1.xy, input.p0p1.zw, vNDC.xy, float2(dimensions.x, dimensions.y)); float coverage = 1.0; ///////////////////////////////////////////////////////////////////// diff --git a/Gems/AtomTressFX/Assets/Shaders/HairRenderingResolvePPLL.azsl b/Gems/AtomTressFX/Assets/Shaders/HairRenderingResolvePPLL.azsl index 82c75280f6..3d72f1e21e 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairRenderingResolvePPLL.azsl +++ b/Gems/AtomTressFX/Assets/Shaders/HairRenderingResolvePPLL.azsl @@ -58,7 +58,7 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback // in the OIT process. // It can also be used to avoid the HW blend done at the end of the pixel // shader stage but HW blend might be cheaper than additional PS blend. - Texture2D m_frameBuffer; // The merged MSAA output + Texture2D m_frameBuffer; // The merged non-MSAA input // Linear depth is used for getting the screen to world transform Texture2D m_linearDepth; @@ -93,26 +93,11 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback #define HairParams PassSrg::m_hairParams //============================================================================== +#include // provides the Vertex Shader #include -#include -#include - -// Generates a fullscreen triangle from pipeline provided vertex id -VSOutput FullScreenVS(VSInput input) -{ - VSOutput OUT; - - float4 posTex = GetVertexPositionAndTexCoords(input.m_vertexID); - - OUT.m_texCoord = float2(posTex.z, posTex.w); - OUT.m_position = float4(posTex.x, posTex.y, 0.0, 1.0); - - return OUT; -} ////////////////////////////////////////////////////////////// // Bind data for PPLLResolvePS - #define NODE_DATA(x) LinkedListNodes[x].data #define NODE_NEXT(x) LinkedListNodes[x].uNext #define NODE_DEPTH(x) LinkedListNodes[x].depth @@ -298,7 +283,7 @@ float4 GatherLinkedList(float2 vfScreenAddress, float2 screenUV, inout float out uint shadeParamIndex; // So we know what settings to shade with float3 vColor = UnpackUintIntoFloat3Byte(color, shadeParamIndex); - float3 fragmentColor = TressFXShading(vfScreenAddress, fDepth, vTangent, vColor, fcolor.w, shadeParamIndex); + float3 fragmentColor = TressFXShadingFullScreen(vfScreenAddress, fDepth, vTangent, vColor, fcolor.w, shadeParamIndex); // Blend in the fragment color fcolor.xyz = fcolor.xyz * (1.f - alpha) + fragmentColor * alpha; @@ -355,7 +340,7 @@ float4 GetClosestFragment(float2 vfScreenAddress, float2 screenUV, inout float c float alpha = 1.0; uint shadeParamIndex; // the material index float3 vColor = UnpackUintIntoFloat3Byte(curColor, shadeParamIndex); - float3 fragmentColor = TressFXShading(vfScreenAddress, curDepth, vTangent, vColor, fcolor.w, shadeParamIndex); + float3 fragmentColor = TressFXShadingFullScreen(vfScreenAddress, curDepth, vTangent, vColor, fcolor.w, shadeParamIndex); // Blend in the fragment color fcolor.xyz = fcolor.xyz * (1.f - alpha) + (fragmentColor * alpha); diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.azsl b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.azsl new file mode 100644 index 0000000000..23c926406f --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.azsl @@ -0,0 +1,131 @@ +/* +* Modifications Copyright (c) Contributors to the Open 3D Engine Project. +* For complete copyright and license terms please see the LICENSE at the root of this distribution. +* +* SPDX-License-Identifier: (Apache-2.0 OR MIT) AND MIT +* +*/ + +// +// Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include +#include + +//!------------------------------ SRG Structure -------------------------------- +//! Per pass SRG that holds the dynamic shared read-write buffer shared +//! across all dispatches and draw calls. It is used for all the dynamic buffers +//! that can change between passes due to the application of skinning, simulation +//! and physics affect. +//! Once the compute pases are done, it is read by the rendering shaders. +ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback +{ + //! This shared buffer needs to match the SharedBuffer structure + //! shared between all draw calls / dispatches for the hair skinning + StructuredBuffer m_skinnedHairSharedBuffer; + + //! Based on [[vk::binding(0, 3)]] RWTexture2DArray RWFragmentDepthsTexture : register(u0, space3); + RWTexture2DArray m_RWFragmentDepthsTexture; +} +//============================================================================== + +//!------------------------------ SRG Structure -------------------------------- +//! Per instance/draw SRG representing dynamic read-write set of buffers +//! that are unique per instance and are shared and changed between passes due +//! to the application of skinning, simulation and physics affect. +//! It is then also read by the rendering shaders. +//! This Srg is NOT shared by the passes since it requires having barriers between +//! both passes and draw calls, instead, all buffers are allocated from a single +//! shared buffer (through BufferViews) and that buffer is then shared between +//! the passes via the PerPass Srg frequency. +ShaderResourceGroup HairDynamicDataSrg : SRG_PerObject // space 1 - per instance / object +{ + Buffer m_hairVertexPositions; + Buffer m_hairVertexTangents; + + //! Per hair object offset to the start location of each buffer within + //! 'm_skinnedHairSharedBuffer'. The offset is in bytes! + uint m_positionBufferOffset; + uint m_tangentBufferOffset; +}; +//------------------------------------------------------------------------------ +// Allow for the code to run with minimal changes - skinning / simulation compute passes +// Usage of per-instance buffer +#define g_GuideHairVertexPositions HairDynamicDataSrg::m_hairVertexPositions +#define g_GuideHairVertexTangents HairDynamicDataSrg::m_hairVertexTangents +//------------------------------------------------------------------------------ + +#include // VS resides here + +//!============================================================================= +//! Geometry Depth Alpha - First Pass of ShortCut Render +//! It is a Geometry pass that stores the K=3 front fragment depths, and accumulates +//! product of 1-alpha multiplications (fade out) of the input render target. +//! +//! Short explanation: in the original AMD implementation 1-alpha is multiplied +//! repeatedly with the incoming render target (back buffer) hence blending out +//! the existing back buffer color based on the density and transparency of the hair. +//! This implies that later on the hair color should be added based on the inverse +//! of this operation. +//!============================================================================= +[earlydepthstencil] +float HairShortCutDepthsAlphaPS(PS_INPUT_HAIR input) : SV_Target +{ + ////////////////////////////////////////////////////////////////////// + // [To Do] Hair: anti aliasing via coverage requires work and is disabled for now +// float3 vNDC = ScreenPosToNDC(PassSrg::m_linearDepth, input.Position.xy, input.Position.z); +// uint2 dimensions; +// PassSrg::m_linearDepth.GetDimensions(dimensions.x, dimensions.y); +// float coverage = ComputeCoverage(input.p0p1.xy, input.p0p1.zw, vNDC.xy, float2(dimensions.x, dimensions.y)); + float coverage = 1.0; + ///////////////////////////////////////////////////////////////////// + + float alpha = coverage * MatBaseColor.a; + + if (alpha < SHORTCUT_MIN_ALPHA) + return 1.0; + + int2 vScreenAddress = int2(input.Position.xy); + uint uDepth = asuint(input.Position.z); + uint uDepth0Prev, uDepth1Prev, uDepth2Prev; + + // Min of depth 0 and input depth - in Atom the Z order is reverse + // Original value is uDepth0Prev + InterlockedMax(PassSrg::m_RWFragmentDepthsTexture[uint3(vScreenAddress, 0)], uDepth, uDepth0Prev); + + // Min of depth 1 and greater of the last compare - in Atom the Z order is reverse + // If fragment opaque, always use input depth (don't need greater depths) + uDepth = (alpha > 0.98) ? uDepth : max(uDepth, uDepth0Prev); + + InterlockedMax(PassSrg::m_RWFragmentDepthsTexture[uint3(vScreenAddress, 1)], uDepth, uDepth1Prev); + + // Min of depth 2 and greater of the last compare - in Atom the Z order is reverse + // If fragment opaque, always use input depth (don't need greater depths) + uDepth = (alpha > 0.98) ? uDepth : max(uDepth, uDepth1Prev); + + InterlockedMax(PassSrg::m_RWFragmentDepthsTexture[uint3(vScreenAddress, 2)], uDepth, uDepth2Prev); + + // Accumulate the alpha multiplication from all hair components by multiplying the inverse and + // therefore going down towards 0. At the end product, the inverse will be taken as the hair + // alpha and the remainder will be used to blend the back buffer. + return 1.0 - alpha; +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.shader b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.shader new file mode 100644 index 0000000000..7cd1f44510 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.shader @@ -0,0 +1,45 @@ +{ + "Source" : "HairShortCutGeometryDepthAlpha.azsl", + "DrawList" : "HairGeometryDepthAlphaDrawList", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, + "WriteMask" : "Zero", // Avoid writing the depth + "CompareFunc" : "GreaterEqual" + // Originally in TressFX this is LessEqual - Atom is using reverse sort + }, + "Stencil" : + { + "Enable" : false + } + }, + + "BlendState" : + { + "Enable" : true, + "BlendSource" : "Zero", + "BlendDest" : "ColorSource", + "BlendOp" : "Add", + "BlendAlphaSource" : "Zero", + "BlendAlphaDest" : "AlphaSource", + "BlendAlphaOp" : "Add" + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "RenderHairVS", + "type": "Vertex" + }, + { + "name": "HairShortCutDepthsAlphaPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.azsl b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.azsl new file mode 100644 index 0000000000..c2a2958dfe --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.azsl @@ -0,0 +1,176 @@ +/* +* Modifications Copyright (c) Contributors to the Open 3D Engine Project. +* For complete copyright and license terms please see the LICENSE at the root of this distribution. +* +* SPDX-License-Identifier: (Apache-2.0 OR MIT) AND MIT +* +*/ + +//------------------------------------------------------------------------------ +// Shader code related to lighting and shadowing for TressFX +//------------------------------------------------------------------------------ +// +// Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include +#include + +#define AMD_TRESSFX_MAX_HAIR_GROUP_RENDER 16 + +//!------------------------------ SRG Structure -------------------------------- +//! Per pass SRG that holds the dynamic shared read-write buffer shared +//! across all dispatches and draw calls. It is used for all the dynamic buffers +//! that can change between passes due to the application of skinning, simulation +//! and physics affect. +//! Once the compute pases are done, it is read by the rendering shaders. +ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback +{ + //! This shared buffer needs to match the SharedBuffer structure + //! shared between all draw calls / dispatches for the hair skinning + StructuredBuffer m_skinnedHairSharedBuffer; + + //! Per hair object material array used by the PPLL resolve pass + //! Originally in TressFXRendering.hlsl this is space 0 + HairObjectShadeParams m_hairParams[AMD_TRESSFX_MAX_HAIR_GROUP_RENDER]; + + // Linear depth is used for getting the screen to world transform + Texture2D m_linearDepth; + + //------------------------------ + // Lighting Data + //------------------------------ + Sampler LinearSampler + { // Required by LightingData.azsli + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; + }; + + Texture2DArray m_directionalLightShadowmap; + Texture2DArray m_directionalLightExponentialShadowmap; + Texture2DArray m_projectedShadowmaps; + Texture2DArray m_projectedExponentialShadowmap; + Texture2D m_brdfMap; + Texture2D m_tileLightData; + StructuredBuffer m_lightListRemapped; +} + +//------------------------------------------------------------------------------ +//! The hair objects' material array buffer used by the rendering resolve pass +#define HairParams PassSrg::m_hairParams +//============================================================================== + +//!------------------------------ SRG Structure -------------------------------- +//! Per instance/draw SRG representing dynamic read-write set of buffers +//! that are unique per instance and are shared and changed between passes due +//! to the application of skinning, simulation and physics affect. +//! It is then also read by the rendering shaders. +//! This Srg is NOT shared by the passes since it requires having barriers between +//! both passes and draw calls, instead, all buffers are allocated from a single +//! shared buffer (through BufferViews) and that buffer is then shared between +//! the passes via the PerPass Srg frequency. +ShaderResourceGroup HairDynamicDataSrg : SRG_PerObject // space 1 - per instance / object +{ + Buffer m_hairVertexPositions; + Buffer m_hairVertexTangents; + + //! Per hair object offset to the start location of each buffer within + //! 'm_skinnedHairSharedBuffer'. The offset is in bytes! + uint m_positionBufferOffset; + uint m_tangentBufferOffset; +}; +//------------------------------------------------------------------------------ +// Allow for the code to run with minimal changes - skinning / simulation compute passes +// Usage of per-instance buffer +#define g_GuideHairVertexPositions HairDynamicDataSrg::m_hairVertexPositions +#define g_GuideHairVertexTangents HairDynamicDataSrg::m_hairVertexTangents +//------------------------------------------------------------------------------ + +#include // VS resides here +#include // Required for world coordinates calculation +#include + +//!============================================================================= +//! Geometry Shading - Third Pass of ShortCut Render +//! Geometry pass that shades pixels that passes the early depth test. Due to this, it +//! is limited to the stored K near fragments due to previous depth write pass that +//! wrote the furthest depth of the K stored depths. +//! Colors are accumulated in the render target for a weighted average in final pass. +//! [To Do] - in the original short cut, the alpha is taken from the depth alpha pass +//!============================================================================= +[earlydepthstencil] +float4 HairShortCutGeometryColorPS(PS_INPUT_HAIR input) : SV_Target +{ + // Strand Color read in is either the BaseMatColor, or BaseMatColor modulated with a color read from texture + // on vertex shader for base color along with modulation by the tip color + float4 strandColor = float4(input.StrandColor.rgb, MatBaseColor.a); + + // If we are supporting strand UV texturing, further blend in the texture color/alpha + // Do this while computing NDC and coverage to hide latency from texture lookup + if (EnableStrandUV) + { + // Grab the uv in case we need it + float2 uv = float2(input.Tangent.w, input.StrandColor.w); + + // Apply StrandUVTiling + float2 strandUV = float2(uv.x, (uv.y * StrandUVTilingFactor) - floor(uv.y * StrandUVTilingFactor)); + + strandColor.rgb *= StrandAlbedoTexture.Sample(LinearWrapSampler, strandUV).rgb; + } + + ////////////////////////////////////////////////////////////////////// + // [To Do] Hair: anti aliasing via coverage requires work and is disabled for now +// float3 vNDC = ScreenPosToNDC(PassSrg::m_linearDepth, input.Position.xy, input.Position.z); +// uint2 dimensions; +// PassSrg::m_linearDepth.GetDimensions(dimensions.x, dimensions.y); +// float2 screenCoords = saturate(pixelCoord / dimensions.xy); +// float coverage = ComputeCoverage(input.p0p1.xy, input.p0p1.zw, vNDC.xy, float2(dimensions.x, dimensions.y)); +// original: float coverage = ComputeCoverage(input.p0p1.xy, input.p0p1.zw, vNDC.xy, g_vViewport.zw - g_vViewport.xy); + float coverage = 1.0; + ///////////////////////////////////////////////////////////////////// + + float alpha = coverage; + + // Update the alpha to have proper value (accounting for coverage, base alpha, and strand alpha) + alpha *= strandColor.w; + + // Early out + if (alpha < SHORTCUT_MIN_ALPHA) + { + return float4(0, 0, 0, 0); + } + + float2 pixelCoord = input.Position.xy; + float depth = input.Position.z; + // [To Do] - the thickness will need to be corrected somehow since this technique doesn't + // keeps track of the accumulated alpha / thickness + float thickness = alpha; + float3 shadedFragment = TressFXShading(pixelCoord, depth, input.Tangent.xyz, strandColor.rgb, thickness, RenderParamsIndex); + + // Color channel: Pre-multiply with alpha to create non-normalized weighted sum. + // Alpha Channel: Sum up all the hair alphas - this will be used to normalize the color + // per fragment at the next pass. + return float4(shadedFragment * alpha, alpha); +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.shader b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.shader new file mode 100644 index 0000000000..40e56006cd --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.shader @@ -0,0 +1,45 @@ +{ + "Source" : "HairShortCutGeometryShading.azsl", + "DrawList" : "HairGeometryShadingDrawList", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, + "WriteMask" : "Zero", // Avoid writing the depth + "CompareFunc" : "GreaterEqual" + // Originally in TressFX this is LessEqual - Atom is using reverse sort + }, + "Stencil" : + { + "Enable" : false + } + }, + + "BlendState" : + { + "Enable" : true, + "BlendSource" : "One", + "BlendDest" : "One", + "BlendOp" : "Add", + "BlendAlphaSource" : "One", + "BlendAlphaDest" : "One", + "BlendAlphaOp" : "Add" + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "RenderHairVS", + "type": "Vertex" + }, + { + "name": "HairShortCutGeometryColorPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.azsl b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.azsl new file mode 100644 index 0000000000..f25c9dd711 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.azsl @@ -0,0 +1,63 @@ +/* +* Modifications Copyright (c) Contributors to the Open 3D Engine Project. +* For complete copyright and license terms please see the LICENSE at the root of this distribution. +* +* SPDX-License-Identifier: (Apache-2.0 OR MIT) AND MIT +* +*/ + +#include +#include + +//!------------------------------ SRG Structure -------------------------------- +//! Per pass SRG that holds the dynamic shared read-write buffer shared +//! across all dispatches and draw calls. It is used for all the dynamic buffers +//! that can change between passes due to the application of skinning, simulation +//! and physics affect. +//! Once the compute pases are done, it is read by the rendering shaders. +ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback +{ + // oiriginally: [[vk::binding(0, 0)]] Texture2D HaiColorTexture : register(t0, space0); + // oiriginally: [[vk::binding(1, 0)]] Texture2D AccumInvAlpha : register(t1, space0); + Texture2D m_hairColorTexture; + Texture2D m_accumInvAlpha; +} +//------------------------------------------------------------------------------ + +#include // provides the Vertex Shader + +//!============================================================================= +//! HairColorPS - Fourth Pass of ShortCut Render +//! Full-screen pass that finalizes the weighted average, and blends using the +//! accumulated 1-alpha product. +//!============================================================================= +[earlydepthstencil] +float4 HairShortCutResolveColorPS(VSOutput input) : SV_Target +{ + int2 vScreenAddress = int2(input.m_position.xy); + + float fInvAlpha = PassSrg::m_accumInvAlpha[vScreenAddress]; + float fAlpha = 1.0 - fInvAlpha; + + if (fAlpha < SHORTCUT_MIN_ALPHA) + { + // next we discard of non-hair pixels to avoid manipulating them depending + // on the alpha blend state - this is the safer and faster approach as there + // is no hair in these pixels + discard; + } + + float4 finalColor; + float weightSum = PassSrg::m_hairColorTexture[vScreenAddress].w; + + // Normalize the sum of the shaded fragment from the previous pass and + // then multiply it by the alpha blend of the hairs done in the depth-alpha pass. + finalColor.xyz = PassSrg::m_hairColorTexture[vScreenAddress] * fAlpha / weightSum; + + // The alpha is set to the inverse alpha of the hair so that the original + // background will be blended using this factor emulating single step alpha blend + // over the sum of all hair fragment blends. + finalColor.w = fInvAlpha; + + return finalColor; +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.shader b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.shader new file mode 100644 index 0000000000..6703c63f18 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.shader @@ -0,0 +1,41 @@ +{ + "Source" : "HairShortCutResolveColor.azsl", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : false // Avoid comparing depth + }, + "Stencil" : + { + "Enable" : false + } + }, + + "BlendState" : + { + "Enable" : true, + "BlendSource" : "One", + "BlendDest" : "AlphaSource", + "BlendOp" : "Add", + "BlendAlphaSource" : "Zero", + "BlendAlphaDest" : "Zero", + "BlendAlphaOp" : "Add" + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "FullScreenVS", + "type": "Vertex" + }, + { + "name": "HairShortCutResolveColorPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.azsl b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.azsl new file mode 100644 index 0000000000..862b7c9015 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.azsl @@ -0,0 +1,65 @@ +/* +* Modifications Copyright (c) Contributors to the Open 3D Engine Project. +* For complete copyright and license terms please see the LICENSE at the root of this distribution. +* +* SPDX-License-Identifier: (Apache-2.0 OR MIT) AND MIT +* +*/ + +// +// Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include + +//!------------------------------ SRG Structure -------------------------------- +//! Per pass SRG that holds the dynamic shared read-write buffer shared +//! across all dispatches and draw calls. It is used for all the dynamic buffers +//! that can change between passes due to the application of skinning, simulation +//! and physics affect. +//! Once the compute pases are done, it is read by the rendering shaders. +ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback +{ + // Originally: [[vk::binding(0, 0)]] Texture2DArray FragmentDepthsTexture : register(t0, space0); + Texture2DArray m_fragmentDepthsTexture; +} +//------------------------------------------------------------------------------ + +#include // provides the Vertex Shader + +//!============================================================================= +//! Resolve Depth - Second Pass of ShortCut +//! Full-screen pass that writes the farthest of the stored K near depths so it +//! could be used for depth culling during the following geometry shading pass. +//!============================================================================= +float HairShortCutResolveDepthPS(VSOutput input) : SV_Depth +{ + // Blend the layers of fragments from back to front + int2 vScreenAddress = int2(input.m_position.xy); + + // Write farthest depth value for culling in the next pass. + // It may be the initial value of 1.0 if there were not enough fragments to write all depths, but then culling not important. + const int farthestDepthIndex = 2; + uint uDepth = PassSrg::m_fragmentDepthsTexture[uint3(vScreenAddress, farthestDepthIndex)]; + + // The following line is writing the depth into the actual depth buffer + return asfloat(uDepth); +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.shader b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.shader new file mode 100644 index 0000000000..5b518c3bfc --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.shader @@ -0,0 +1,37 @@ +{ + "Source" : "HairShortCutResolveDepth.azsl", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, // test the written depth and accept/discard based on the depth buffer + "CompareFunc" : "GreaterEqual" + // Originally in TressFX this is LessEqual - Atom is using reverse sort + }, + "Stencil" : + { + "Enable" : false + } + }, + + "BlendState" : + { + "Enable" : false + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "FullScreenVS", + "type": "Vertex" + }, + { + "name": "HairShortCutResolveDepthPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairSimulationCompute.azsl b/Gems/AtomTressFX/Assets/Shaders/HairSimulationCompute.azsl index 496aa5c7da..ed6ec5de27 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairSimulationCompute.azsl +++ b/Gems/AtomTressFX/Assets/Shaders/HairSimulationCompute.azsl @@ -31,7 +31,7 @@ // THE SOFTWARE. // //-------------------------------------------------------------------------------------- -#include +#include #include //-------------------------------------------------------------------------------------- diff --git a/Gems/AtomTressFX/Assets/Shaders/HairSimulationSrgs.azsli b/Gems/AtomTressFX/Assets/Shaders/HairSimulationComputeSrgs.azsli similarity index 99% rename from Gems/AtomTressFX/Assets/Shaders/HairSimulationSrgs.azsli rename to Gems/AtomTressFX/Assets/Shaders/HairSimulationComputeSrgs.azsli index f95dac92cf..3ded3ab2af 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairSimulationSrgs.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairSimulationComputeSrgs.azsli @@ -29,13 +29,13 @@ // THE SOFTWARE. // //------------------------------------------------------------------------------ -// File: HairSRGs.azsli +// File: HairSimulationComputeSrgs.azsli // // Declarations of SRGs used by the hair shaders. //------------------------------------------------------------------------------ #pragma once -#include +#include //!----------------------------------------------------------------------------- //! diff --git a/Gems/AtomTressFX/Assets/Shaders/HairStrands.azsli b/Gems/AtomTressFX/Assets/Shaders/HairStrands.azsli index b8c4ea9eb4..abbbc8c0de 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairStrands.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairStrands.azsli @@ -66,12 +66,22 @@ float3 GetSharedTangent(int tangentIndex) ); } +//! Hair vertex geometry output - input structure for the Pixel shaders struct TressFXVertex { float4 Position; - float4 Tangent; + float4 Tangent; // xyz = Tangent, w = Strand U float4 p0p1; - float4 StrandColor; + float4 StrandColor; // xyz = Strand Color, w = Strand V +}; + +//! Matching structure to carry out as VS output / PS input +struct PS_INPUT_HAIR +{ + float4 Position : SV_POSITION; + float4 Tangent : Tangent; + float4 p0p1 : TEXCOORD0; + float4 StrandColor : TEXCOORD1; }; float3 GetStrandColor(int index, float fractionOfStrand) @@ -178,5 +188,26 @@ TressFXVertex GetExpandedTressFXShadowVert(uint vertexId, float3 eye, float2 win return Output; } -// EndHLSL +//!============================================================================= +//! Hair Render VS - Used by all geometry hair shaders +//!============================================================================= +PS_INPUT_HAIR RenderHairVS(uint vertexId : SV_VertexID) +{ + PS_INPUT_HAIR vsOutput; + // uint2 scrSize; + // PassSrg::m_linearDepth.GetDimensions(scrSize.x, scrSize.y); + // TressFXVertex tressfxVert = GetExpandedTressFXVert(vertexId, g_vEye.xyz, float2(scrSize), g_mVP); + + // [To Do] Hair: the above code should replace the existing but requires modifications to + // the function GetExpandedTressFXVert. + // Note that in Atom g_vViewport is aspect ratio and NOT size. + TressFXVertex tressfxVert = GetExpandedTressFXVert(vertexId, g_vEye.xyz, g_vViewport.zw, g_mVP); + + vsOutput.Position = tressfxVert.Position; + vsOutput.Tangent = tressfxVert.Tangent; + vsOutput.p0p1 = tressfxVert.p0p1; + vsOutput.StrandColor = tressfxVert.StrandColor; + + return vsOutput; +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairUtilities.azsli b/Gems/AtomTressFX/Assets/Shaders/HairUtilities.azsli index 1d50d3c040..f91ba1ff3b 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairUtilities.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairUtilities.azsli @@ -34,9 +34,6 @@ #pragma once -#include - - #define SHORTCUT_MIN_ALPHA 0.02 #define TRESSFX_FLOAT_EPSILON 1e-7 @@ -52,40 +49,6 @@ float4 MatrixMult(float4x4 m, float4 v) return mul(m, v); } -// Given the depth buffer depth of the current pixel and the fragment XY position, -// reconstruct the NDC. -// screenCoords - from 0.. dimension of the screen of the current pixel -// screenTexture - screen buffer texture representing the same resolution we work in -// sDepth - the depth buffer depth at the fragment location -// NDC - Normalized Device Coordinates = warped screen space ( -1.1, -1..1, 0..1 ) -float3 ScreenPosToNDC( Texture2D screenTexture, float2 screenCoords, float depth ) -{ - uint2 dimensions; - screenTexture.GetDimensions(dimensions.x, dimensions.y); - float2 UV = saturate(screenCoords / dimensions.xy); - - float x = UV.x * 2.0f - 1.0f; - float y = (1.0f - UV.y) * 2.0f - 1.0f; - float3 NDC = float3(x, y, depth); - - return NDC; -} - -// Given the depth buffer depth of the current pixel and the fragment XY position, -// reconstruct the world space position -float3 ScreenPosToWorldPos( - Texture2D screenTexture, float2 screenCoords, float depth, - inout float3 screenPosNDC ) -{ - screenPosNDC = ScreenPosToNDC(PassSrg::m_linearDepth, screenCoords, depth); - float4 projectedPos = float4(screenPosNDC, 1.0f); // warped projected space [0..1] - float4 positionVS = mul(ViewSrg::m_projectionMatrixInverse, projectedPos); - positionVS /= positionVS.w; // notice the normalization factor - crucial! - float4 positionWS = mul(ViewSrg::m_viewMatrixInverse, positionVS); - - return positionWS.xyz; -} - // Pack a float4 into an uint uint PackFloat4IntoUint(float4 vValue) { diff --git a/Gems/AtomTressFX/Code/Components/HairSystemComponent.cpp b/Gems/AtomTressFX/Code/Components/HairSystemComponent.cpp index 66ac3d2e09..2ae8b9d97d 100644 --- a/Gems/AtomTressFX/Code/Components/HairSystemComponent.cpp +++ b/Gems/AtomTressFX/Code/Components/HairSystemComponent.cpp @@ -80,8 +80,14 @@ namespace AZ // Load the AtomTressFX pass classes passSystem->AddPassCreator(Name("HairSkinningComputePass"), &HairSkinningComputePass::Create); + + // Load the PPLL render method passes passSystem->AddPassCreator(Name("HairPPLLRasterPass"), &HairPPLLRasterPass::Create); passSystem->AddPassCreator(Name("HairPPLLResolvePass"), &HairPPLLResolvePass::Create); + + // Load the ShortCut render method passes + passSystem->AddPassCreator(Name("HairShortCutGeometryDepthAlphaPass"), &HairShortCutGeometryDepthAlphaPass::Create); + passSystem->AddPassCreator(Name("HairShortCutGeometryShadingPass"), &HairShortCutGeometryShadingPass::Create); } void HairSystemComponent::Deactivate() diff --git a/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.cpp b/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.cpp index 7af5903cf3..b9c0d2e379 100644 --- a/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.cpp +++ b/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.cpp @@ -165,6 +165,15 @@ namespace AZ return true; } + Data::Instance HairGeometryRasterPass::GetShader() + { + if (!m_initialized || !m_shader) + { + AZ_Error("Hair Gem", LoadShaderAndPipelineState(), "HairGeometryRasterPass could not initialize pipeline or shader"); + } + return m_shader; + } + void HairGeometryRasterPass::SchedulePacketBuild(HairRenderObject* hairObject) { m_newRenderObjects.insert(hairObject); @@ -188,7 +197,7 @@ namespace AZ // The PerPass is gathered through the RasterPass::m_shaderResourceGroup AZStd::lock_guard lock(m_mutex); - return hairObject->BuildPPLLDrawPacket(drawRequest); + return hairObject->BuildDrawPacket(m_shader.get(), drawRequest); } bool HairGeometryRasterPass::AddDrawPackets(AZStd::list>& hairRenderObjects) @@ -205,7 +214,7 @@ namespace AZ for (auto& renderObject : hairRenderObjects) { - const RHI::DrawPacket* drawPacket = renderObject->GetFillDrawPacket(); + const RHI::DrawPacket* drawPacket = renderObject->GetGeometrylDrawPacket(m_shader.get()); if (!drawPacket) { // might not be an error - the object might have just been added and the DrawPacket is // scheduled to be built when the render frame begins diff --git a/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.h b/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.h index a226d2c294..d3ba22039d 100644 --- a/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.h +++ b/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.h @@ -51,7 +51,7 @@ namespace AZ //! The following will be called when an object was added or shader has been compiled void SchedulePacketBuild(HairRenderObject* hairObject); - Data::Instance GetShader() { return m_shader; } + Data::Instance GetShader(); void SetFeatureProcessor(HairFeatureProcessor* featureProcessor) { @@ -76,7 +76,6 @@ namespace AZ // Pass behavior overrides void InitializeInternal() override; -// void BuildInternal() override; void FrameBeginInternal(FramePrepareParams params) override; // Scope producer functions... diff --git a/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.cpp b/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.cpp index fa643a5488..de66d91e0b 100644 --- a/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.cpp +++ b/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.cpp @@ -30,6 +30,17 @@ namespace AZ HairPPLLResolvePass::HairPPLLResolvePass(const RPI::PassDescriptor& descriptor) : RPI::FullscreenTrianglePass(descriptor) { + o_enableShadows = AZ::Name("o_enableShadows"); + o_enableDirectionalLights = AZ::Name("o_enableDirectionalLights"); + o_enablePunctualLights = AZ::Name("o_enablePunctualLights"); + o_enableAreaLights = AZ::Name("o_enableAreaLights"); + o_enableIBL = AZ::Name("o_enableIBL"); + o_hairLightingModel = AZ::Name("o_hairLightingModel"); + o_enableMarschner_R = AZ::Name("o_enableMarschner_R"); + o_enableMarschner_TRT = AZ::Name("o_enableMarschner_TRT"); + o_enableMarschner_TT = AZ::Name("o_enableMarschner_TT"); + o_enableLongtitudeCoeff = AZ::Name("o_enableLongtitudeCoeff"); + o_enableAzimuthCoeff = AZ::Name("o_enableAzimuthCoeff"); } void HairPPLLResolvePass::UpdateGlobalShaderOptions() @@ -38,17 +49,17 @@ namespace AZ m_featureProcessor->GetHairGlobalSettings(m_hairGlobalSettings); - shaderOption.SetValue(AZ::Name("o_enableShadows"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableShadows }); - shaderOption.SetValue(AZ::Name("o_enableDirectionalLights"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableDirectionalLights }); - shaderOption.SetValue(AZ::Name("o_enablePunctualLights"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enablePunctualLights }); - shaderOption.SetValue(AZ::Name("o_enableAreaLights"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAreaLights }); - shaderOption.SetValue(AZ::Name("o_enableIBL"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableIBL }); - shaderOption.SetValue(AZ::Name("o_hairLightingModel"), AZ::Name{ "HairLightingModel::" + AZStd::string(HairLightingModelNamespace::ToString(m_hairGlobalSettings.m_hairLightingModel)) }); - shaderOption.SetValue(AZ::Name("o_enableMarschner_R"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_R }); - shaderOption.SetValue(AZ::Name("o_enableMarschner_TRT"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TRT }); - shaderOption.SetValue(AZ::Name("o_enableMarschner_TT"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TT }); - shaderOption.SetValue(AZ::Name("o_enableLongtitudeCoeff"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableLongtitudeCoeff }); - shaderOption.SetValue(AZ::Name("o_enableAzimuthCoeff"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAzimuthCoeff }); + shaderOption.SetValue(o_enableShadows, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableShadows }); + shaderOption.SetValue(o_enableDirectionalLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableDirectionalLights }); + shaderOption.SetValue(o_enablePunctualLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enablePunctualLights }); + shaderOption.SetValue(o_enableAreaLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAreaLights }); + shaderOption.SetValue(o_enableIBL, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableIBL }); + shaderOption.SetValue(o_hairLightingModel, AZ::Name{ "HairLightingModel::" + AZStd::string(HairLightingModelNamespace::ToString(m_hairGlobalSettings.m_hairLightingModel)) }); + shaderOption.SetValue(o_enableMarschner_R, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_R }); + shaderOption.SetValue(o_enableMarschner_TRT, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TRT }); + shaderOption.SetValue(o_enableMarschner_TT, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TT }); + shaderOption.SetValue(o_enableLongtitudeCoeff, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableLongtitudeCoeff }); + shaderOption.SetValue(o_enableAzimuthCoeff, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAzimuthCoeff }); m_shaderOptions = shaderOption.GetShaderVariantKeyFallbackValue(); } diff --git a/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.h b/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.h index bf1a3f768e..036659b9a5 100644 --- a/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.h +++ b/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.h @@ -58,9 +58,20 @@ namespace AZ void CompileResources(const RHI::FrameGraphCompileContext& context) override; private: + AZ::Name o_enableShadows; + AZ::Name o_enableDirectionalLights; + AZ::Name o_enablePunctualLights; + AZ::Name o_enableAreaLights; + AZ::Name o_enableIBL; + AZ::Name o_hairLightingModel; + AZ::Name o_enableMarschner_R; + AZ::Name o_enableMarschner_TRT; + AZ::Name o_enableMarschner_TT; + AZ::Name o_enableLongtitudeCoeff; + AZ::Name o_enableAzimuthCoeff; + HairPPLLResolvePass(const RPI::PassDescriptor& descriptor); - private: void UpdateGlobalShaderOptions(); HairGlobalSettings m_hairGlobalSettings; diff --git a/Gems/AtomTressFX/Code/Passes/HairParentPass.cpp b/Gems/AtomTressFX/Code/Passes/HairParentPass.cpp index 24cd6465dd..1b0b034dba 100644 --- a/Gems/AtomTressFX/Code/Passes/HairParentPass.cpp +++ b/Gems/AtomTressFX/Code/Passes/HairParentPass.cpp @@ -9,7 +9,6 @@ #include #include #include -#include namespace AZ { diff --git a/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.cpp b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.cpp new file mode 100644 index 0000000000..5e402e33f6 --- /dev/null +++ b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +#include +#include +#include + +namespace AZ +{ + namespace Render + { + namespace Hair + { + + HairShortCutGeometryDepthAlphaPass::HairShortCutGeometryDepthAlphaPass(const RPI::PassDescriptor& descriptor) + : HairGeometryRasterPass(descriptor) + { + SetShaderPath("Shaders/hairshortcutgeometrydepthalpha.azshader"); + } + + RPI::Ptr HairShortCutGeometryDepthAlphaPass::Create(const RPI::PassDescriptor& descriptor) + { + RPI::Ptr pass = aznew HairShortCutGeometryDepthAlphaPass(descriptor); + return pass; + } + + void HairShortCutGeometryDepthAlphaPass::BuildInternal() + { + RasterPass::BuildInternal(); // change this to call parent if the method exists + + if (!AcquireFeatureProcessor()) + { + return; + } + + LoadShaderAndPipelineState(); + } + + } // namespace Hair + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.h b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.h new file mode 100644 index 0000000000..da6e28fe31 --- /dev/null +++ b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include + +namespace AZ +{ + namespace RHI + { + struct DrawItem; + } + + namespace Render + { + namespace Hair + { + //! This geometry pass uses the following Srgs: + //! - PerPassSrg shared by all hair passes for the shared dynamic buffer + //! - PerMaterialSrg - used solely by this pass to alter the vertices and apply the visual + //! hair properties to each fragment. + //! - HairDynamicDataSrg (PerObjectSrg) - shared buffers views for this hair object only. + //! - PerViewSrg and PerSceneSrg - as per the data from Atom. + class HairShortCutGeometryDepthAlphaPass + : public HairGeometryRasterPass + { + AZ_RPI_PASS(HairShortCutGeometryDepthAlphaPass); + + public: + AZ_RTTI(HairShortCutGeometryDepthAlphaPass, "{F09A0411-B1FF-4085-98E7-6B8B0E1B2C3D}", HairGeometryRasterPass); + AZ_CLASS_ALLOCATOR(HairShortCutGeometryDepthAlphaPass, SystemAllocator, 0); + + static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); + + protected: + explicit HairShortCutGeometryDepthAlphaPass(const RPI::PassDescriptor& descriptor); + + // Pass behavior overrides + void BuildInternal() override; + }; + + } // namespace Hair + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.cpp b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.cpp new file mode 100644 index 0000000000..eda7f9f83a --- /dev/null +++ b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +#include +#include +#include + +namespace AZ +{ + namespace Render + { + namespace Hair + { + + HairShortCutGeometryShadingPass::HairShortCutGeometryShadingPass(const RPI::PassDescriptor& descriptor) + : HairGeometryRasterPass(descriptor) + { + o_enableShadows = AZ::Name("o_enableShadows"); + o_enableDirectionalLights = AZ::Name("o_enableDirectionalLights"); + o_enablePunctualLights = AZ::Name("o_enablePunctualLights"); + o_enableAreaLights = AZ::Name("o_enableAreaLights"); + o_enableIBL = AZ::Name("o_enableIBL"); + o_hairLightingModel = AZ::Name("o_hairLightingModel"); + o_enableMarschner_R = AZ::Name("o_enableMarschner_R"); + o_enableMarschner_TRT = AZ::Name("o_enableMarschner_TRT"); + o_enableMarschner_TT = AZ::Name("o_enableMarschner_TT"); + o_enableLongtitudeCoeff = AZ::Name("o_enableLongtitudeCoeff"); + o_enableAzimuthCoeff = AZ::Name("o_enableAzimuthCoeff"); + + SetShaderPath("Shaders/hairshortcutgeometryshading.azshader"); + } + + RPI::Ptr HairShortCutGeometryShadingPass::Create(const RPI::PassDescriptor& descriptor) + { + RPI::Ptr pass = aznew HairShortCutGeometryShadingPass(descriptor); + return pass; + } + + void HairShortCutGeometryShadingPass::UpdateGlobalShaderOptions() + { + RPI::ShaderOptionGroup shaderOption = m_shader->CreateShaderOptionGroup(); + + m_featureProcessor->GetHairGlobalSettings(m_hairGlobalSettings); + + shaderOption.SetValue(o_enableShadows, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableShadows }); + shaderOption.SetValue(o_enableDirectionalLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableDirectionalLights }); + shaderOption.SetValue(o_enablePunctualLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enablePunctualLights }); + shaderOption.SetValue(o_enableAreaLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAreaLights }); + shaderOption.SetValue(o_enableIBL, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableIBL }); + shaderOption.SetValue(o_hairLightingModel, AZ::Name{ "HairLightingModel::" + AZStd::string(HairLightingModelNamespace::ToString(m_hairGlobalSettings.m_hairLightingModel)) }); + shaderOption.SetValue(o_enableMarschner_R, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_R }); + shaderOption.SetValue(o_enableMarschner_TRT, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TRT }); + shaderOption.SetValue(o_enableMarschner_TT, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TT }); + shaderOption.SetValue(o_enableLongtitudeCoeff, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableLongtitudeCoeff }); + shaderOption.SetValue(o_enableAzimuthCoeff, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAzimuthCoeff }); + + m_shaderOptions = shaderOption.GetShaderVariantKeyFallbackValue(); + } + + void HairShortCutGeometryShadingPass::CompileResources(const RHI::FrameGraphCompileContext& context) + { + if (!m_shaderResourceGroup || !AcquireFeatureProcessor()) + { + AZ_Error("Hair Gem", m_shaderResourceGroup, "HairShortCutGeometryShadingPass: missing Srg or no feature processor yet"); + return; // no error message due to FP - initialization not complete yet, wait for the next frame + } + + UpdateGlobalShaderOptions(); + + if (m_shaderResourceGroup->HasShaderVariantKeyFallbackEntry()) + { + m_shaderResourceGroup->SetShaderVariantKeyFallbackValue(m_shaderOptions); + } + + // Update the material array constant buffer within the per pass srg + SrgBufferDescriptor descriptor = SrgBufferDescriptor( + RPI::CommonBufferPoolType::Constant, RHI::Format::Unknown, + sizeof(AMD::TressFXShadeParams), 1, + Name{ "HairMaterialsArray" }, Name{ "m_hairParams" }, 0, 0 + ); + + m_featureProcessor->GetMaterialsArray().UpdateGPUData(m_shaderResourceGroup, descriptor); + + // Compilation of remaining srgs will be done by the parent class + RPI::RasterPass::CompileResources(context); + } + + void HairShortCutGeometryShadingPass::BuildInternal() + { + RasterPass::BuildInternal(); // change this to call parent if the method exists + + if (!AcquireFeatureProcessor()) + { + return; + } + + LoadShaderAndPipelineState(); + } + + } // namespace Hair + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.h b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.h new file mode 100644 index 0000000000..d728803473 --- /dev/null +++ b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include + +namespace AZ +{ + namespace RHI + { + struct DrawItem; + } + + namespace Render + { + namespace Hair + { + //! This geometry pass uses the following Srgs: + //! - PerPassSrg shared by all hair passes for the shared dynamic buffer + //! - PerMaterialSrg - used solely by this pass to alter the vertices and apply the visual + //! hair properties to each fragment. + //! - HairDynamicDataSrg (PerObjectSrg) - shared buffers views for this hair object only. + //! - PerViewSrg and PerSceneSrg - as per the data from Atom. + class HairShortCutGeometryShadingPass + : public HairGeometryRasterPass + { + AZ_RPI_PASS(HairShortCutGeometryShadingPass); + + public: + AZ_RTTI(HairShortCutGeometryShadingPass, "{11BA673D-0788-4B25-978D-9737BF4E48FE}", HairGeometryRasterPass); + AZ_CLASS_ALLOCATOR(HairShortCutGeometryShadingPass, SystemAllocator, 0); + + static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); + + void CompileResources(const RHI::FrameGraphCompileContext& context) override; + + protected: + AZ::Name o_enableShadows; + AZ::Name o_enableDirectionalLights; + AZ::Name o_enablePunctualLights; + AZ::Name o_enableAreaLights; + AZ::Name o_enableIBL; + AZ::Name o_hairLightingModel; + AZ::Name o_enableMarschner_R; + AZ::Name o_enableMarschner_TRT; + AZ::Name o_enableMarschner_TT; + AZ::Name o_enableLongtitudeCoeff; + AZ::Name o_enableAzimuthCoeff; + + explicit HairShortCutGeometryShadingPass(const RPI::PassDescriptor& descriptor); + + void UpdateGlobalShaderOptions(); + + // Pass behavior overrides + void BuildInternal() override; + + HairGlobalSettings m_hairGlobalSettings; + AZ::RPI::ShaderVariantKey m_shaderOptions; + }; + + } // namespace Hair + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp index 7a317dc09c..b7c6bbce13 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp +++ b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp @@ -47,11 +47,11 @@ namespace AZ HairFeatureProcessor::HairFeatureProcessor() { + m_usePPLLRenderTechnique = false; // Use the ShortCut rendering technique + HairParentPassName = Name{ "HairParentPass" }; - HairPPLLRasterPassName = Name{ "HairPPLLRasterPass" }; - HairPPLLResolvePassName = Name{ "HairPPLLResolvePass" }; - + // Hair Skinning and Simulation Compute passes GlobalShapeConstraintsPassName = Name{ "HairGlobalShapeConstraintsComputePass" }; CalculateStrandDataPassName = Name{ "HairCalculateStrandLevelDataComputePass" }; VelocityShockPropagationPassName = Name{ "HairVelocityShockPropagationComputePass" }; @@ -59,12 +59,21 @@ namespace AZ LengthConstriantsWindAndCollisionPassName = Name{ "HairLengthConstraintsWindAndCollisionComputePass" }; UpdateFollowHairPassName = Name{ "HairUpdateFollowHairComputePass" }; + // PPLL render technique pases + HairPPLLRasterPassName = Name{ "HairPPLLRasterPass" }; + HairPPLLResolvePassName = Name{ "HairPPLLResolvePass" }; + + // ShortCut render technique pases + HairShortCutGeometryDepthAlphaPassName = Name{ "HairShortCutGeometryDepthAlphaPass" }; + HairShortCutResolveDepthPassName = Name{ "HairShortCutResolveDepthPass" }; + HairShortCutGeometryShadingPassName = Name{ "HairShortCutGeometryShadingPass" }; + HairShortCutResolveColorPassName = Name{ "HairShortCutResolveColorPass" }; + ++s_instanceCount; if (!CreatePerPassResources()) { // this might not be an error - if the pass system is still empty / minimal - // and these passes are not part of the minimal pipeline, they will not - // be created. + // and these passes are not part of the minimal pipeline, they will not be created. AZ_Error("Hair Gem", false, "Failed to create the hair shared buffer resource"); } } @@ -127,25 +136,33 @@ namespace AZ m_hairRenderObjects.push_back(renderObject); + // Adding the object will schedule Srgs binding and the DrawItem build for the geometry passes. BuildDispatchAndDrawItems(renderObject); EnablePasses(true); } - void HairFeatureProcessor::EnablePasses(bool enable) + void HairFeatureProcessor::EnablePasses([[maybe_unused]] bool enable) { + return; + + // [To Do] - This part should be enabled (remove the return) to reduce overhead + // when Hair is disabled / doesn't exist in the scene. + // Currently it might break features such as fog that depend on the output and for some + // reason doesn't quite work for ShortCut. + // The current overhead is minimal (< 0.1 msec) and this Gem is disabled by default. +/* if (!m_initialized) { return; } - for (auto& [passName, pass] : m_computePasses) + RPI::Ptr desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairParentPassName); + if (desiredPass) { - pass->SetEnabled(enable); + desiredPass->SetEnabled(enable); } - - m_hairPPLLRasterPass->SetEnabled(enable); - m_hairPPLLResolvePass->SetEnabled(enable); +*/ } bool HairFeatureProcessor::RemoveHairRenderObject(Data::Instance renderObject) @@ -214,7 +231,8 @@ namespace AZ } if (m_forceRebuildRenderData) - { + { // In the case of a force build, schedule Srgs binding and the DrawItem build for + // the geometry passes of all existing hair objects. for (auto& hairRenderObject : m_hairRenderObjects) { BuildDispatchAndDrawItems(hairRenderObject); @@ -276,17 +294,32 @@ namespace AZ pass->AddDispatchItems(m_hairRenderObjects); } - // Add all hair objects to the Render / Raster Pass - m_hairPPLLRasterPass->AddDrawPackets(m_hairRenderObjects); + if (m_usePPLLRenderTechnique) + { + // Add all hair objects to the Render / Raster Pass + m_hairPPLLRasterPass->AddDrawPackets(m_hairRenderObjects); + } + else + { + m_hairShortCutGeometryDepthAlphaPass->AddDrawPackets(m_hairRenderObjects); + m_hairShortCutGeometryShadingPass->AddDrawPackets(m_hairRenderObjects); + } } void HairFeatureProcessor::ClearPasses() { m_initialized = false; // Avoid simulation or render m_computePasses.clear(); + + // PPLL geometry and resolve full screen passes m_hairPPLLRasterPass = nullptr; m_hairPPLLResolvePass = nullptr; + // ShortCut passes - Special handling of geometry passes only, and using the regular + // full screen pass for the resolve + m_hairShortCutGeometryDepthAlphaPass = nullptr; + m_hairShortCutGeometryShadingPass = nullptr; + // Mark for all passes to evacuate their render data and recreate it. m_forceRebuildRenderData = true; m_forceClearRenderData = true; @@ -338,6 +371,12 @@ namespace AZ ClearPasses(); + if (!m_renderPipeline) + { + AZ_Error("Hair Gem", false, "HairFeatureProcessor does NOT have render pipeline set yet"); + return false; + } + // Compute Passes - populate the passes map bool resultSuccess = InitComputePass(GlobalShapeConstraintsPassName); resultSuccess &= InitComputePass(CalculateStrandDataPassName); @@ -347,8 +386,15 @@ namespace AZ resultSuccess &= InitComputePass(UpdateFollowHairPassName); // Rendering Passes - resultSuccess &= InitPPLLFillPass(); - resultSuccess &= InitPPLLResolvePass(); + if (m_usePPLLRenderTechnique) + { + resultSuccess &= InitPPLLFillPass(); + resultSuccess &= InitPPLLResolvePass(); + } + else + { + resultSuccess &= InitShortCutRenderPasses(); + } m_initialized = resultSuccess; @@ -388,7 +434,8 @@ namespace AZ } } - // PPLL nodes buffer + // PPLL nodes buffer - created only if the PPLL technique is used + if (m_usePPLLRenderTechnique) { descriptor = SrgBufferDescriptor( RPI::CommonBufferPoolType::ReadWrite, RHI::Format::Unknown, @@ -425,11 +472,6 @@ namespace AZ bool HairFeatureProcessor::InitComputePass(const Name& passName, bool allowIterations) { m_computePasses[passName] = nullptr; - if (!m_renderPipeline) - { - AZ_Error("Hair Gem", false, "%s does NOT have render pipeline set yet", passName.GetCStr()); - return false; - } RPI::Ptr desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(passName); if (desiredPass) @@ -452,11 +494,6 @@ namespace AZ bool HairFeatureProcessor::InitPPLLFillPass() { m_hairPPLLRasterPass = nullptr; // reset it to null, just in case it fails to load the assets properly - if (!m_renderPipeline) - { - AZ_Error("Hair Gem", false, "Hair Fill Pass does NOT have render pipeline set yet"); - return false; - } RPI::Ptr desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairPPLLRasterPassName); if (desiredPass) @@ -466,7 +503,7 @@ namespace AZ } else { - AZ_Error("Hair Gem", false, "HairPPLLRasterPass does not have any valid passes. Check your game project's .pass assets."); + AZ_Error("Hair Gem", false, "HairPPLLRasterPass cannot be found. Check your game project's .pass assets."); return false; } return true; @@ -475,11 +512,6 @@ namespace AZ bool HairFeatureProcessor::InitPPLLResolvePass() { m_hairPPLLResolvePass = nullptr; // reset it to null, just in case it fails to load the assets properly - if (!m_renderPipeline) - { - AZ_Error("Hair Gem", false, "Hair Fill Pass does NOT have render pipeline set yet"); - return false; - } RPI::Ptr desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairPPLLResolvePassName); if (desiredPass) @@ -489,12 +521,46 @@ namespace AZ } else { - AZ_Error("Hair Gem", false, "HairPPLLResolvePassTemplate does not have valid passes. Check your game project's .pass assets."); + AZ_Error("Hair Gem", false, "HairPPLLResolvePass cannot be found. Check your game project's .pass assets."); return false; } return true; } + //! Set the two short cut geometry pases and assign them the FP. The other two full screen passes + //! are generic full screen passes and don't need any interaction with the FP. + bool HairFeatureProcessor::InitShortCutRenderPasses() + { + m_hairShortCutGeometryDepthAlphaPass = nullptr; + m_hairShortCutGeometryShadingPass = nullptr; + + m_hairShortCutGeometryDepthAlphaPass = static_cast( + m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairShortCutGeometryDepthAlphaPassName).get()); + if (m_hairShortCutGeometryDepthAlphaPass) + { + m_hairShortCutGeometryDepthAlphaPass->SetFeatureProcessor(this); + } + else + { + AZ_Error("Hair Gem", false, "HairShortCutResolveDepthPass cannot be found. Check your game project's .pass assets."); + return false; + } + + m_hairShortCutGeometryShadingPass = static_cast( + m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairShortCutGeometryShadingPassName).get()); + if (m_hairShortCutGeometryShadingPass) + { + m_hairShortCutGeometryShadingPass->SetFeatureProcessor(this); + } + else + { + AZ_Error("Hair Gem", false, "HairShortCutGeometryShadingPass cannot be found. Check your game project's .pass assets."); + return false; + } + + return true; + } + void HairFeatureProcessor::BuildDispatchAndDrawItems(Data::Instance renderObject) { HairRenderObject* renderObjectPtr = renderObject.get(); @@ -513,9 +579,18 @@ namespace AZ m_computePasses[UpdateFollowHairPassName]->BuildDispatchItem( renderObjectPtr, DispatchLevel::DISPATCHLEVEL_VERTEX); - // Render / Raster pass - adding the object will schedule Srgs binding - // and DrawItem build. - m_hairPPLLRasterPass->SchedulePacketBuild(renderObjectPtr); + // Schedule Srgs binding and the DrawItem build. + // Since this does not bind the PerPass srg but prepare the rest of the Srgs + // such as the dynamic srg, it should only be done once per object per frame. + if (m_usePPLLRenderTechnique) + { + m_hairPPLLRasterPass->SchedulePacketBuild(renderObjectPtr); + } + else + { + m_hairShortCutGeometryDepthAlphaPass->SchedulePacketBuild(renderObjectPtr); + m_hairShortCutGeometryShadingPass->SchedulePacketBuild(renderObjectPtr); + } } Data::Instance HairFeatureProcessor::GetHairSkinningComputegPass() @@ -527,14 +602,28 @@ namespace AZ return m_computePasses[GlobalShapeConstraintsPassName]; } - Data::Instance HairFeatureProcessor::GetHairPPLLRasterPass() + Data::Instance HairFeatureProcessor::GetGeometryRasterShader() { - if (!m_hairPPLLRasterPass) + if (m_usePPLLRenderTechnique) { - Init(m_renderPipeline); + if (!m_hairPPLLRasterPass && !Init(m_renderPipeline)) + { + AZ_Error("Hair Gem", false, + "GetGeometryRasterShader - m_hairPPLLRasterPass was not created"); + return nullptr; + } + return m_hairPPLLRasterPass->GetShader(); } - return m_hairPPLLRasterPass; + + if (!m_hairShortCutGeometryDepthAlphaPass && !Init(m_renderPipeline)) + { + AZ_Error("Hair Gem", false, + "GetGeometryRasterShader - m_hairShortCutGeometryDepthAlphaPass was not created"); + return nullptr; + } + return m_hairShortCutGeometryDepthAlphaPass->GetShader(); } + } // namespace Hair } // namespace Render } // namespace AZ diff --git a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h index c56dc5c921..46660a6623 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h +++ b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h @@ -17,14 +17,19 @@ #include #include +#include // Hair specific #include #include + #include #include +#include +#include + #include #include #include @@ -73,9 +78,7 @@ namespace AZ { Name HairParentPassName; - Name HairPPLLRasterPassName; - Name HairPPLLResolvePassName; - + // Compute passes Name GlobalShapeConstraintsPassName; Name CalculateStrandDataPassName; Name VelocityShockPropagationPassName; @@ -83,6 +86,16 @@ namespace AZ Name LengthConstriantsWindAndCollisionPassName; Name UpdateFollowHairPassName; + // PPLL render passes + Name HairPPLLRasterPassName; + Name HairPPLLResolvePassName; + + // ShortCut render passes + Name HairShortCutGeometryDepthAlphaPassName; + Name HairShortCutResolveDepthPassName; + Name HairShortCutGeometryShadingPassName; + Name HairShortCutResolveColorPassName; + public: AZ_RTTI(AZ::Render::Hair::HairFeatureProcessor, "{5F9DDA81-B43F-4E30-9E56-C7C3DC517A4C}", RPI::FeatureProcessor); @@ -117,6 +130,7 @@ namespace AZ Data::Instance GetHairSkinningComputegPass(); Data::Instance GetHairPPLLRasterPass(); + Data::Instance GetGeometryRasterShader(); //! Update the hair objects materials array. void FillHairMaterialsArray(std::vector& renderSettings); @@ -144,6 +158,7 @@ namespace AZ bool InitPPLLFillPass(); bool InitPPLLResolvePass(); + bool InitShortCutRenderPasses(); bool InitComputePass(const Name& passName, bool allowIterations = false); void BuildDispatchAndDrawItems(Data::Instance renderObject); @@ -168,10 +183,14 @@ namespace AZ //! Simulation Compute Passes AZStd::unordered_map > m_computePasses; - // Render Passes + // PPLL Render Passes Data::Instance m_hairPPLLRasterPass = nullptr; Data::Instance m_hairPPLLResolvePass = nullptr; + // ShortCut Render Passes - special case for the geometry render passes + Data::Instance m_hairShortCutGeometryDepthAlphaPass = nullptr; + Data::Instance m_hairShortCutGeometryShadingPass = nullptr; + //-------------------------------------------------------------- // Per Pass Resources //-------------------------------------------------------------- @@ -196,6 +215,7 @@ namespace AZ bool m_forceClearRenderData = false; bool m_initialized = false; bool m_isEnabled = true; + bool m_usePPLLRenderTechnique = true; static uint32_t s_instanceCount; HairGlobalSettings m_hairGlobalSettings; diff --git a/Gems/AtomTressFX/Code/Rendering/HairRenderObject.cpp b/Gems/AtomTressFX/Code/Rendering/HairRenderObject.cpp index baf986daf6..4d615e31ae 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairRenderObject.cpp +++ b/Gems/AtomTressFX/Code/Rendering/HairRenderObject.cpp @@ -993,7 +993,7 @@ namespace AZ //------------------------------------- // Dynamic buffers, data and Srg creation - shared between passes and changed on the GPU if (!m_dynamicHairData.CreateDynamicGPUResources( - m_skinningShader, m_PPLLFillShader, + m_skinningShader, m_geometryRasterShader, m_NumTotalVertices, m_NumTotalStrands)) { AZ_Error("Hair Gem", false, "Hair - Error creating dynamic resources [%s]", assetName ); @@ -1028,7 +1028,7 @@ namespace AZ // Rendering setup bool renderResourcesSuccess; - renderResourcesSuccess = CreateRenderingGPUResources(m_PPLLFillShader, *asset, assetName); + renderResourcesSuccess = CreateRenderingGPUResources(m_geometryRasterShader, *asset, assetName); renderResourcesSuccess &= PopulateDrawStrandsBindSet(renderSettings); renderResourcesSuccess &= UploadRenderingGPUResources(*asset); @@ -1057,17 +1057,10 @@ namespace AZ } { - Data::Instance rasterPass = m_featureProcessor->GetHairPPLLRasterPass(); - if (!rasterPass.get()) + m_geometryRasterShader = m_featureProcessor->GetGeometryRasterShader(); + if (!m_geometryRasterShader) { - AZ_Error("Hair Gem", false, "Failed to get PPLL raster fill Pass."); - return false; - } - - m_PPLLFillShader = rasterPass->GetShader(); - if (!m_PPLLFillShader) - { - AZ_Error("Hair Gem", false, "Failed to get hair raster fill shader from raster pass"); + AZ_Error("Hair Gem", false, "Failed to get hair geometry raster shader"); return false; } } @@ -1116,7 +1109,7 @@ namespace AZ return updatedCB; } - bool HairRenderObject::BuildPPLLDrawPacket(RHI::DrawPacketBuilder::DrawRequest& drawRequest) + bool HairRenderObject::BuildDrawPacket(RPI::Shader* geometryShader, RHI::DrawPacketBuilder::DrawRequest& drawRequest) { RHI::DrawPacketBuilder drawPacketBuilder; RHI::DrawIndexed drawIndexed; @@ -1159,21 +1152,38 @@ namespace AZ drawPacketBuilder.AddShaderResourceGroup(simSrg->GetRHIShaderResourceGroup()); drawPacketBuilder.AddDrawItem(drawRequest); - if (m_fillDrawPacket) - { - delete m_fillDrawPacket; - } - m_fillDrawPacket = drawPacketBuilder.End(); - - if (!m_fillDrawPacket) + const RHI::DrawPacket* drawPacket = drawPacketBuilder.End(); + if (!drawPacket) { AZ_Error("Hair Gem", false, "Failed to build the hair DrawPacket."); return false; } + // Insert the newly created draw packet to the map based on its shader + auto iter = m_geometryDrawPackets.find(geometryShader); + if (iter != m_geometryDrawPackets.end()) + { + delete iter->second; + iter->second = drawPacket; + } + else + { + m_geometryDrawPackets[geometryShader] = drawPacket; + } + return true; } + const RHI::DrawPacket* HairRenderObject::GetGeometrylDrawPacket(RPI::Shader* geometryShader) + { + auto iter = m_geometryDrawPackets.find(geometryShader); + if (iter == m_geometryDrawPackets.end()) + { + return nullptr; + } + return iter->second; + } + const RHI::DispatchItem* HairRenderObject::GetDispatchItem(RPI::Shader* computeShader) { auto dispatchIter = m_dispatchItems.find(computeShader); @@ -1210,4 +1220,3 @@ namespace AZ } // namespace Hair } // namespace Render } // namespace AZ - diff --git a/Gems/AtomTressFX/Code/Rendering/HairRenderObject.h b/Gems/AtomTressFX/Code/Rendering/HairRenderObject.h index fa4095eed0..817ebd89fa 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairRenderObject.h +++ b/Gems/AtomTressFX/Code/Rendering/HairRenderObject.h @@ -179,14 +179,9 @@ namespace AZ AMD::TressFXSimulationSettings* simSettings, AMD::TressFXRenderingSettings* renderSettings ); - //! Creates and fill the draw item associated with the PPLL render of the - //! current hair object - const RHI::DrawPacket* GetFillDrawPacket() - { - return m_fillDrawPacket; - } + bool BuildDrawPacket(RPI::Shader* geometryShader, RHI::DrawPacketBuilder::DrawRequest& drawRequest); - bool BuildPPLLDrawPacket(RHI::DrawPacketBuilder::DrawRequest& drawRequest); + const RHI::DrawPacket* GetGeometrylDrawPacket(RPI::Shader* geometryShader); //! Creates and fill the dispatch item associated with the compute shader bool BuildDispatchItem(RPI::Shader* computeShader, DispatchLevel dispatchLevel); @@ -302,17 +297,20 @@ namespace AZ //! responsible for the various stages and passes' updates HairFeatureProcessor* m_featureProcessor = nullptr; - //! The dispatch item used for the skinning - HairDispatchItem m_skinningDispatchItem; - - //! Compute dispatch items map per the existing passes - AZStd::map> m_dispatchItems; - //! Skinning compute shader used for creation of the compute Srgs and dispatch item Data::Instance m_skinningShader = nullptr; - //! PPLL fill shader used for creation of the raster Srgs and draw item - Data::Instance m_PPLLFillShader = nullptr; + //! Compute dispatch items map per the existing passes + AZStd::unordered_map> m_dispatchItems; + + //! Geometry raster shader used for creation of the raster Srgs. + //! Since the Srgs for geometry raster are the same across the shaders we keep + //! only a single shader - if this to change in the future, several shaders and sets + //! of dynamic Srgs should be created. + Data::Instance m_geometryRasterShader = nullptr; + + //! DrawPacket for the multi object geometry raster pass. + AZStd::unordered_map m_geometryDrawPackets; float m_frameDeltaTime = 0.02; @@ -378,9 +376,6 @@ namespace AZ //! Index buffer for the render pass via draw calls - naming was kept Data::Instance m_indexBuffer; RHI::IndexBufferView m_indexBufferView; - - //! DrawPacket for the multi object raster fill pass. - const RHI::DrawPacket* m_fillDrawPacket = nullptr; //------------------------------------------------------------------- AZStd::mutex m_mutex; diff --git a/Gems/AtomTressFX/Hair_files.cmake b/Gems/AtomTressFX/Hair_files.cmake index 000abeb559..180685d499 100644 --- a/Gems/AtomTressFX/Hair_files.cmake +++ b/Gems/AtomTressFX/Hair_files.cmake @@ -67,6 +67,13 @@ set(FILES # Base class of all geometry raster passes Code/Passes/HairGeometryRasterPass.h Code/Passes/HairGeometryRasterPass.cpp + + # ShortCut rendering technique - pass classes + Code/Passes/HairShortCutGeometryDepthAlphaPass.h + Code/Passes/HairShortCutGeometryDepthAlphaPass.cpp + Code/Passes/HairShortCutGeometryShadingPass.h + Code/Passes/HairShortCutGeometryShadingPass.cpp + # PPLL rendering technique - geometry raster pass Code/Passes/HairPPLLRasterPass.h Code/Passes/HairPPLLRasterPass.cpp @@ -84,30 +91,37 @@ set(FILES Code/Assets/HairAsset.cpp #) #set(shaders_sources - # Srgs and Utility files - Assets/Shaders/HairSrgs.azsli - Assets/Shaders/HairSimulationSrgs.azsli + # Geometry and Full Screen azsl utility files Assets/Shaders/HairRenderingSrgs.azsli - Assets/Shaders/HairSimulationCommon.azsli Assets/Shaders/HairStrands.azsli Assets/Shaders/HairUtilities.azsli + Assets/Shaders/HairFullScreenUtils.azsli Assets/Shaders/HairLighting.azsli Assets/Shaders/HairLightingEquations.azsli Assets/Shaders/HairLightTypes.azsli Assets/Shaders/HairSurface.azsli - # Simulation Compute shaders - Assets/Shaders/HairSimulationCompute.azsl - - # Collision shaders - to be included soon -# Assets/Shaders/HairCollisionPrepareSDF.azsl -# Assets/Shaders/HairCollisionWithSDF.azsl + # ShortCut technique shaders (using multiple RTs instead of PPLL for GPU memory reduction) + Assets/Shaders/HairShortCutGeometryDepthAlpha.azsl + Assets/Shaders/HairShortCutResolveDepth.azsl + Assets/Shaders/HairShortCutGeometryShading.azsl + Assets/Shaders/HairShortCutResolveColor.azsl - # Rendering shaders + # Rendering azsl files Assets/Shaders/HairRenderingFillPPLL.azsl Assets/Shaders/HairRenderingResolvePPLL.azsl - # Simulation .shader files + # Simulation Compute azsl files + Assets/Shaders/HairComputeSrgs.azsli + Assets/Shaders/HairSimulationComputeSrgs.azsli + Assets/Shaders/HairSimulationCommon.azsli + Assets/Shaders/HairSimulationCompute.azsl + + # Collision azsl files - to be included soon +# Assets/Shaders/HairCollisionPrepareSDF.azsl +# Assets/Shaders/HairCollisionWithSDF.azsl + + # Simulation Compute .shader files Assets/Shaders/HairGlobalShapeConstraintsCompute.shader Assets/Shaders/HairCalculateStrandLevelDataCompute.shader Assets/Shaders/HairVelocityShockPropagationCompute.shader @@ -115,9 +129,15 @@ set(FILES Assets/Shaders/HairLengthConstraintsWindAndCollisionCompute.shader Assets/Shaders/HairUpdateFollowHairCompute.shader - # Rendering .shader file + # PPLL Render .shader file Assets/Shaders/HairRenderingFillPPLL.shader Assets/Shaders/HairRenderingResolvePPLL.shader + + # ShortCut Render .shader file + Assets/Shaders/HairShortCutGeometryDepthAlpha.shader + Assets/Shaders/HairShortCutResolveDepth.shader + Assets/Shaders/HairShortCutGeometryShading.shader + Assets/Shaders/HairShortCutResolveColor.shader # Colisions .shader files - to be included soon # Assets/Shaders/HairCollisionInitializeSDF.shader @@ -127,15 +147,25 @@ set(FILES #) # #set(atom_hair_passes + # Compute simulation and skinning passes Assets/Passes/HairParentPass.pass + Assets/Passes/HairParentShortCutPass.pass Assets/Passes/HairGlobalShapeConstraintsCompute.pass Assets/Passes/HairCalculateStrandLevelDataCompute.pass Assets/Passes/HairVelocityShockPropagationCompute.pass Assets/Passes/HairLocalShapeConstraintsCompute.pass Assets/Passes/HairLengthConstraintsWindAndCollisionCompute.pass Assets/Passes/HairUpdateFollowHairCompute.pass + + # PPLL render passes Assets/Passes/HairFillPPLL.pass Assets/Passes/HairResolvePPLL.pass + + # Shortcut render passes + Assets/Passes/HairShortCutGeometryDepthAlpha.pass + Assets/Passes/HairShortCutResolveDepth.pass + Assets/Passes/HairShortCutGeometryShading.pass + Assets/Passes/HairShortCutResolveColor.pass ) set(SKIP_UNITY_BUILD_INCLUSION_FILES From 68b585102e5c8b64254683c78e734576b20910de Mon Sep 17 00:00:00 2001 From: hershey5045 <43485729+hershey5045@users.noreply.github.com> Date: Thu, 21 Oct 2021 12:06:46 -0700 Subject: [PATCH 24/27] Re-order some color grading operations. Increase values for SMH to account for HDR colors. (#4856) Signed-off-by: rbarrand Co-authored-by: rbarrand --- .../PostProcessing/HDRColorGradingCommon.azsl | 5 ++--- .../ColorGrading/EditorHDRColorGradingComponent.cpp | 12 ++++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/HDRColorGradingCommon.azsl b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/HDRColorGradingCommon.azsl index 099a6394d4..a380a0a547 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/HDRColorGradingCommon.azsl +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/HDRColorGradingCommon.azsl @@ -138,7 +138,7 @@ float3 ColorGrade(float3 frameColor) PassSrg::m_colorFilterMultiply, PassSrg::m_colorFilterIntensity), PassSrg::m_colorAdjustmentWeight); frameColor = max(frameColor, 0.0); frameColor = lerp(frameColor, ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPreSaturation), PassSrg::m_colorAdjustmentWeight); - + frameColor = max(frameColor, 0.0); frameColor = ColorGradeSplitTone(frameColor, PassSrg::m_splitToneBalance, PassSrg::m_splitToneWeight, PassSrg::m_splitToneShadowsColor, PassSrg::m_splitToneHighlightsColor); frameColor = ColorGradeChannelMixer(frameColor, PassSrg::m_channelMixingRed, PassSrg::m_channelMixingGreen, PassSrg::m_channelMixingBlue); @@ -147,8 +147,7 @@ float3 ColorGrade(float3 frameColor) PassSrg::m_smhHighlightsStart, PassSrg::m_smhHighlightsEnd, PassSrg::m_smhWeight, PassSrg::m_smhShadowsColor, PassSrg::m_smhMidtonesColor, PassSrg::m_smhHighlightsColor); - - frameColor = lerp(frameColor, ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPostSaturation), PassSrg::m_finalAdjustmentWeight); frameColor = lerp(frameColor, ColorGradeHueShift(frameColor, PassSrg::m_colorGradingHueShift), PassSrg::m_finalAdjustmentWeight); + frameColor = lerp(frameColor, ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPostSaturation), PassSrg::m_finalAdjustmentWeight); return max(frameColor.rgb, 0.0); } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ColorGrading/EditorHDRColorGradingComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ColorGrading/EditorHDRColorGradingComponent.cpp index 2d8f83a32a..e32b26f0c5 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ColorGrading/EditorHDRColorGradingComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ColorGrading/EditorHDRColorGradingComponent.cpp @@ -131,16 +131,20 @@ namespace AZ ->Attribute(Edit::Attributes::Max, 1.0f) ->DataElement(AZ::Edit::UIHandlers::Slider, &HDRColorGradingComponentConfig::m_smhShadowsStart, "Shadows Start", "SMH Shadows Start Value") ->Attribute(Edit::Attributes::Min, 0.0f) - ->Attribute(Edit::Attributes::Max, 1.0f) + ->Attribute(Edit::Attributes::Max, 16.0f) + ->Attribute(Edit::Attributes::SoftMax, 2.0f) ->DataElement(AZ::Edit::UIHandlers::Slider, &HDRColorGradingComponentConfig::m_smhShadowsEnd, "Shadows End", "SMH Shadows End Value") ->Attribute(Edit::Attributes::Min, 0.0f) - ->Attribute(Edit::Attributes::Max, 1.0f) + ->Attribute(Edit::Attributes::Max, 16.0f) + ->Attribute(Edit::Attributes::SoftMax, 2.0f) ->DataElement(AZ::Edit::UIHandlers::Slider, &HDRColorGradingComponentConfig::m_smhHighlightsStart, "Highlights Start", "SMH Highlights Start Value") ->Attribute(Edit::Attributes::Min, 0.0f) - ->Attribute(Edit::Attributes::Max, 1.0f) + ->Attribute(Edit::Attributes::Max, 16.0f) + ->Attribute(Edit::Attributes::SoftMax, 2.0f) ->DataElement(AZ::Edit::UIHandlers::Slider, &HDRColorGradingComponentConfig::m_smhHighlightsEnd, "Highlights End", "SMH Highlights End Value") ->Attribute(Edit::Attributes::Min, 0.0f) - ->Attribute(Edit::Attributes::Max, 1.0f) + ->Attribute(Edit::Attributes::Max, 16.0f) + ->Attribute(Edit::Attributes::SoftMax, 2.0f) ->DataElement(AZ::Edit::UIHandlers::Color, &HDRColorGradingComponentConfig::m_smhShadowsColor, "Shadows Color", "SMH Shadows Color") ->DataElement(AZ::Edit::UIHandlers::Color, &HDRColorGradingComponentConfig::m_smhMidtonesColor, "Midtones Color", "SMH Midtones Color") ->DataElement(AZ::Edit::UIHandlers::Color, &HDRColorGradingComponentConfig::m_smhHighlightsColor, "Highlights Color", "SMH Highlights Color") From 731680294138c193c69d0f1f654f4478df5f74d3 Mon Sep 17 00:00:00 2001 From: chiyenteng <82238204+chiyenteng@users.noreply.github.com> Date: Thu, 21 Oct 2021 12:08:00 -0700 Subject: [PATCH 25/27] Add Detach and Duplicate Prefab basic workflow auto test (#4506) - Add a new automated test PrefabBasicWorkflow_CreateReparentAndDetachPrefab for verifying prefab detachment basic workflow. - Add a new automated test PrefabBasicWorkflow_CreateAndDuplicatePrefab for verifying prefab detachment basic workflow. - Fix a bug related to sets of entity ids in Reparent helper function . --- .../editor_python_test_tools/prefab_utils.py | 206 ++++++++++++++---- .../Gem/PythonTests/Prefab/TestSuite_Main.py | 8 + ...efabBasicWorkflow_CreateAndDeletePrefab.py | 7 +- ...bBasicWorkflow_CreateAndDuplicatePrefab.py | 32 +++ ...abBasicWorkflow_CreateAndReparentPrefab.py | 11 +- .../tests/PrefabBasicWorkflow_CreatePrefab.py | 5 +- ...cWorkflow_CreateReparentAndDetachPrefab.py | 51 +++++ .../PrefabBasicWorkflow_InstantiatePrefab.py | 3 +- .../Prefab/tests/PrefabTestUtils.py | 27 --- .../Prefab/EditorPrefabComponent.cpp | 9 + .../Prefab/EditorPrefabComponent.h | 4 +- .../Prefab/PrefabPublicHandler.cpp | 9 +- .../Prefab/PrefabPublicHandler.h | 2 +- .../Prefab/PrefabPublicInterface.h | 8 +- .../Prefab/PrefabPublicRequestBus.h | 24 ++ .../Prefab/PrefabPublicRequestHandler.cpp | 17 ++ .../Prefab/PrefabPublicRequestHandler.h | 3 + .../AzToolsFramework/Prefab/PrefabUndo.cpp | 2 +- .../AzToolsFramework/Prefab/PrefabUndo.h | 2 +- 19 files changed, 328 insertions(+), 102 deletions(-) create mode 100644 AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDuplicatePrefab.py create mode 100644 AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateReparentAndDetachPrefab.py diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py index 2d8b124125..10a6ab1ef4 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py @@ -9,6 +9,7 @@ from __future__ import annotations from collections import Counter from collections import deque from os import path +from pathlib import Path from PySide2 import QtWidgets @@ -20,6 +21,10 @@ from editor_python_test_tools.utils import Report import azlmbr.entity as entity import azlmbr.bus as bus +import azlmbr.components as components +import azlmbr.editor as editor +import azlmbr.globals +import azlmbr.math as math import azlmbr.prefab as prefab import editor_python_test_tools.pyside_utils as pyside_utils @@ -57,26 +62,46 @@ class PrefabInstance: def __hash__(self): return hash(self.container_entity.id) - """ - See if this instance is valid to be used with other prefab operations. - :return: Whether the target instance is valid or not. - """ def is_valid(self) -> bool: + """ + See if this instance is valid to be used with other prefab operations. + :return: Whether the target instance is valid or not. + """ return self.container_entity.id.IsValid() and self.prefab_file_name in Prefab.existing_prefabs - """ - Reparent this instance to target parent entity. - The function will also check pop up dialog ui in editor to see if there's prefab cyclical dependency error while reparenting prefabs. - :param parent_entity_id: The id of the entity this instance should be a child of in the transform hierarchy next. - """ + def has_editor_prefab_component(self) -> bool: + """ + Check if the instance's container entity contains EditorPrefabComponent. + :return: Whether the container entity of target instance has EditorPrefabComponent in it or not. + """ + return editor.EditorComponentAPIBus(bus.Broadcast, "HasComponentOfType", self.container_entity.id, azlmbr.globals.property.EditorPrefabComponentTypeId) + + def is_at_position(self, expected_position): + """ + Check if the instance's container entity is at expected position given. + :return: Whether the container entity of target instance is at expected position or not. + """ + actual_position = components.TransformBus(bus.Event, "GetWorldTranslation", self.container_entity.id) + is_at_position = actual_position.IsClose(expected_position) + + if not is_at_position: + Report.info(f"Prefab Instance Container Entity '{self.container_entity.id.ToString()}'\'s expected position: {expected_position.ToString()}, actual position: {actual_position.ToString()}") + + return is_at_position + async def ui_reparent_prefab_instance(self, parent_entity_id: EntityId): + """ + Reparent this instance to target parent entity. + The function will also check pop up dialog ui in editor to see if there's prefab cyclical dependency error while reparenting prefabs. + :param parent_entity_id: The id of the entity this instance should be a child of in the transform hierarchy next. + """ container_entity_id_before_reparent = self.container_entity.id original_parent = EditorEntity(self.container_entity.get_parent_id()) - original_parent_before_reparent_children_ids = set(original_parent.get_children_ids()) + original_parent_before_reparent_children_ids = {child_id.ToString(): child_id for child_id in original_parent.get_children_ids()} new_parent = EditorEntity(parent_entity_id) - new_parent_before_reparent_children_ids = set(new_parent.get_children_ids()) + new_parent_before_reparent_children_ids = {child_id.ToString(): child_id for child_id in new_parent.get_children_ids()} pyside_utils.run_soon(lambda: self.container_entity.set_parent_entity(parent_entity_id)) pyside_utils.run_soon(lambda: wait_for_propagation()) @@ -90,23 +115,28 @@ class PrefabInstance: except pyside_utils.EventLoopTimeoutException: pass - original_parent_after_reparent_children_ids = set(original_parent.get_children_ids()) + original_parent_after_reparent_children_ids = {child_id.ToString(): child_id for child_id in original_parent.get_children_ids()} assert len(original_parent_after_reparent_children_ids) == len(original_parent_before_reparent_children_ids) - 1, \ "The children count of the Prefab Instance's original parent should be decreased by 1." assert not container_entity_id_before_reparent in original_parent_after_reparent_children_ids, \ "This Prefab Instance is still a child entity of its original parent entity." - new_parent_after_reparent_children_ids = set(new_parent.get_children_ids()) + new_parent_after_reparent_children_ids = {child_id.ToString(): child_id for child_id in new_parent.get_children_ids()} assert len(new_parent_after_reparent_children_ids) == len(new_parent_before_reparent_children_ids) + 1, \ "The children count of the Prefab Instance's new parent should be increased by 1." - container_entity_id_after_reparent = set(new_parent_after_reparent_children_ids).difference(new_parent_before_reparent_children_ids).pop() + after_before_diff = set(new_parent_after_reparent_children_ids.keys()).difference(set(new_parent_before_reparent_children_ids.keys())) + container_entity_id_after_reparent = new_parent_after_reparent_children_ids[after_before_diff.pop()] reparented_container_entity = EditorEntity(container_entity_id_after_reparent) reparented_container_entity_parent_id = reparented_container_entity.get_parent_id() has_correct_parent = reparented_container_entity_parent_id.ToString() == parent_entity_id.ToString() assert has_correct_parent, "Prefab Instance reparented is *not* under the expected parent entity" + current_instance_prefab = Prefab.get_prefab(self.prefab_file_name) + current_instance_prefab.instances.remove(self) + self.container_entity = reparented_container_entity + current_instance_prefab.instances.add(self) # This is a helper class which contains some of the useful information about a prefab template. class Prefab: @@ -117,31 +147,32 @@ class Prefab: self.file_path: str = get_prefab_file_path(file_path) self.instances: set[PrefabInstance] = set() - """ - Check if a prefab is ready to be used to generate its instances. - :param file_path: A unique file path of the target prefab. - :return: Whether the target prefab is loaded or not. - """ @classmethod def is_prefab_loaded(cls, file_path: str) -> bool: + """ + Check if a prefab is ready to be used to generate its instances. + :param file_path: A unique file path of the target prefab. + :return: Whether the target prefab is loaded or not. + """ return file_path in Prefab.existing_prefabs - """ - Check if a prefab exists in the directory for files of prefab tests. - :param file_name: A unique file name of the target prefab. - :return: Whether the target prefab exists or not. - """ + @classmethod def prefab_exists(cls, file_path: str) -> bool: + """ + Check if a prefab exists in the directory for files of prefab tests. + :param file_name: A unique file name of the target prefab. + :return: Whether the target prefab exists or not. + """ return path.exists(get_prefab_file_path(file_path)) - """ - Return a prefab which can be used immediately. - :param file_name: A unique file name of the target prefab. - :return: The prefab with given file name. - """ @classmethod def get_prefab(cls, file_name: str) -> Prefab: + """ + Return a prefab which can be used immediately. + :param file_name: A unique file name of the target prefab. + :return: The prefab with given file name. + """ assert file_name, "Received an empty file_name" if Prefab.is_prefab_loaded(file_name): return Prefab.existing_prefabs[file_name] @@ -151,15 +182,15 @@ class Prefab: Prefab.existing_prefabs[file_name] = Prefab(file_name) return new_prefab - """ - Create a prefab in memory and return it. The very first instance of this prefab will also be created. - :param entities: The entities that should form the new prefab (along with their descendants). - :param file_name: A unique file name of new prefab. - :param prefab_instance_name: A name for the very first instance generated while prefab creation. The default instance name is the same as file_name. - :return: Created Prefab object and the very first PrefabInstance object owned by the prefab. - """ @classmethod def create_prefab(cls, entities: list[EditorEntity], file_name: str, prefab_instance_name: str=None) -> tuple(Prefab, PrefabInstance): + """ + Create a prefab in memory and return it. The very first instance of this prefab will also be created. + :param entities: The entities that should form the new prefab (along with their descendants). + :param file_name: A unique file name of new prefab. + :param prefab_instance_name: A name for the very first instance generated while prefab creation. The default instance name is the same as file_name. + :return: Created Prefab object and the very first PrefabInstance object owned by the prefab. + """ assert not Prefab.is_prefab_loaded(file_name), f"Can't create Prefab '{file_name}' since the prefab already exists" new_prefab = Prefab(file_name) @@ -169,6 +200,9 @@ class Prefab: container_entity_id = create_prefab_result.GetValue() container_entity = EditorEntity(container_entity_id) + children_entity_ids = container_entity.get_children_ids() + + assert len(children_entity_ids) == len(entities), f"Entity count of created prefab instance does *not* match the count of given entities." if prefab_instance_name: container_entity.set_name(prefab_instance_name) @@ -180,12 +214,12 @@ class Prefab: Prefab.existing_prefabs[file_name] = new_prefab return new_prefab, new_prefab_instance - """ - Remove target prefab instances. - :param prefab_instances: Instances to be removed. - """ @classmethod def remove_prefabs(cls, prefab_instances: list[PrefabInstance]): + """ + Remove target prefab instances. + :param prefab_instances: Instances to be removed. + """ entity_ids_to_remove = [] entity_id_queue = [prefab_instance.container_entity for prefab_instance in prefab_instances] while entity_id_queue: @@ -212,15 +246,89 @@ class Prefab: instance_deleted_prefab = Prefab.get_prefab(instance.prefab_file_name) instance_deleted_prefab.instances.remove(instance) instance = PrefabInstance() - - """ - Instantiate an instance of this prefab. - :param parent_entity: The entity the prefab should be a child of in the transform hierarchy. - :param name: A name for newly instantiated prefab instance. The default instance name is the same as this prefab's file name. - :param prefab_position: The position in world space the prefab should be instantiated in. - :return: Instantiated PrefabInstance object owned by this prefab. - """ + + @classmethod + def duplicate_prefabs(cls, prefab_instances: list[PrefabInstance]): + """ + Duplicate target prefab instances. + :param prefab_instances: Instances to be duplicated. + :return: PrefabInstance objects of given prefab instances' duplicates. + """ + assert prefab_instances, "Input list of prefab instances should *not* be empty." + + common_parent = EditorEntity(prefab_instances[0].container_entity.get_parent_id()) + common_parent_children_ids_before_duplicate = set([child_id.ToString() for child_id in common_parent.get_children_ids()]) + + container_entity_ids = [prefab_instance.container_entity.id for prefab_instance in prefab_instances] + + duplicate_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'DuplicateEntitiesInInstance', container_entity_ids) + assert duplicate_prefab_result.IsSuccess(), f"Prefab operation 'DuplicateEntitiesInInstance' failed. Error: {duplicate_prefab_result.GetError()}" + + wait_for_propagation() + + duplicate_container_entity_ids = duplicate_prefab_result.GetValue() + common_parent_children_ids_after_duplicate = set([child_id.ToString() for child_id in common_parent.get_children_ids()]) + + assert set([container_entity_id.ToString() for container_entity_id in container_entity_ids]).issubset(common_parent_children_ids_after_duplicate), \ + "Provided prefab instances are *not* the children of their common parent anymore after duplication." + assert common_parent_children_ids_before_duplicate.issubset(common_parent_children_ids_after_duplicate), \ + "Some children of provided entities' common parent before duplication are *not* the children of the common parent anymore after duplication." + assert len(common_parent_children_ids_after_duplicate) == len(common_parent_children_ids_before_duplicate) + len(prefab_instances), \ + "The children count of the given prefab instances' common parent entity is *not* increased to the expected number." + assert EditorEntity(duplicate_container_entity_ids[0]).get_parent_id().ToString() == common_parent.id.ToString(), \ + "Provided prefab instances' parent should be the same as duplicates' parent." + + duplicate_instances = [] + for duplicate_container_entity_id in duplicate_container_entity_ids: + prefab_file_path = prefab.PrefabPublicRequestBus(bus.Broadcast, 'GetOwningInstancePrefabPath', duplicate_container_entity_id) + assert prefab_file_path, "Returned file path should *not* be empty." + + prefab_file_name = Path(prefab_file_path).stem + duplicate_instance_prefab = Prefab.get_prefab(prefab_file_name) + duplicate_instance = PrefabInstance(prefab_file_path, EditorEntity(duplicate_container_entity_id)) + duplicate_instance_prefab.instances.add(duplicate_instance) + duplicate_instances.append(duplicate_instance) + + return duplicate_instances + + @classmethod + def detach_prefab(cls, prefab_instance: PrefabInstance): + """ + Detach target prefab instance. + :param prefab_instances: Instance to be detached. + """ + parent = EditorEntity(prefab_instance.container_entity.get_parent_id()) + parent_children_ids_before_detach = set([child_id.ToString() for child_id in parent.get_children_ids()]) + + assert prefab_instance.has_editor_prefab_component(), f"Container entity should have EditorPrefabComponent before detachment." + + detach_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'DetachPrefab', prefab_instance.container_entity.id) + assert detach_prefab_result.IsSuccess(), f"Prefab operation 'DetachPrefab' failed. Error: {detach_prefab_result.GetError()}" + + assert not prefab_instance.has_editor_prefab_component(), f"Container entity should *not* have EditorPrefabComponent after detachment." + + parent_children_ids_after_detach = set([child_id.ToString() for child_id in parent.get_children_ids()]) + + assert prefab_instance.container_entity.id.ToString() in parent_children_ids_after_detach, \ + "Target prefab instance's container entity id should still exists after the detachment and before the propagation." + + assert len(parent_children_ids_after_detach) == len(parent_children_ids_before_detach), \ + "Parent entity should still keep the same amount of children entities." + + wait_for_propagation() + + instance_owner_prefab = Prefab.get_prefab(prefab_instance.prefab_file_name) + instance_owner_prefab.instances.remove(prefab_instance) + prefab_instance = PrefabInstance() + def instantiate(self, parent_entity: EditorEntity=None, name: str=None, prefab_position: Vector3=Vector3()) -> PrefabInstance: + """ + Instantiate an instance of this prefab. + :param parent_entity: The entity the prefab should be a child of in the transform hierarchy. + :param name: A name for newly instantiated prefab instance. The default instance name is the same as this prefab's file name. + :param prefab_position: The position in world space the prefab should be instantiated in. + :return: Instantiated PrefabInstance object owned by this prefab. + """ parent_entity_id = parent_entity.id if parent_entity is not None else EntityId() instantiate_prefab_result = prefab.PrefabPublicRequestBus( @@ -240,4 +348,6 @@ class Prefab: assert not new_prefab_instance in self.instances, "This prefab instance is already existed before this instantiation." self.instances.add(new_prefab_instance) + assert new_prefab_instance.is_at_position(prefab_position), "This prefab instance is *not* at expected position." + return new_prefab_instance diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py index 30a0055d5a..5337f0669c 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py @@ -47,3 +47,11 @@ class TestAutomation(TestAutomationBase): def test_PrefabBasicWorkflow_CreateAndReparentPrefab(self, request, workspace, editor, launcher_platform): from .tests import PrefabBasicWorkflow_CreateAndReparentPrefab as test_module self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False) + + def test_PrefabBasicWorkflow_CreateReparentAndDetachPrefab(self, request, workspace, editor, launcher_platform): + from .tests import PrefabBasicWorkflow_CreateReparentAndDetachPrefab as test_module + self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False) + + def test_PrefabBasicWorkflow_CreateAndDuplicatePrefab(self, request, workspace, editor, launcher_platform): + from .tests import PrefabBasicWorkflow_CreateAndDuplicatePrefab as test_module + self._run_prefab_test(request, workspace, editor, test_module) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDeletePrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDeletePrefab.py index 9ae4614d80..bbebd70e04 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDeletePrefab.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDeletePrefab.py @@ -16,16 +16,15 @@ def PrefabBasicWorkflow_CreateAndDeletePrefab(): prefab_test_utils.open_base_tests_level() - # Creates a new Entity at the root level - # Asserts if creation didn't succeed + # Creates a new entity at the root level car_entity = EditorEntity.create_editor_entity() car_prefab_entities = [car_entity] - # Asserts if prefab creation doesn't succeeds + # Creates a prefab from the new entity _, car = Prefab.create_prefab( car_prefab_entities, CAR_PREFAB_FILE_NAME) - # Asserts if prefab deletion fails + # Deletes the prefab instance Prefab.remove_prefabs([car]) if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDuplicatePrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDuplicatePrefab.py new file mode 100644 index 0000000000..2479ae549e --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDuplicatePrefab.py @@ -0,0 +1,32 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +def PrefabBasicWorkflow_CreateAndDuplicatePrefab(): + + CAR_PREFAB_FILE_NAME = 'car_prefab' + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.prefab_utils import Prefab + + import PrefabTestUtils as prefab_test_utils + + prefab_test_utils.open_base_tests_level() + + # Creates a new entity at the root level + car_entity = EditorEntity.create_editor_entity() + car_prefab_entities = [car_entity] + + # Creates a prefab from the new entity + _, car = Prefab.create_prefab( + car_prefab_entities, CAR_PREFAB_FILE_NAME) + + # Duplicates the prefab instance + Prefab.duplicate_prefabs([car]) + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(PrefabBasicWorkflow_CreateAndDuplicatePrefab) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndReparentPrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndReparentPrefab.py index f78bbf483d..1cbc591c29 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndReparentPrefab.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndReparentPrefab.py @@ -22,24 +22,23 @@ def PrefabBasicWorkflow_CreateAndReparentPrefab(): prefab_test_utils.open_base_tests_level() - # Creates a new Entity at the root level - # Asserts if creation didn't succeed + # Creates a new car entity at the root level car_entity = EditorEntity.create_editor_entity() car_prefab_entities = [car_entity] - # Checks for prefab creation passed or not + # Creates a prefab from the car entity _, car = Prefab.create_prefab( car_prefab_entities, CAR_PREFAB_FILE_NAME) - # Creates another new Entity at the root level + # Creates another new wheel entity at the root level wheel_entity = EditorEntity.create_editor_entity() wheel_prefab_entities = [wheel_entity] - # Checks for wheel prefab creation passed or not + # Creates another prefab from the wheel entity _, wheel = Prefab.create_prefab( wheel_prefab_entities, WHEEL_PREFAB_FILE_NAME) - # Checks for prefab reparenting passed or not + # Reparents the wheel prefab instance to the container entity of the car prefab instance await wheel.ui_reparent_prefab_instance(car.container_entity.id) run_test() diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreatePrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreatePrefab.py index 568a2c15b4..cae105a9a9 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreatePrefab.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreatePrefab.py @@ -17,12 +17,11 @@ def PrefabBasicWorkflow_CreatePrefab(): prefab_test_utils.open_base_tests_level() - # Creates a new Entity at the root level - # Asserts if creation didn't succeed + # Creates a new entity at the root level car_entity = EditorEntity.create_editor_entity() car_prefab_entities = [car_entity] - # Checks for prefab creation passed or not + # Creates a prefab from the new entity Prefab.create_prefab(car_prefab_entities, CAR_PREFAB_FILE_NAME) if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateReparentAndDetachPrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateReparentAndDetachPrefab.py new file mode 100644 index 0000000000..bdf77c4bf3 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateReparentAndDetachPrefab.py @@ -0,0 +1,51 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +def PrefabBasicWorkflow_CreateReparentAndDetachPrefab(): + + CAR_PREFAB_FILE_NAME = 'car_prefab' + WHEEL_PREFAB_FILE_NAME = 'wheel_prefab' + + import editor_python_test_tools.pyside_utils as pyside_utils + + @pyside_utils.wrap_async + async def run_test(): + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.prefab_utils import Prefab + + import PrefabTestUtils as prefab_test_utils + + prefab_test_utils.open_base_tests_level() + + # Creates a new car entity at the root level + car_entity = EditorEntity.create_editor_entity() + car_prefab_entities = [car_entity] + + # Creates a prefab from the car entity + _, car = Prefab.create_prefab( + car_prefab_entities, CAR_PREFAB_FILE_NAME) + + # Creates another new wheel entity at the root level + wheel_entity = EditorEntity.create_editor_entity() + wheel_prefab_entities = [wheel_entity] + + # Creates another prefab from the wheel entity + _, wheel = Prefab.create_prefab( + wheel_prefab_entities, WHEEL_PREFAB_FILE_NAME) + + # Reparents the wheel prefab instance to the container entity of the car prefab instance + await wheel.ui_reparent_prefab_instance(car.container_entity.id) + + # Detaches the wheel prefab instance + Prefab.detach_prefab(wheel) + + run_test() + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(PrefabBasicWorkflow_CreateReparentAndDetachPrefab) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_InstantiatePrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_InstantiatePrefab.py index 1b962d2ca7..a701802cd4 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_InstantiatePrefab.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_InstantiatePrefab.py @@ -19,9 +19,8 @@ def PrefabBasicWorkflow_InstantiatePrefab(): prefab_test_utils.open_base_tests_level() - # Checks for prefab instantiation passed or not + # Instantiates a new car prefab instance test_prefab = Prefab.get_prefab(EXISTING_TEST_PREFAB_FILE_NAME) - test_instance = test_prefab.instantiate( prefab_position=INSTANTIATED_TEST_PREFAB_POSITION) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabTestUtils.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabTestUtils.py index f82af23023..f865daf41a 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabTestUtils.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabTestUtils.py @@ -18,20 +18,6 @@ import azlmbr.components as components import azlmbr.entity as entity import azlmbr.legacy.general as general -def check_entity_at_position(entity_id, expected_entity_position): - entity_at_expected_position_result = ( - "entity is at expected position", - "entity is *not* at expected position") - - actual_entity_position = components.TransformBus(bus.Event, "GetWorldTranslation", entity_id) - is_at_position = actual_entity_position.IsClose(expected_entity_position) - Report.result(entity_at_expected_position_result, is_at_position) - - if not is_at_position: - Report.info(f"Entity '{entity_id.ToString()}'\'s expected position: {expected_entity_position.ToString()}, actual position: {actual_entity_position.ToString()}") - - return is_at_position - def check_entity_children_count(entity_id, expected_children_count): entity_children_count_matched_result = ( "Entity with a unique name found", @@ -47,19 +33,6 @@ def check_entity_children_count(entity_id, expected_children_count): return entity_children_count_matched -def get_children_ids_by_name(entity_id, entity_name): - entity = EditorEntity(entity_id) - children_entity_ids = entity.get_children_ids() - - result = [] - for child_entity_id in children_entity_ids: - child_entity = EditorEntity(child_entity_id) - child_entity_name = child_entity.get_name() - if child_entity_name == entity_name: - result.append(child_entity_id) - - return result - def open_base_tests_level(): helper.init_idle() helper.open_level("Prefab", "Base") diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/EditorPrefabComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/EditorPrefabComponent.cpp index d95a704e84..e9a5bee87a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/EditorPrefabComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/EditorPrefabComponent.cpp @@ -9,6 +9,8 @@ #include #include +#include +#include #include #include #include @@ -38,6 +40,13 @@ namespace AzToolsFramework AZ::Edit::SliceFlags::DontGatherReference); } } + + if (auto behaviorContext = azrtti_cast(context)) + { + behaviorContext->ConstantProperty( + "EditorPrefabComponentTypeId", BehaviorConstant(AZ::Uuid(EditorPrefabComponent::EditorPrefabComponentTypeId))) + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation); + } } void EditorPrefabComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/EditorPrefabComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/EditorPrefabComponent.h index 8873787bd3..aa15b63ac2 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/EditorPrefabComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/EditorPrefabComponent.h @@ -16,7 +16,9 @@ namespace AzToolsFramework class EditorPrefabComponent : public AzToolsFramework::Components::EditorComponentBase { public: - AZ_COMPONENT(EditorPrefabComponent, "{756E5F9C-3E08-4F8D-855C-A5AEEFB6FCDD}", EditorComponentBase); + static constexpr const char* const EditorPrefabComponentTypeId = "{756E5F9C-3E08-4F8D-855C-A5AEEFB6FCDD}"; + + AZ_COMPONENT(EditorPrefabComponent, EditorPrefabComponentTypeId, EditorComponentBase); static void Reflect(AZ::ReflectContext* context); static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index ee34b628bb..d976c91c3e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -974,7 +974,7 @@ namespace AzToolsFramework return DeleteFromInstance(entityIds, true); } - PrefabOperationResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds) + DuplicatePrefabResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds) { if (entityIds.empty()) { @@ -1021,6 +1021,7 @@ namespace AzToolsFramework ScopedUndoBatch undoBatch("Duplicate Entities"); + EntityIdList duplicatedEntityAndInstanceIds; { AZ_PROFILE_SCOPE(AzToolsFramework, "DuplicateEntitiesInInstance::UndoCaptureAndDuplicateEntities"); @@ -1033,7 +1034,7 @@ namespace AzToolsFramework if (!retrieveEntitiesAndInstancesOutcome.IsSuccess()) { - return AZStd::move(retrieveEntitiesAndInstancesOutcome); + return AZ::Failure(retrieveEntitiesAndInstancesOutcome.TakeError()); } // Take a snapshot of the instance DOM before we manipulate it @@ -1044,8 +1045,6 @@ namespace AzToolsFramework PrefabDom instanceDomAfter; instanceDomAfter.CopyFrom(instanceDomBefore, instanceDomAfter.GetAllocator()); - EntityIdList duplicatedEntityAndInstanceIds; - // Duplicate any nested entities and instances as requested AZStd::unordered_map newInstanceAliasToOldInstanceMap; AZStd::unordered_map duplicateEntityAliasMap; @@ -1114,7 +1113,7 @@ namespace AzToolsFramework ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, duplicatedEntityAndInstanceIds); } - return AZ::Success(); + return AZ::Success(AZStd::move(duplicatedEntityAndInstanceIds)); } PrefabOperationResult PrefabPublicHandler::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index a9dadc3336..4961be9d77 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -63,7 +63,7 @@ namespace AzToolsFramework PrefabOperationResult DeleteEntitiesInInstance(const EntityIdList& entityIds) override; PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override; - PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override; + DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override; PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h index 528d4f6d1b..ede857dd2b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h @@ -26,6 +26,7 @@ namespace AzToolsFramework { typedef AZ::Outcome CreatePrefabResult; typedef AZ::Outcome InstantiatePrefabResult; + typedef AZ::Outcome DuplicatePrefabResult; typedef AZ::Outcome PrefabOperationResult; typedef AZ::Outcome PrefabRequestResult; typedef AZ::Outcome PrefabEntityResult; @@ -160,14 +161,15 @@ namespace AzToolsFramework /** * Duplicates all entities in the owning instance. Bails if the entities don't all belong to the same instance. * @param entities The entities to duplicate. - * @return An outcome object; on failure, it comes with an error message detailing the cause of the error. + * @return An outcome object with a list of ids of target entities' duplicates if duplication succeeded; + * on failure, it comes with an error message detailing the cause of the error. */ - virtual PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0; + virtual DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0; /** * If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting * the container entity into a regular entity and putting it under the parent prefab, removing the link between this - * instance and the parent, removing links between this instance and it's nested instances, adding entities directly + * instance and the parent, removing links between this instance and its nested instances, and adding entities directly * owned by this instance under the parent instance. * Bails if the entity is not a container entity or belongs to the level prefab instance. * @param containerEntityId The container entity id of the instance to detach. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestBus.h index 7b23fffb7f..fd4b8a5f17 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestBus.h @@ -25,6 +25,7 @@ namespace AzToolsFramework { using CreatePrefabResult = AZ::Outcome; using InstantiatePrefabResult = AZ::Outcome; + using DuplicatePrefabResult = AZ::Outcome; using PrefabOperationResult = AZ::Outcome; /** @@ -69,6 +70,29 @@ namespace AzToolsFramework * Return an outcome object; on failure, it comes with an error message detailing the cause of the error. */ virtual PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) = 0; + + /** + * If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting + * the container entity into a regular entity and putting it under the parent prefab, removing the link between this + * instance and the parent, removing links between this instance and its nested instances, and adding entities directly + * owned by this instance under the parent instance. + * Bails if the entity is not a container entity or belongs to the level prefab instance. + * Return an outcome object; on failure, it comes with an error message detailing the cause of the error. + */ + virtual PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) = 0; + + /** + * Duplicates all entities in the owning instance. Bails if the entities don't all belong to the same instance. + * Return an outcome object with a list of ids of given entities' duplicates if duplication succeeded; + * on failure, it comes with an error message detailing the cause of the error. + */ + virtual DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0; + + /** + * Get the file path to the prefab file for the prefab instance owning the entity provided. + * Returns the path to the prefab, or an empty path if the entity is owned by the level. + */ + virtual AZStd::string GetOwningInstancePrefabPath(AZ::EntityId entityId) const = 0; }; using PrefabPublicRequestBus = AZ::EBus; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestHandler.cpp index 0aaf81c4c9..3b69dcdfe4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestHandler.cpp @@ -28,6 +28,9 @@ namespace AzToolsFramework ->Event("CreatePrefabInMemory", &PrefabPublicRequests::CreatePrefabInMemory) ->Event("InstantiatePrefab", &PrefabPublicRequests::InstantiatePrefab) ->Event("DeleteEntitiesAndAllDescendantsInInstance", &PrefabPublicRequests::DeleteEntitiesAndAllDescendantsInInstance) + ->Event("DetachPrefab", &PrefabPublicRequests::DetachPrefab) + ->Event("DuplicateEntitiesInInstance", &PrefabPublicRequests::DuplicateEntitiesInInstance) + ->Event("GetOwningInstancePrefabPath", &PrefabPublicRequests::GetOwningInstancePrefabPath) ; } } @@ -62,5 +65,19 @@ namespace AzToolsFramework return m_prefabPublicInterface->DeleteEntitiesAndAllDescendantsInInstance(entityIds); } + PrefabOperationResult PrefabPublicRequestHandler::DetachPrefab(const AZ::EntityId& containerEntityId) + { + return m_prefabPublicInterface->DetachPrefab(containerEntityId); + } + + DuplicatePrefabResult PrefabPublicRequestHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds) + { + return m_prefabPublicInterface->DuplicateEntitiesInInstance(entityIds); + } + + AZStd::string PrefabPublicRequestHandler::GetOwningInstancePrefabPath(AZ::EntityId entityId) const + { + return m_prefabPublicInterface->GetOwningInstancePrefabPath(entityId).Native(); + } } // namespace Prefab } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestHandler.h index ae0ed2a5d1..b24ea7ec2a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestHandler.h @@ -34,6 +34,9 @@ namespace AzToolsFramework CreatePrefabResult CreatePrefabInMemory(const EntityIdList& entityIds, AZStd::string_view filePath) override; InstantiatePrefabResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) override; PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override; + PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override; + DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override; + AZStd::string GetOwningInstancePrefabPath(AZ::EntityId entityId) const override; private: PrefabPublicInterface* m_prefabPublicInterface = nullptr; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp index 6c96209d56..1c2230fa83 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp @@ -23,7 +23,7 @@ namespace AzToolsFramework } //PrefabInstanceUndo - PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName, const bool useImmediatePropagation) + PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation) : PrefabUndoBase(undoOperationName) { m_useImmediatePropagation = useImmediatePropagation; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.h index 0946a36951..bc0b86a8c6 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.h @@ -45,7 +45,7 @@ namespace AzToolsFramework : public PrefabUndoBase { public: - explicit PrefabUndoInstance(const AZStd::string& undoOperationName, const bool useImmediatePropagation = true); + explicit PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation = true); void Capture( const PrefabDom& initialState, From 244878483a7fa04f6d7e10b06339f4e9669a6a7b Mon Sep 17 00:00:00 2001 From: Steve Pham <82231385+spham-amzn@users.noreply.github.com> Date: Thu, 21 Oct 2021 12:40:10 -0700 Subject: [PATCH 26/27] Update open file limit on linux for applications (#4878) * Programmatically update the ulimit for open files if the current limit is not enough Signed-off-by: Steve Pham --- .../Application/Application_Linux.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux.cpp b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux.cpp index 158240210e..85669e093c 100644 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux.cpp +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux.cpp @@ -7,17 +7,35 @@ */ #include +#include #if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB #include #endif +constexpr rlim_t g_minimumOpenFileHandles = 65536L; + //////////////////////////////////////////////////////////////////////////////////////////////////// namespace AzFramework { //////////////////////////////////////////////////////////////////////////////////////////////// Application::Implementation* Application::Implementation::Create() { + // The default open file limit for processes may not be enough for O3DE applications. + // We will need to increase to the recommended value if the current open file limit + // is not sufficient. + rlimit currentLimit; + int get_limit_result = getrlimit(RLIMIT_NOFILE, ¤tLimit); + AZ_Warning("Application", get_limit_result == 0, "Unable to read current ulimit open file limits"); + if ((get_limit_result == 0) && (currentLimit.rlim_cur < g_minimumOpenFileHandles || currentLimit.rlim_max < g_minimumOpenFileHandles)) + { + rlimit newLimit; + newLimit.rlim_cur = g_minimumOpenFileHandles; // Soft Limit + newLimit.rlim_max = g_minimumOpenFileHandles; // Hard Limit + [[maybe_unused]] int set_limit_result = setrlimit(RLIMIT_NOFILE, &newLimit); + AZ_Assert(set_limit_result == 0, "Unable to update open file limits"); + } + #if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB return aznew XcbApplication(); #elif PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND From 50f66bde69c00e507df4861e0eb8aa934b0f8923 Mon Sep 17 00:00:00 2001 From: amzn-phist <52085794+amzn-phist@users.noreply.github.com> Date: Thu, 21 Oct 2021 15:38:56 -0500 Subject: [PATCH 27/27] Remove the use of a .Stub version of the Wwise Gem (#4879) * Remove the definition of a 'Stub' Wwise Gem This removes the conditional compilation of a '.Stub' version of the Wwise Gem. Now, all we do is not define any of the AudioEngineWwise targets in CMake whenever Wwise is not available (SDK not found or platform unsupported). Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com> * Remove the remaining stub files, no longer needed This removes the files.cmake and stub module cpp files that made up the .Stub version of this Gem. These are no longer needed. Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com> --- Gems/AudioEngineWwise/Code/CMakeLists.txt | 27 +++++-------------- .../Code/Platform/Android/PAL_android.cmake | 2 +- .../Code/Platform/Linux/PAL_linux.cmake | 2 +- .../Code/Platform/Mac/PAL_mac.cmake | 2 +- .../Code/Platform/Windows/PAL_windows.cmake | 2 +- .../Code/Platform/iOS/PAL_ios.cmake | 2 +- .../Source/AudioEngineWwiseModule_Stub.cpp | 11 -------- .../Code/audioenginewwise_stub_files.cmake | 11 -------- 8 files changed, 12 insertions(+), 47 deletions(-) delete mode 100644 Gems/AudioEngineWwise/Code/Source/AudioEngineWwiseModule_Stub.cpp delete mode 100644 Gems/AudioEngineWwise/Code/audioenginewwise_stub_files.cmake diff --git a/Gems/AudioEngineWwise/Code/CMakeLists.txt b/Gems/AudioEngineWwise/Code/CMakeLists.txt index 98a31fb91a..33c80bc31c 100644 --- a/Gems/AudioEngineWwise/Code/CMakeLists.txt +++ b/Gems/AudioEngineWwise/Code/CMakeLists.txt @@ -18,31 +18,18 @@ set(AUDIOENGINEWWISE_COMPILEDEFINITIONS find_package(Wwise MODULE) ################################################################################ -# Server / Unsupported +# Servers +# (and situations where Wwise SDK is not found or otherwise unavailable) ################################################################################ -if (PAL_TRAIT_BUILD_SERVER_SUPPORTED OR PAL_TRAIT_AUDIO_ENGINE_WWISE_USE_STUB OR NOT Wwise_FOUND) - # Stub gem for server and unsupported platforms. Audio Engine Wwise is client only - ly_add_target( - NAME AudioEngineWwise.Stub ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE} - NAMESPACE Gem - FILES_CMAKE - audioenginewwise_stub_files.cmake - BUILD_DEPENDENCIES - PRIVATE - AZ::AzCore - ) -endif() - -if (PAL_TRAIT_AUDIO_ENGINE_WWISE_USE_STUB OR NOT Wwise_FOUND) - # setup aliases so stubs will be used if something references AudioEngineWwise(.Editor) - add_library(Gem::AudioEngineWwise ALIAS AudioEngineWwise.Stub) - add_library(Gem::AudioEngineWwise.Editor ALIAS AudioEngineWwise.Stub) +if(NOT PAL_TRAIT_AUDIO_ENGINE_WWISE_SUPPORTED OR NOT Wwise_FOUND) + # Don't create any Gem targets and aliases. Nothing should depend on this + # Gem directly, because if it doesn't define targets it will cause an error. return() endif() ################################################################################ -# Runtime / Game +# Clients ################################################################################ ly_add_target( NAME AudioEngineWwise.Static STATIC @@ -181,7 +168,7 @@ if (PAL_TRAIT_BUILD_TESTS_SUPPORTED) endif() ################################################################################ -# Tools / Editor +# Tools / Builders ################################################################################ if (PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( diff --git a/Gems/AudioEngineWwise/Code/Platform/Android/PAL_android.cmake b/Gems/AudioEngineWwise/Code/Platform/Android/PAL_android.cmake index e3729baea7..c706c464d3 100644 --- a/Gems/AudioEngineWwise/Code/Platform/Android/PAL_android.cmake +++ b/Gems/AudioEngineWwise/Code/Platform/Android/PAL_android.cmake @@ -6,4 +6,4 @@ # # -set(PAL_TRAIT_AUDIO_ENGINE_WWISE_USE_STUB FALSE) +set(PAL_TRAIT_AUDIO_ENGINE_WWISE_SUPPORTED TRUE) diff --git a/Gems/AudioEngineWwise/Code/Platform/Linux/PAL_linux.cmake b/Gems/AudioEngineWwise/Code/Platform/Linux/PAL_linux.cmake index e3729baea7..c706c464d3 100644 --- a/Gems/AudioEngineWwise/Code/Platform/Linux/PAL_linux.cmake +++ b/Gems/AudioEngineWwise/Code/Platform/Linux/PAL_linux.cmake @@ -6,4 +6,4 @@ # # -set(PAL_TRAIT_AUDIO_ENGINE_WWISE_USE_STUB FALSE) +set(PAL_TRAIT_AUDIO_ENGINE_WWISE_SUPPORTED TRUE) diff --git a/Gems/AudioEngineWwise/Code/Platform/Mac/PAL_mac.cmake b/Gems/AudioEngineWwise/Code/Platform/Mac/PAL_mac.cmake index e3729baea7..c706c464d3 100644 --- a/Gems/AudioEngineWwise/Code/Platform/Mac/PAL_mac.cmake +++ b/Gems/AudioEngineWwise/Code/Platform/Mac/PAL_mac.cmake @@ -6,4 +6,4 @@ # # -set(PAL_TRAIT_AUDIO_ENGINE_WWISE_USE_STUB FALSE) +set(PAL_TRAIT_AUDIO_ENGINE_WWISE_SUPPORTED TRUE) diff --git a/Gems/AudioEngineWwise/Code/Platform/Windows/PAL_windows.cmake b/Gems/AudioEngineWwise/Code/Platform/Windows/PAL_windows.cmake index e3729baea7..c706c464d3 100644 --- a/Gems/AudioEngineWwise/Code/Platform/Windows/PAL_windows.cmake +++ b/Gems/AudioEngineWwise/Code/Platform/Windows/PAL_windows.cmake @@ -6,4 +6,4 @@ # # -set(PAL_TRAIT_AUDIO_ENGINE_WWISE_USE_STUB FALSE) +set(PAL_TRAIT_AUDIO_ENGINE_WWISE_SUPPORTED TRUE) diff --git a/Gems/AudioEngineWwise/Code/Platform/iOS/PAL_ios.cmake b/Gems/AudioEngineWwise/Code/Platform/iOS/PAL_ios.cmake index e3729baea7..c706c464d3 100644 --- a/Gems/AudioEngineWwise/Code/Platform/iOS/PAL_ios.cmake +++ b/Gems/AudioEngineWwise/Code/Platform/iOS/PAL_ios.cmake @@ -6,4 +6,4 @@ # # -set(PAL_TRAIT_AUDIO_ENGINE_WWISE_USE_STUB FALSE) +set(PAL_TRAIT_AUDIO_ENGINE_WWISE_SUPPORTED TRUE) diff --git a/Gems/AudioEngineWwise/Code/Source/AudioEngineWwiseModule_Stub.cpp b/Gems/AudioEngineWwise/Code/Source/AudioEngineWwiseModule_Stub.cpp deleted file mode 100644 index 4344eb6072..0000000000 --- a/Gems/AudioEngineWwise/Code/Source/AudioEngineWwiseModule_Stub.cpp +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#include - -AZ_DECLARE_MODULE_CLASS(Gem_AudioEngineWwise, AZ::Module) diff --git a/Gems/AudioEngineWwise/Code/audioenginewwise_stub_files.cmake b/Gems/AudioEngineWwise/Code/audioenginewwise_stub_files.cmake deleted file mode 100644 index 5597c28d04..0000000000 --- a/Gems/AudioEngineWwise/Code/audioenginewwise_stub_files.cmake +++ /dev/null @@ -1,11 +0,0 @@ -# -# Copyright (c) Contributors to the Open 3D Engine Project. -# For complete copyright and license terms please see the LICENSE at the root of this distribution. -# -# SPDX-License-Identifier: Apache-2.0 OR MIT -# -# - -set(FILES - Source/AudioEngineWwiseModule_Stub.cpp -)