diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityModel.cpp index 8d96cc42fe..ce1bf84db0 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityModel.cpp @@ -381,12 +381,22 @@ namespace AzToolsFramework return; } - //orphan any children that remain attached to the entity - auto children = entityInfo.GetChildren(); - for (auto childId : children) - { - ReparentChild(childId, AZ::EntityId(), entityId); - m_entityOrphanTable[entityId].insert(childId); + bool isPrefabSystemEnabled = false; + AzFramework::ApplicationRequests::Bus::BroadcastResult( + isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); + + // For slices, orphan any children that remain attached to the entity + // For prefabs, this is an unneeded operation because the prefab system handles the orphans + // and the extra reparenting operation can be problematic for consumers subscribed to entity + // events, such as the entity outliner. + if (!isPrefabSystemEnabled) + { + auto children = entityInfo.GetChildren(); + for (auto childId : children) + { + ReparentChild(childId, AZ::EntityId(), entityId); + m_entityOrphanTable[entityId].insert(childId); + } } m_savedOrderInfo[entityId] = AZStd::make_pair(entityInfo.GetParent(), entityInfo.GetIndexForSorting()); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h index feb2fc12bf..3bd6e656ed 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h @@ -56,5 +56,7 @@ namespace AzToolsFramework virtual void StopPlayInEditor() = 0; virtual void CreateNewLevelPrefab(AZStd::string_view filename, const AZStd::string& templateFilename) = 0; + + virtual bool IsRootPrefabAssigned() const = 0; }; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp index 0f2b896b40..e5daf2674d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp @@ -79,6 +79,8 @@ namespace AzToolsFramework void PrefabEditorEntityOwnershipService::Reset() { + m_isRootPrefabAssigned = false; + if (m_rootInstance) { AzToolsFramework::ToolsApplicationRequestBus::Broadcast( @@ -203,6 +205,7 @@ namespace AzToolsFramework m_rootInstance->SetTemplateSourcePath(m_loaderInterface->GenerateRelativePath(filename)); m_rootInstance->SetContainerEntityName("Level"); m_prefabSystemComponent->PropagateTemplateChanges(templateId); + m_isRootPrefabAssigned = true; return true; } @@ -302,6 +305,12 @@ namespace AzToolsFramework } m_prefabSystemComponent->PropagateTemplateChanges(templateId); + m_isRootPrefabAssigned = true; + } + + bool PrefabEditorEntityOwnershipService::IsRootPrefabAssigned() const + { + return m_isRootPrefabAssigned; } Prefab::InstanceOptionalReference PrefabEditorEntityOwnershipService::CreatePrefab( diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h index a98fce8059..a172e9550c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h @@ -167,6 +167,7 @@ namespace AzToolsFramework void StopPlayInEditor() override; void CreateNewLevelPrefab(AZStd::string_view filename, const AZStd::string& templateFilename) override; + bool IsRootPrefabAssigned() const override; protected: @@ -215,5 +216,6 @@ namespace AzToolsFramework Prefab::PrefabLoaderInterface* m_loaderInterface; AzFramework::EntityContextId m_entityContextId; AZ::SerializeContext m_serializeContext; + bool m_isRootPrefabAssigned = false; }; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 63f042be85..41538d54e8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -329,6 +329,11 @@ namespace AzToolsFramework return AZ::Failure(AZStd::string("Could not instantiate prefab - internal error " "(PrefabEditorEntityOwnershipInterface unavailable).")); } + if (!prefabEditorEntityOwnershipInterface->IsRootPrefabAssigned()) + { + return AZ::Failure(AZStd::string("Could not instantiate prefab - no root prefab assigned. " + "Currently, prefabs can only be instantiated inside a level")); + } InstanceOptionalReference instanceToParentUnder; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp index 90f8e1d121..2872c1bce3 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp @@ -82,6 +82,8 @@ namespace AzToolsFramework , m_entityExpansionState() , m_entityFilteredState() { + m_focusModeInterface = AZ::Interface::Get(); + AZ_Assert(m_focusModeInterface != nullptr, "EntityOutlinerListModel requires a FocusModeInterface instance on construction."); } EntityOutlinerListModel::~EntityOutlinerListModel() @@ -106,11 +108,6 @@ namespace AzToolsFramework m_editorEntityUiInterface = AZ::Interface::Get(); AZ_Assert(m_editorEntityUiInterface != nullptr, "EntityOutlinerListModel requires a EditorEntityUiInterface instance on Initialize."); - - m_focusModeInterface = AZ::Interface::Get(); - AZ_Assert( - m_focusModeInterface != nullptr, - "EntityOutlinerListModel requires a FocusModeInterface instance on Initialize."); } int EntityOutlinerListModel::rowCount(const QModelIndex& parent) const @@ -1347,7 +1344,10 @@ namespace AzToolsFramework //add/remove operations trigger selection change signals which assert and break undo/redo operations in progress in inspector etc. //so disallow selection updates until change is complete emit EnableSelectionUpdates(false); - beginResetModel(); + + auto parentIndex = GetIndexFromEntity(parentId); + auto childIndex = GetIndexFromEntity(childId); + beginRemoveRows(parentIndex, childIndex.row(), childIndex.row()); } void EntityOutlinerListModel::OnEntityInfoUpdatedRemoveChildEnd(AZ::EntityId parentId, AZ::EntityId childId) @@ -1355,7 +1355,7 @@ namespace AzToolsFramework (void)childId; AZ_PROFILE_FUNCTION(AzToolsFramework); - endResetModel(); + endRemoveRows(); //must refresh partial lock/visibility of parents m_isFilterDirty = true; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp index 4db235d073..689e5b5dc4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp @@ -232,6 +232,9 @@ namespace AzToolsFramework m_gui->m_objectTree->header()->setSortIndicatorShown(false); m_gui->m_objectTree->header()->setStretchLastSection(false); + // Always expand root entity (level entity) - needed if the widget is re-created while a level is already open. + m_gui->m_objectTree->expand(m_proxyModel->index(0, 0)); + // resize the icon columns so that the Visibility and Lock toggle icon columns stay right-justified m_gui->m_objectTree->header()->setStretchLastSection(false); m_gui->m_objectTree->header()->setMinimumSectionSize(0); diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp index 81d4047db5..b313c05230 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp @@ -358,8 +358,9 @@ namespace O3DE::ProjectManager painter.drawPixmap(backgroundRect, m_background); // Draw a semi-transparent overlay to darken down the colors. - painter.setCompositionMode (QPainter::CompositionMode_DestinationIn); - const float overlayTransparency = 0.7f; + // Use SourceOver, DestinationIn will make background transparent on Mac + painter.setCompositionMode (QPainter::CompositionMode_SourceOver); + const float overlayTransparency = 0.3f; painter.fillRect(backgroundRect, QColor(0, 0, 0, static_cast(255.0f * overlayTransparency))); } diff --git a/Gems/Atom/Feature/Common/Code/Source/RenderCommon.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/RenderCommon.h similarity index 100% rename from Gems/Atom/Feature/Common/Code/Source/RenderCommon.h rename to Gems/Atom/Feature/Common/Code/Include/Atom/Feature/RenderCommon.h diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/CoreLightsSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/CoreLightsSystemComponent.cpp index a2c6b8a67e..947028610c 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/CoreLightsSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/CoreLightsSystemComponent.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -33,8 +34,6 @@ #include #include -#include - namespace AZ { namespace Render diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index 3fcda71998..cced38c3a7 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -6,10 +6,9 @@ * */ -#include - #include #include +#include #include #include #include diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h index 5b56b70ec8..bee304c5b9 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h @@ -9,7 +9,7 @@ #pragma once #include -#include +#include #include #include #include diff --git a/Gems/Atom/Feature/Common/Code/atom_feature_common_editor_files.cmake b/Gems/Atom/Feature/Common/Code/atom_feature_common_editor_files.cmake index 59a29f04b6..8262500ca1 100644 --- a/Gems/Atom/Feature/Common/Code/atom_feature_common_editor_files.cmake +++ b/Gems/Atom/Feature/Common/Code/atom_feature_common_editor_files.cmake @@ -7,6 +7,7 @@ # set(FILES + Include/Atom/Feature/RenderCommon.h Include/Atom/Feature/Utils/EditorRenderComponentAdapter.h Include/Atom/Feature/Utils/EditorRenderComponentAdapter.inl Include/Atom/Feature/Utils/EditorLightingPreset.h @@ -16,7 +17,6 @@ set(FILES Source/EditorCommonSystemComponent.cpp Source/EditorCommonSystemComponent.h Source/CommonModule.cpp - Source/RenderCommon.h Source/Material/ConvertEmissiveUnitFunctorSourceData.cpp Source/Material/ConvertEmissiveUnitFunctorSourceData.h Source/Material/MaterialConverterSystemComponent.cpp diff --git a/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake b/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake index 92f96a591b..9372d6594a 100644 --- a/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake +++ b/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake @@ -8,6 +8,7 @@ set(FILES 3rdParty/ACES/ACES/Aces.h + Include/Atom/Feature/RenderCommon.h Include/Atom/Feature/ACES/AcesDisplayMapperFeatureProcessor.h Include/Atom/Feature/Automation/AtomAutomationBus.h Include/Atom/Feature/AuxGeom/AuxGeomFeatureProcessor.h @@ -52,7 +53,6 @@ set(FILES Source/FrameCaptureSystemComponent.h Source/ProfilingCaptureSystemComponent.cpp Source/ProfilingCaptureSystemComponent.h - Source/RenderCommon.h 3rdParty/ACES/ACES/Aces.cpp Source/ACES/AcesDisplayMapperFeatureProcessor.cpp Source/AuxGeom/AuxGeomBase.h diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp index 5eda93ca59..5ef2dbb2c5 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp @@ -31,20 +31,26 @@ namespace AtomToolsFramework void InspectorWidget::AddHeading(QWidget* headingWidget) { - headingWidget->setParent(m_ui->m_headingSection); - m_ui->m_headingSectionLayout->addWidget(headingWidget); + headingWidget->setParent(this); + m_ui->m_headingContentsLayout->addWidget(headingWidget); } void InspectorWidget::ClearHeading() { - qDeleteAll(m_ui->m_headingSection->findChildren(QString(), Qt::FindDirectChildrenOnly)); - qDeleteAll(m_ui->m_headingSectionLayout->children()); + while (QLayoutItem* child = m_ui->m_headingContentsLayout->takeAt(0)) + { + delete child->widget(); + delete child; + } } void InspectorWidget::Reset() { - qDeleteAll(m_ui->m_groupContents->findChildren(QString(), Qt::FindDirectChildrenOnly)); - qDeleteAll(m_ui->m_groupContentsLayout->children()); + while (QLayoutItem* child = m_ui->m_groupContentsLayout->takeAt(0)) + { + delete child->widget(); + delete child; + } m_groups.clear(); } diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.ui b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.ui index 13f9c9e41c..c197ee172e 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.ui +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.ui @@ -30,104 +30,85 @@ 0 - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::ScrollBarAsNeeded - - 0 + + true - - 0 - - - 0 - - - 0 - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::ScrollBarAsNeeded + + + + 0 + 0 + 679 + 767 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 - - true + + 0 - - - - 0 - 0 - 683 - 763 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - + + + + + diff --git a/Gems/Terrain/Assets/Materials/Terrain/DefaultPbrTerrain.material b/Gems/Terrain/Assets/Materials/Terrain/DefaultPbrTerrain.material new file mode 100644 index 0000000000..095fbba21a --- /dev/null +++ b/Gems/Terrain/Assets/Materials/Terrain/DefaultPbrTerrain.material @@ -0,0 +1,11 @@ +{ + "description": "", + "materialType": "PbrTerrain.materialtype", + "parentMaterial": "", + "propertyLayoutVersion": 1, + "properties": { + "settings": { + "baseColor": [ 0.78, 0.59, 0.48 ] + } + } +} diff --git a/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype b/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype new file mode 100644 index 0000000000..bf6cbbb850 --- /dev/null +++ b/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype @@ -0,0 +1,140 @@ +{ + "description": "A material for rendering terrain with a physically-based rendering (PBR) material shading model.", + "propertyLayout": { + "version": 1, + "groups": [ + { + "id": "settings", + "displayName": "Settings" + } + ], + "properties": { + "general": [ + { + "id": "applySpecularAA", + "displayName": "Apply Specular AA", + "description": "Whether to apply specular anti-aliasing in the shader.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "id": "o_applySpecularAA" + } + }, + { + "id": "enableShadows", + "displayName": "Enable Shadows", + "description": "Whether to use the shadow maps.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "id": "o_enableShadows" + } + }, + { + "id": "enableDirectionalLights", + "displayName": "Enable Directional Lights", + "description": "Whether to use directional lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "id": "o_enableDirectionalLights" + } + }, + { + "id": "enablePunctualLights", + "displayName": "Enable Punctual Lights", + "description": "Whether to use punctual lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "id": "o_enablePunctualLights" + } + }, + { + "id": "enableAreaLights", + "displayName": "Enable Area Lights", + "description": "Whether to use area lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "id": "o_enableAreaLights" + } + }, + { + "id": "enableIBL", + "displayName": "Enable IBL", + "description": "Whether to use Image Based Lighting (IBL).", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "id": "o_enableIBL" + } + }, + { + "id": "forwardPassIBLSpecular", + "displayName": "Forward Pass IBL Specular", + "description": "Whether to apply IBL specular in the forward pass.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "id": "o_materialUseForwardPassIBLSpecular" + } + } + ], + "settings": [ + { + "id": "heightmapImage", + "displayName": "Heightmap Image", + "description": "Heightmap of the terrain, controlled by the runtime.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "id": "m_heightmapImage" + } + }, + { + "id": "baseColor", + "displayName": "Base Color", + "type": "Color", + "defaultValue": [ 0.18, 0.18, 0.18 ], + "connection": { + "type": "ShaderInput", + "id": "m_baseColor" + } + }, + { + "id": "roughness", + "displayName": "Roughness", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "id": "m_roughness" + } + } + ] + } + }, + "shaders": [ + { + "file": "../../Shaders/Terrain/TerrainPBR_ForwardPass.shader" + }, + { + "file": "../../Shaders/Terrain/Terrain_Shadowmap.shader" + }, + { + "file": "../../Shaders/Terrain/Terrain_DepthPass.shader" + } + ], + "functors": [ + ] +} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/Terrain.azsl b/Gems/Terrain/Assets/Shaders/Terrain/Terrain.azsl deleted file mode 100644 index 50d11ca610..0000000000 --- a/Gems/Terrain/Assets/Shaders/Terrain/Terrain.azsl +++ /dev/null @@ -1,95 +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 -#include -#include -#include "TerrainCommon.azsli" - -struct VertexOutput -{ - linear centroid float4 m_position : SV_Position; - float3 m_normal : NORMAL; - float3 m_tangent : TANGENT; - float3 m_bitangent : BITANGENT; - float m_height : UV; -}; - -struct ForwardPassOutput -{ - float4 m_diffuseColor : SV_Target0; //!< RGB = Diffuse Lighting, A = Blend Alpha (for blended surfaces) OR A = special encoding of surfaceScatteringFactor, m_subsurfaceScatteringQuality, o_enableSubsurfaceScattering - float4 m_specularColor : SV_Target1; //!< RGB = Specular Lighting, A = Unused - float4 m_albedo : SV_Target2; //!< RGB = Surface albedo pre-multiplied by other factors that will be multiplied later by diffuse GI, A = specularOcclusion - float4 m_specularF0 : SV_Target3; //!< RGB = Specular F0, A = roughness - float4 m_normal : SV_Target4; //!< RGB10 = EncodeNormalSignedOctahedron(worldNormal), A2 = multiScatterCompensationEnabled -}; - -struct PixelOutput -{ - float4 m_color : SV_Target0; -}; - -VertexOutput MainVS(in VertexInput input) -{ - VertexOutput output; - ObjectSrg::TerrainData terrainData = ObjectSrg::m_terrainData; - - float2 uv = input.m_uv; - float2 origUv = lerp(terrainData.m_uvMin, terrainData.m_uvMax, uv); - output.m_position = GetTerrainProjectedPosition(terrainData, input.m_position, origUv); - - // Calculate normal - float up = GetHeight(origUv + terrainData.m_uvStep * float2( 0.0f, -1.0f)); - float right = GetHeight(origUv + terrainData.m_uvStep * float2( 1.0f, 0.0f)); - float down = GetHeight(origUv + terrainData.m_uvStep * float2( 0.0f, 1.0f)); - float left = GetHeight(origUv + terrainData.m_uvStep * float2(-1.0f, 0.0f)); - - output.m_bitangent = normalize(float3(0.0, terrainData.m_sampleSpacing * 2.0f, down - up)); - output.m_tangent = normalize(float3(terrainData.m_sampleSpacing * 2.0f, 0.0, right - left)); - output.m_normal = cross(output.m_tangent, output.m_bitangent); - - output.m_height = GetHeight(origUv); - return output; -} - -ForwardPassOutput MainPS(in VertexOutput input) -{ - ForwardPassOutput output; - - float3 lightDirection = normalize(float3(-1.0, 1.0, -1.0)); - float3 lightIntensity = float3(1.0, 1.0, 1.0); - if (SceneSrg::m_directionalLightCount > 0) - { - lightDirection = SceneSrg::m_directionalLights[0].m_direction; - lightIntensity = SceneSrg::m_directionalLights[0].m_rgbIntensityLux; - } - - // Fake light intensity ranges from 1.0 for normals directly facing the light to zero for those - // directly facing away. - const float minLight = 0.01; - const float midLight = 0.1; - float lightDot = dot(normalize(input.m_normal), -lightDirection); - lightIntensity *= lightDot > 0.0 ? - lightDot * (1.0 - midLight) + midLight : // surface facing light - (lightDot + 1.0) * (midLight - minLight) + minLight; // surface facing away - - output.m_diffuseColor.rgb = 0.5 * lightIntensity; - output.m_diffuseColor.a = 0.0f; - - output.m_specularColor.rgb = 0.0; - - output.m_albedo.rgb = 0.25 + input.m_height * 0.5; - output.m_albedo.a = 0.0; - - output.m_specularF0.rgb = 0.04; - output.m_specularF0.a = 1.0; - - output.m_normal.rgb = input.m_normal; - output.m_normal.a = 0.0; - - return output; -} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/Terrain.shader b/Gems/Terrain/Assets/Shaders/Terrain/Terrain.shader deleted file mode 100644 index e844a54a21..0000000000 --- a/Gems/Terrain/Assets/Shaders/Terrain/Terrain.shader +++ /dev/null @@ -1,33 +0,0 @@ -{ - "Source" : "./Terrain.azsl", - - "DepthStencilState" : - { - "Depth" : - { - "Enable" : true, - "CompareFunc" : "GreaterEqual" - } - }, - - "DrawList" : "forward", - - "ProgramSettings": - { - "EntryPoints": - [ - { - "name": "MainVS", - "type": "Vertex" - }, - { - "name": "MainPS", - "type": "Fragment" - } - ] - }, - - "BlendState" : { - "Enable" : false - } -} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli b/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli index 18b85bbd43..9df7b35ce8 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli @@ -9,18 +9,6 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject { - Texture2D m_heightmapImage; - - Sampler PointSampler - { - MinFilter = Point; - MagFilter = Point; - MipFilter = Point; - AddressU = Clamp; - AddressV = Clamp; - AddressW = Clamp; - }; - row_major float3x4 m_modelToWorld; struct TerrainData @@ -33,14 +21,131 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject }; TerrainData m_terrainData; + + // The below shouldn't be in this SRG but needs to be for now because the lighting functions depend on them. + + //! Reflection Probe (smallest probe volume that overlaps the object position) + struct ReflectionProbeData + { + row_major float3x4 m_modelToWorld; + row_major float3x4 m_modelToWorldInverse; // does not include extents + float3 m_outerObbHalfLengths; + float3 m_innerObbHalfLengths; + float m_padding; + bool m_useReflectionProbe; + bool m_useParallaxCorrection; + }; + + ReflectionProbeData m_reflectionProbeData; + TextureCube m_reflectionProbeCubeMap; + + float4x4 GetReflectionProbeWorldMatrix() + { + float4x4 modelToWorld = float4x4( + float4(1, 0, 0, 0), + float4(0, 1, 0, 0), + float4(0, 0, 1, 0), + float4(0, 0, 0, 1)); + + modelToWorld[0] = m_reflectionProbeData.m_modelToWorld[0]; + modelToWorld[1] = m_reflectionProbeData.m_modelToWorld[1]; + modelToWorld[2] = m_reflectionProbeData.m_modelToWorld[2]; + return modelToWorld; + } + + float4x4 GetReflectionProbeWorldMatrixInverse() + { + float4x4 modelToWorldInverse = float4x4( + float4(1, 0, 0, 0), + float4(0, 1, 0, 0), + float4(0, 0, 1, 0), + float4(0, 0, 0, 1)); + + modelToWorldInverse[0] = m_reflectionProbeData.m_modelToWorldInverse[0]; + modelToWorldInverse[1] = m_reflectionProbeData.m_modelToWorldInverse[1]; + modelToWorldInverse[2] = m_reflectionProbeData.m_modelToWorldInverse[2]; + return modelToWorldInverse; + } +} + +ShaderResourceGroup TerrainMaterialSrg : SRG_PerMaterial +{ + Texture2D m_heightmapImage; + + Sampler HeightmapSampler + { + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Point; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; + }; + + float3 m_baseColor; + float m_roughness; } +option bool o_useTerrainSmoothing = false; + struct VertexInput { float2 m_position : POSITION; float2 m_uv : UV; }; +// Sample a texture with a 5 tap B-Spline. Consider ripping this out and putting in a more general location. +// This function samples a 4x4 neighborhood around the uv. Normally this would take 16 samples, but by taking +// advantage of bilinear filtering this can be done with 9 taps on the edges between pixels. The cost is further +// reduced by dropping the diagonals. +float SampleBSpline5Tap(Texture2D texture, SamplerState textureSampler, float2 uv, float2 textureSize, float2 rcpTextureSize) +{ + // Think of sample locations in the 4x4 neighborhood as having a top left coordinate of 0,0 and + // a bottom right coordinate of 3,3. + + // Find the position in texture space then round it to get the center of the 1,1 pixel (tc1) + float2 texelPos = uv * textureSize; + float2 tc1= floor(texelPos - 0.5) + 0.5; + + // Offset from center position to texel + float2 f = texelPos - tc1; + + // Compute B-Spline weights based on the offset + float2 OneMinusF = (1.0 - f); + float2 OneMinusF2 = OneMinusF * OneMinusF; + float2 OneMinusF3 = OneMinusF2 * OneMinusF; + float2 w0 = OneMinusF3; + float2 w1 = 4.0 + 3.0 * f * f * f - 6.0 * f * f; + float2 w2 = 4.0 + 3.0 * OneMinusF3 - 6.0 * OneMinusF2; + float2 w3 = f * f * f; + + float2 w12 = w1 + w2; + + // Compute uv coordinates for sampling the texture + float2 tc0 = (tc1 - 1.0f) * rcpTextureSize; + float2 tc3 = (tc1 + 2.0f) * rcpTextureSize; + float2 tc12 = (tc1 + w2 / w12) * rcpTextureSize; + + // Compute sample weights + float sw0 = w12.x * w12.y; // middle + float sw1 = w12.x * w0.y; // top + float sw2 = w0.x * w12.y; // left + float sw3 = w12.x * w3.y; // bottom + float sw4 = w3.x * w12.y; // right + + // total weight of samples to normalize result. + float totalWeight = sw0 + sw1 + sw2 + sw3 + sw4; + + float result = 0.0f; + result += texture.SampleLevel(textureSampler, float2(tc12.x, tc12.y), 0.0).r * sw0; + result += texture.SampleLevel(textureSampler, float2(tc12.x, tc0.y), 0.0).r * sw1; + result += texture.SampleLevel(textureSampler, float2( tc0.x, tc12.y), 0.0).r * sw2; + result += texture.SampleLevel(textureSampler, float2(tc12.x, tc3.y), 0.0).r * sw3; + result += texture.SampleLevel(textureSampler, float2( tc3.x, tc12.y), 0.0).r * sw4; + + return result / totalWeight; +} + float4x4 GetObject_WorldMatrix() { float4x4 modelToWorld = float4x4( @@ -58,10 +163,23 @@ float4x4 GetObject_WorldMatrix() float GetHeight(float2 origUv) { float2 uv = clamp(origUv + (ObjectSrg::m_terrainData.m_uvStep * 0.5f), 0.0f, 1.0f); - return ObjectSrg::m_terrainData.m_heightScale * (ObjectSrg::m_heightmapImage.SampleLevel(ObjectSrg::PointSampler, uv, 0).r - 0.5f); + float height = 0.0f; + + if (o_useTerrainSmoothing) + { + float2 textureSize; + TerrainMaterialSrg::m_heightmapImage.GetDimensions(textureSize.x, textureSize.y); + height = SampleBSpline5Tap(TerrainMaterialSrg::m_heightmapImage, TerrainMaterialSrg::HeightmapSampler, uv, textureSize, rcp(textureSize)); + } + else + { + height = TerrainMaterialSrg::m_heightmapImage.SampleLevel(TerrainMaterialSrg::HeightmapSampler, uv, 0).r; + } + + return ObjectSrg::m_terrainData.m_heightScale * (height - 0.5f); } -float4 GetTerrainProjectedPosition(ObjectSrg::TerrainData terrainData, float2 vertexPosition, float2 uv) +float3 GetTerrainWorldPosition(ObjectSrg::TerrainData terrainData, float2 vertexPosition, float2 uv) { // Remove all vertices outside our bounds by turning them into NaN positions. if (any(uv > 1.0) || any (uv < 0.0)) @@ -71,6 +189,10 @@ float4 GetTerrainProjectedPosition(ObjectSrg::TerrainData terrainData, float2 ve // Loop up the height and calculate our final position. float height = GetHeight(uv); - float4 worldPosition = mul(GetObject_WorldMatrix(), float4(vertexPosition, height, 1.0f)); - return mul(ViewSrg::m_viewProjectionMatrix, worldPosition); + return mul(GetObject_WorldMatrix(), float4(vertexPosition, height, 1.0f)).xyz; +} + +float4 GetTerrainProjectedPosition(ObjectSrg::TerrainData terrainData, float2 vertexPosition, float2 uv) +{ + return mul(ViewSrg::m_viewProjectionMatrix, float4(GetTerrainWorldPosition(terrainData, vertexPosition, uv), 1.0)); } diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl new file mode 100644 index 0000000000..d77b944498 --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl @@ -0,0 +1,134 @@ +/* + * 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 +#include +#include +#include +#include +#include + +struct VSOutput +{ + float4 m_position : SV_Position; + float3 m_normal: NORMAL; + float3 m_tangent : TANGENT; + float3 m_bitangent : BITANGENT; + float3 m_worldPosition : UV0; + float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV2; + float2 m_uv : UV1; +}; + +VSOutput TerrainPBR_MainPassVS(VertexInput IN) +{ + VSOutput OUT; + + ObjectSrg::TerrainData terrainData = ObjectSrg::m_terrainData; + + float2 uv = IN.m_uv; + float2 origUv = lerp(terrainData.m_uvMin, terrainData.m_uvMax, uv); + float3 worldPosition = GetTerrainWorldPosition(terrainData, IN.m_position, origUv); + OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0)); + OUT.m_worldPosition = worldPosition; + + // Calculate normal + float up = GetHeight(origUv + terrainData.m_uvStep * float2( 0.0f, -1.0f)); + float right = GetHeight(origUv + terrainData.m_uvStep * float2( 1.0f, 0.0f)); + float down = GetHeight(origUv + terrainData.m_uvStep * float2( 0.0f, 1.0f)); + float left = GetHeight(origUv + terrainData.m_uvStep * float2(-1.0f, 0.0f)); + + OUT.m_bitangent = normalize(float3(0.0, terrainData.m_sampleSpacing * 2.0f, down - up)); + OUT.m_tangent = normalize(float3(terrainData.m_sampleSpacing * 2.0f, 0.0, right - left)); + OUT.m_normal = cross(OUT.m_tangent, OUT.m_bitangent); + OUT.m_uv = uv; + + // directional light shadow + const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight; + if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount) + { + DirectionalLightShadow::GetShadowCoords( + shadowIndex, + worldPosition, + OUT.m_shadowCoords); + } + + return OUT; +} + +ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN) +{ + // ------- Surface ------- + + Surface surface; + + // Position, Normal, Roughness + surface.position = IN.m_worldPosition.xyz; + surface.normal = normalize(IN.m_normal); + surface.roughnessLinear = TerrainMaterialSrg::m_roughness; + surface.CalculateRoughnessA(); + + // Albedo, SpecularF0 + const float specularF0Factor = 0.5f; + float3 color = TerrainMaterialSrg::m_baseColor; + surface.SetAlbedoAndSpecularF0(color, specularF0Factor, 0.0); + + // Clear Coat, Transmission + surface.clearCoat.InitializeToZero(); + surface.transmission.InitializeToZero(); + + // ------- LightingData ------- + + LightingData lightingData; + + // Light iterator + lightingData.tileIterator.Init(IN.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData); + lightingData.Init(surface.position, surface.normal, surface.roughnessLinear); + + // Shadow, Occlusion + lightingData.shadowCoords = IN.m_shadowCoords; + + // Diffuse and Specular response + lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear); + lightingData.diffuseResponse = 1.0f - lightingData.specularResponse; + + const float alpha = 1.0f; + + // ------- Lighting Calculation ------- + + // Apply Decals + ApplyDecals(lightingData.tileIterator, surface); + + // Apply Direct Lighting + ApplyDirectLighting(surface, lightingData); + + // Apply Image Based Lighting (IBL) + ApplyIBL(surface, lightingData); + + // Finalize Lighting + lightingData.FinalizeLighting(surface.transmission.tint); + + PbrLightingOutput lightingOutput = GetPbrLightingOutput(surface, lightingData, alpha); + + // ------- Output ------- + + ForwardPassOutput OUT; + + OUT.m_diffuseColor = lightingOutput.m_diffuseColor; + OUT.m_diffuseColor.w = -1; // Subsurface scattering is disabled + OUT.m_specularColor = lightingOutput.m_specularColor; + OUT.m_specularF0 = lightingOutput.m_specularF0; + OUT.m_albedo = lightingOutput.m_albedo; + OUT.m_normal = lightingOutput.m_normal; + + return OUT; +} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.shader b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.shader new file mode 100644 index 0000000000..0e6f0beb1d --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.shader @@ -0,0 +1,42 @@ +{ + "Source" : "./TerrainPBR_ForwardPass.azsl", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, + "CompareFunc" : "GreaterEqual" + }, + "Stencil" : + { + "Enable" : true, + "ReadMask" : "0x00", + "WriteMask" : "0xFF", + "FrontFace" : + { + "Func" : "Always", + "DepthFailOp" : "Keep", + "FailOp" : "Keep", + "PassOp" : "Replace" + } + } + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "TerrainPBR_MainPassVS", + "type": "Vertex" + }, + { + "name": "TerrainPBR_MainPassPS", + "type": "Fragment" + } + ] + }, + + "DrawList" : "forward" +} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/Terrain_DepthPass.azsl b/Gems/Terrain/Assets/Shaders/Terrain/Terrain_DepthPass.azsl index 20c56323ac..fd855a1bcd 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/Terrain_DepthPass.azsl +++ b/Gems/Terrain/Assets/Shaders/Terrain/Terrain_DepthPass.azsl @@ -8,6 +8,7 @@ #include #include #include "TerrainCommon.azsli" +#include struct VSDepthOutput { diff --git a/Gems/Terrain/Assets/Shaders/Terrain/Terrain_Shadowmap.shader b/Gems/Terrain/Assets/Shaders/Terrain/Terrain_Shadowmap.shader new file mode 100644 index 0000000000..290c83bddf --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/Terrain_Shadowmap.shader @@ -0,0 +1,26 @@ +{ + "Source" : "Terrain_DepthPass", + + "DepthStencilState" : { + "Depth" : { "Enable" : true, "CompareFunc" : "LessEqual" } + }, + + "DrawList" : "shadow", + + "RasterState" : + { + "depthBias" : "10", + "depthBiasSlopeScale" : "4" + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "MainVS", + "type": "Vertex" + } + ] + } +} diff --git a/Gems/Terrain/Code/CMakeLists.txt b/Gems/Terrain/Code/CMakeLists.txt index fc07294dab..17ac3d8474 100644 --- a/Gems/Terrain/Code/CMakeLists.txt +++ b/Gems/Terrain/Code/CMakeLists.txt @@ -21,6 +21,7 @@ ly_add_target( AZ::AzFramework Gem::Atom_RPI.Public Gem::Atom_Utils.Static + Gem::Atom_Feature_Common Gem::GradientSignal Gem::SurfaceData Gem::LmbrCentral diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp index 511b206b84..3e5f9a0608 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp @@ -20,28 +20,39 @@ #include #include #include +#include #include #include +#include #include #include +#include +#include +#include +#include +#include +#include #include #include #include #include #include - +#include namespace Terrain { namespace { - const uint32_t DEFAULT_UploadBufferSize = 512 * 1024; // 512k [[maybe_unused]] const char* TerrainFPName = "TerrainFeatureProcessor"; } + namespace MaterialInputs + { + static const char* const HeightmapImage("settings.heightmapImage"); + } + namespace ShaderInputs { - static const char* const HeightmapImage("m_heightmapImage"); static const char* const ModelToWorld("m_modelToWorld"); static const char* const TerrainData("m_terrainData"); } @@ -60,142 +71,44 @@ namespace Terrain void TerrainFeatureProcessor::Activate() { m_areaData = {}; - - InitializeAtomStuff(); - EnableSceneNotification(); + Initialize(); } - void TerrainFeatureProcessor::ConfigurePipelineState(ShaderState& shaderState, bool assertOnFail) + void TerrainFeatureProcessor::Initialize() { - if (shaderState.m_shader == nullptr) - { - AZ_Assert(shaderState.m_shader || !assertOnFail, "Terrain shader failed to load correctly."); - return; - } - - bool success = GetParentScene()->ConfigurePipelineState(shaderState.m_shader->GetDrawListTag(), shaderState.m_pipelineStateDescriptor); - AZ_Assert(success || !assertOnFail, "Couldn't configure the pipeline state."); - if (success) { - shaderState.m_pipelineState = shaderState.m_shader->AcquirePipelineState(shaderState.m_pipelineStateDescriptor); - AZ_Assert(shaderState.m_pipelineState, "Failed to acquire default pipeline state."); - } - } - - void TerrainFeatureProcessor::InitializeAtomStuff() - { - m_rhiSystem = AZ::RHI::RHISystemInterface::Get(); - - { - auto LoadShader = [this](const char* filePath, ShaderState& shaderState) - { - shaderState.m_shader = AZ::RPI::LoadShader(filePath); - if (!shaderState.m_shader) - { - AZ_Error(TerrainFPName, false, "Failed to find or create a shader instance from shader asset '%s'", filePath); - return; - } - - // Create the data layout - shaderState.m_pipelineStateDescriptor = AZ::RHI::PipelineStateDescriptorForDraw{}; + // Load the terrain material asynchronously + const AZStd::string materialFilePath = "Materials/Terrain/DefaultPbrTerrain.azmaterial"; + m_materialAssetLoader = AZStd::make_unique(); + *m_materialAssetLoader = AZ::RPI::AssetUtils::AsyncAssetLoader::Create(materialFilePath, 0u, + [&](AZ::Data::Asset assetData, bool success) -> void { - AZ::RHI::InputStreamLayoutBuilder layoutBuilder; - - layoutBuilder.AddBuffer() - ->Channel("POSITION", AZ::RHI::Format::R32G32_FLOAT) - ->Channel("UV", AZ::RHI::Format::R32G32_FLOAT) - ; - shaderState.m_pipelineStateDescriptor.m_inputStreamLayout = layoutBuilder.End(); + const AZ::Data::Asset& materialAsset = static_cast>(assetData); + if (success) + { + m_materialInstance = AZ::RPI::Material::FindOrCreate(assetData); + if (!materialAsset->GetObjectSrgLayout()) + { + AZ_Error("TerrainFeatureProcessor", false, "No per-object ShaderResourceGroup found on terrain material."); + } + } } - - auto shaderVariant = shaderState.m_shader->GetVariant(AZ::RPI::ShaderAsset::RootShaderVariantStableId); - shaderVariant.ConfigurePipelineState(shaderState.m_pipelineStateDescriptor); - - // If this fails to run now, it's ok, we'll initialize it in OnRenderPipelineAdded later. - ConfigurePipelineState(shaderState, false); - }; - - LoadShader("Shaders/Terrain/Terrain.azshader", m_shaderStates[ShaderType::Forward]); - LoadShader("Shaders/Terrain/Terrain_DepthPass.azshader", m_shaderStates[ShaderType::Depth]); - - // Forward and depth shader use same srg layout. - AZ::RHI::Ptr perObjectSrgLayout = - m_shaderStates[ShaderType::Forward].m_shader->FindShaderResourceGroupLayout(AZ::Name{"ObjectSrg"}); - - if (!perObjectSrgLayout) - { - AZ_Error(TerrainFPName, false, "Failed to get shader resource group layout"); - return; - } - else if (!perObjectSrgLayout->IsFinalized()) - { - AZ_Error(TerrainFPName, false, "Shader resource group layout is not loaded"); - return; - } - - m_heightmapImageIndex = perObjectSrgLayout->FindShaderInputImageIndex(AZ::Name(ShaderInputs::HeightmapImage)); - AZ_Error(TerrainFPName, m_heightmapImageIndex.IsValid(), "Failed to find shader input image %s.", ShaderInputs::HeightmapImage); - - m_modelToWorldIndex = perObjectSrgLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::ModelToWorld)); - AZ_Error(TerrainFPName, m_modelToWorldIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::ModelToWorld); - - m_terrainDataIndex = perObjectSrgLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::TerrainData)); - AZ_Error(TerrainFPName, m_terrainDataIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::TerrainData); - } - - AZ::RHI::BufferPoolDescriptor dmaPoolDescriptor; - dmaPoolDescriptor.m_heapMemoryLevel = AZ::RHI::HeapMemoryLevel::Host; - dmaPoolDescriptor.m_bindFlags = AZ::RHI::BufferBindFlags::InputAssembly; - - m_hostPool = AZ::RHI::Factory::Get().CreateBufferPool(); - m_hostPool->SetName(AZ::Name("TerrainVertexPool")); - AZ::RHI::ResultCode resultCode = m_hostPool->Init(*m_rhiSystem->GetDevice(), dmaPoolDescriptor); - - if (resultCode != AZ::RHI::ResultCode::Success) - { - AZ_Error(TerrainFPName, false, "Failed to create host buffer pool from RPI"); - return; + ); } - InitializeTerrainPatch(); - - if (!InitializeRenderBuffers()) + if (!InitializePatchModel()) { AZ_Error(TerrainFPName, false, "Failed to create Terrain render buffers!"); return; } } - void TerrainFeatureProcessor::OnRenderPipelineAdded([[maybe_unused]] AZ::RPI::RenderPipelinePtr pipeline) - { - for (ShaderState& shaderState: m_shaderStates) - { - ConfigurePipelineState(shaderState, true); - } - } - - void TerrainFeatureProcessor::OnRenderPipelineRemoved([[maybe_unused]] AZ::RPI::RenderPipeline* pipeline) - { - } - - void TerrainFeatureProcessor::OnRenderPipelinePassesChanged([[maybe_unused]] AZ::RPI::RenderPipeline* renderPipeline) - { - } - - void TerrainFeatureProcessor::Deactivate() { DisableSceneNotification(); - DestroyRenderBuffers(); + m_patchModel = {}; m_areaData = {}; - - if (m_hostPool) - { - m_hostPool.reset(); - } - - m_rhiSystem = nullptr; } void TerrainFeatureProcessor::Render(const AZ::RPI::FeatureProcessor::RenderPacket& packet) @@ -254,240 +167,269 @@ namespace Terrain { AZ_PROFILE_FUNCTION(AzRender); - if ((m_shaderStates[ShaderType::Forward].m_shader == nullptr) || - (m_shaderStates[ShaderType::Depth].m_shader == nullptr) || - m_shaderStates[ShaderType::Forward].m_shader->GetDrawListTag().IsNull() || - m_shaderStates[ShaderType::Depth].m_shader->GetDrawListTag().IsNull()) - { - return; - } - if (!m_areaData.m_terrainBounds.IsValid()) { return; } - if (m_areaData.m_propertiesDirty) + if (m_areaData.m_propertiesDirty && m_materialInstance) { m_areaData.m_propertiesDirty = false; m_sectorData.clear(); - AZ::RHI::DrawPacketBuilder drawPacketBuilder; + AZ::RPI::MaterialPropertyIndex heightmapPropertyIndex = + m_materialInstance->GetMaterialPropertiesLayout()->FindPropertyIndex(AZ::Name(MaterialInputs::HeightmapImage)); + AZ_Error(TerrainFPName, heightmapPropertyIndex.IsValid(), "Failed to find material input constant %s.", MaterialInputs::HeightmapImage); + AZ::Data::Instance heightmapImage = m_areaData.m_heightmapImage; + m_materialInstance->SetPropertyValue(heightmapPropertyIndex, heightmapImage); + m_materialInstance->Compile(); - uint32_t numIndices = static_cast(m_gridIndices.size()); + const auto layout = m_materialInstance->GetAsset()->GetObjectSrgLayout(); - AZ::RHI::DrawIndexed drawIndexed; - drawIndexed.m_indexCount = numIndices; - drawIndexed.m_indexOffset = 0; - drawIndexed.m_vertexOffset = 0; + m_modelToWorldIndex = layout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::ModelToWorld)); + AZ_Error(TerrainFPName, m_modelToWorldIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::ModelToWorld); + + m_terrainDataIndex = layout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::TerrainData)); + AZ_Error(TerrainFPName, m_terrainDataIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::TerrainData); float xFirstPatchStart = - m_areaData.m_terrainBounds.GetMin().GetX() - fmod(m_areaData.m_terrainBounds.GetMin().GetX(), m_gridMeters); - float xLastPatchStart = m_areaData.m_terrainBounds.GetMax().GetX() - fmod(m_areaData.m_terrainBounds.GetMax().GetX(), m_gridMeters); + m_areaData.m_terrainBounds.GetMin().GetX() - fmod(m_areaData.m_terrainBounds.GetMin().GetX(), GridMeters); + float xLastPatchStart = m_areaData.m_terrainBounds.GetMax().GetX() - fmod(m_areaData.m_terrainBounds.GetMax().GetX(), GridMeters); float yFirstPatchStart = - m_areaData.m_terrainBounds.GetMin().GetY() - fmod(m_areaData.m_terrainBounds.GetMin().GetY(), m_gridMeters); - float yLastPatchStart = m_areaData.m_terrainBounds.GetMax().GetY() - fmod(m_areaData.m_terrainBounds.GetMax().GetY(), m_gridMeters); + m_areaData.m_terrainBounds.GetMin().GetY() - fmod(m_areaData.m_terrainBounds.GetMin().GetY(), GridMeters); + float yLastPatchStart = m_areaData.m_terrainBounds.GetMax().GetY() - fmod(m_areaData.m_terrainBounds.GetMax().GetY(), GridMeters); - for (float yPatch = yFirstPatchStart; yPatch <= yLastPatchStart; yPatch += m_gridMeters) + for (float yPatch = yFirstPatchStart; yPatch <= yLastPatchStart; yPatch += GridMeters) { - for (float xPatch = xFirstPatchStart; xPatch <= xLastPatchStart; xPatch += m_gridMeters) + for (float xPatch = xFirstPatchStart; xPatch <= xLastPatchStart; xPatch += GridMeters) { - drawPacketBuilder.Begin(nullptr); - drawPacketBuilder.SetDrawArguments(drawIndexed); - drawPacketBuilder.SetIndexBufferView(m_indexBufferView); - auto& forwardShader = m_shaderStates[ShaderType::Forward].m_shader; - - auto resourceGroup = AZ::RPI::ShaderResourceGroup::Create(forwardShader->GetAsset(), forwardShader->GetSupervariantIndex(), AZ::Name("ObjectSrg")); - if (!resourceGroup) + const auto& materialAsset = m_materialInstance->GetAsset(); + auto& shaderAsset = materialAsset->GetMaterialTypeAsset()->GetShaderAssetForObjectSrg(); + auto objectSrg = AZ::RPI::ShaderResourceGroup::Create(shaderAsset, materialAsset->GetObjectSrgLayout()->GetName()); + if (!objectSrg) { - AZ_Error(TerrainFPName, false, "Failed to create shader resource group"); - return; + AZ_Warning("TerrainFeatureProcessor", false, "Failed to create a new shader resource group, skipping."); + continue; } - AZStd::array uvMin = { 0.0f, 0.0f }; - AZStd::array uvMax = { 1.0f, 1.0f }; + { // Update SRG + + AZStd::array uvMin = { 0.0f, 0.0f }; + AZStd::array uvMax = { 1.0f, 1.0f }; - uvMin[0] = (float)((xPatch - m_areaData.m_terrainBounds.GetMin().GetX()) / m_areaData.m_terrainBounds.GetXExtent()); - uvMin[1] = (float)((yPatch - m_areaData.m_terrainBounds.GetMin().GetY()) / m_areaData.m_terrainBounds.GetYExtent()); + uvMin[0] = (float)((xPatch - m_areaData.m_terrainBounds.GetMin().GetX()) / m_areaData.m_terrainBounds.GetXExtent()); + uvMin[1] = (float)((yPatch - m_areaData.m_terrainBounds.GetMin().GetY()) / m_areaData.m_terrainBounds.GetYExtent()); - uvMax[0] = - (float)(((xPatch + m_gridMeters) - m_areaData.m_terrainBounds.GetMin().GetX()) / m_areaData.m_terrainBounds.GetXExtent()); - uvMax[1] = - (float)(((yPatch + m_gridMeters) - m_areaData.m_terrainBounds.GetMin().GetY()) / m_areaData.m_terrainBounds.GetYExtent()); + uvMax[0] = + (float)(((xPatch + GridMeters) - m_areaData.m_terrainBounds.GetMin().GetX()) / m_areaData.m_terrainBounds.GetXExtent()); + uvMax[1] = + (float)(((yPatch + GridMeters) - m_areaData.m_terrainBounds.GetMin().GetY()) / m_areaData.m_terrainBounds.GetYExtent()); - AZStd::array uvStep = - { - 1.0f / m_areaData.m_heightmapImageWidth, 1.0f / m_areaData.m_heightmapImageHeight, - }; + AZStd::array uvStep = + { + 1.0f / m_areaData.m_heightmapImageWidth, 1.0f / m_areaData.m_heightmapImageHeight, + }; - AZ::Transform transform = m_areaData.m_transform; - transform.SetTranslation(xPatch, yPatch, m_areaData.m_transform.GetTranslation().GetZ()); + AZ::Transform transform = m_areaData.m_transform; + transform.SetTranslation(xPatch, yPatch, m_areaData.m_transform.GetTranslation().GetZ()); - AZ::Matrix3x4 matrix3x4 = AZ::Matrix3x4::CreateFromTransform(transform); + AZ::Matrix3x4 matrix3x4 = AZ::Matrix3x4::CreateFromTransform(transform); - resourceGroup->SetImage(m_heightmapImageIndex, m_areaData.m_heightmapImage); - resourceGroup->SetConstant(m_modelToWorldIndex, matrix3x4); + objectSrg->SetConstant(m_modelToWorldIndex, matrix3x4); - ShaderTerrainData terrainDataForSrg; - terrainDataForSrg.m_sampleSpacing = m_areaData.m_sampleSpacing; - terrainDataForSrg.m_heightScale = m_areaData.m_heightScale; - terrainDataForSrg.m_uvMin = uvMin; - terrainDataForSrg.m_uvMax = uvMax; - terrainDataForSrg.m_uvStep = uvStep; - resourceGroup->SetConstant(m_terrainDataIndex, terrainDataForSrg); + ShaderTerrainData terrainDataForSrg; + terrainDataForSrg.m_sampleSpacing = m_areaData.m_sampleSpacing; + terrainDataForSrg.m_heightScale = m_areaData.m_heightScale; + terrainDataForSrg.m_uvMin = uvMin; + terrainDataForSrg.m_uvMax = uvMax; + terrainDataForSrg.m_uvStep = uvStep; + objectSrg->SetConstant(m_terrainDataIndex, terrainDataForSrg); - resourceGroup->Compile(); - drawPacketBuilder.AddShaderResourceGroup(resourceGroup->GetRHIShaderResourceGroup()); + objectSrg->Compile(); + } - auto addDrawItem = [&](ShaderState& shaderState) - { - AZ::RHI::DrawPacketBuilder::DrawRequest drawRequest; - drawRequest.m_listTag = shaderState.m_shader->GetDrawListTag(); - drawRequest.m_pipelineState = shaderState.m_pipelineState.get(); - drawRequest.m_streamBufferViews = AZStd::array_view(&m_vertexBufferView, 1); - drawPacketBuilder.AddDrawItem(drawRequest); - }; - - for (ShaderState& shaderState : m_shaderStates) + m_sectorData.push_back(); + SectorData& sectorData = m_sectorData.back(); + + for (auto& lod : m_patchModel->GetLods()) { - addDrawItem(shaderState); + AZ::RPI::ModelLod& modelLod = *lod.get(); + sectorData.m_drawPackets.emplace_back(modelLod, 0, m_materialInstance, objectSrg); + AZ::RPI::MeshDrawPacket& drawPacket = sectorData.m_drawPackets.back(); + + // set the shader option to select forward pass IBL specular if necessary + if (!drawPacket.SetShaderOption(AZ::Name("o_meshUseForwardPassIBLSpecular"), AZ::RPI::ShaderOptionValue{ false })) + { + AZ_Warning("MeshDrawPacket", false, "Failed to set o_meshUseForwardPassIBLSpecular on mesh draw packet"); + } + uint8_t stencilRef = AZ::Render::StencilRefs::UseDiffuseGIPass | AZ::Render::StencilRefs::UseIBLSpecularPass; + drawPacket.SetStencilRef(stencilRef); + drawPacket.Update(*GetParentScene(), true); } - //addDrawItem(m_shaderStates[ShaderType::Forward]); - - m_sectorData.emplace_back( - drawPacketBuilder.End(), + + sectorData.m_aabb = AZ::Aabb::CreateFromMinMax( AZ::Vector3(xPatch, yPatch, m_areaData.m_terrainBounds.GetMin().GetZ()), - AZ::Vector3(xPatch + m_gridMeters, yPatch + m_gridMeters, m_areaData.m_terrainBounds.GetMax().GetZ()) - ), - resourceGroup - ); + AZ::Vector3(xPatch + GridMeters, yPatch + GridMeters, m_areaData.m_terrainBounds.GetMax().GetZ()) + ); + sectorData.m_srg = objectSrg; } } } - - for (auto& view : process.m_views) + + for (auto& sectorData : m_sectorData) { - AZ::Frustum viewFrustum = AZ::Frustum::CreateFromMatrixColumnMajor(view->GetWorldToClipMatrix()); - for (auto& sectorData : m_sectorData) + uint8_t lodChoice = AZ::RPI::ModelLodAsset::LodCountMax; + + // Go through all cameras and choose an LOD based on the closest camera. + for (auto& view : process.m_views) + { + if ((view->GetUsageFlags() & AZ::RPI::View::UsageFlags::UsageCamera) > 0) + { + AZ::Vector3 cameraPosition = view->GetCameraTransform().GetTranslation(); + AZ::Vector2 cameraPositionXY = AZ::Vector2(cameraPosition.GetX(), cameraPosition.GetY()); + AZ::Vector2 sectorCenterXY = AZ::Vector2(sectorData.m_aabb.GetCenter().GetX(), sectorData.m_aabb.GetCenter().GetY()); + + float sectorDistance = sectorCenterXY.GetDistance(cameraPositionXY); + float lodForCamera = ceilf(AZ::GetMax(0.0f, log2f(sectorDistance / (GridMeters * 4.0f)))); + lodChoice = AZ::GetMin(lodChoice, aznumeric_cast(lodForCamera)); + } + } + + // Add the correct LOD draw packet for visible sectors. + for (auto& view : process.m_views) { + AZ::Frustum viewFrustum = AZ::Frustum::CreateFromMatrixColumnMajor(view->GetWorldToClipMatrix()); if (viewFrustum.IntersectAabb(sectorData.m_aabb) != AZ::IntersectResult::Exterior) { - view->AddDrawPacket(sectorData.m_drawPacket.get()); + uint8_t lodToRender = AZ::GetMin(lodChoice, aznumeric_cast(sectorData.m_drawPackets.size() - 1)); + view->AddDrawPacket(sectorData.m_drawPackets.at(lodToRender).GetRHIDrawPacket()); } } } } - void TerrainFeatureProcessor::InitializeTerrainPatch() + void TerrainFeatureProcessor::InitializeTerrainPatch(uint16_t gridSize, float gridSpacing, PatchData& patchdata) { - m_gridVertices.clear(); - m_gridIndices.clear(); + patchdata.m_positions.clear(); + patchdata.m_uvs.clear(); + patchdata.m_indices.clear(); + + uint16_t gridVertices = gridSize + 1; // For m_gridSize quads, (m_gridSize + 1) vertices are needed. + size_t size = gridVertices * gridVertices; - for (float y = 0.0f; y < m_gridMeters; y += m_gridSpacing) + patchdata.m_positions.reserve(size); + patchdata.m_uvs.reserve(size); + + for (uint16_t y = 0; y < gridVertices; ++y) { - for (float x = 0.0f; x < m_gridMeters; x += m_gridSpacing) + for (uint16_t x = 0; x < gridVertices; ++x) { - float x0 = x; - float x1 = x + m_gridSpacing; - float y0 = y; - float y1 = y + m_gridSpacing; - - uint16_t startIndex = (uint16_t)(m_gridVertices.size()); - - m_gridVertices.emplace_back(x0, y0, x0 / m_gridMeters, y0 / m_gridMeters); - m_gridVertices.emplace_back(x1, y0, x1 / m_gridMeters, y0 / m_gridMeters); - m_gridVertices.emplace_back(x0, y1, x0 / m_gridMeters, y1 / m_gridMeters); - m_gridVertices.emplace_back(x1, y1, x1 / m_gridMeters, y1 / m_gridMeters); - - m_gridIndices.emplace_back(startIndex); - m_gridIndices.emplace_back(aznumeric_cast(startIndex + 1)); - m_gridIndices.emplace_back(aznumeric_cast(startIndex + 2)); - m_gridIndices.emplace_back(aznumeric_cast(startIndex + 1)); - m_gridIndices.emplace_back(aznumeric_cast(startIndex + 3)); - m_gridIndices.emplace_back(aznumeric_cast(startIndex + 2)); + patchdata.m_positions.push_back({ aznumeric_cast(x) * gridSpacing, aznumeric_cast(y) * gridSpacing }); + patchdata.m_uvs.push_back({ aznumeric_cast(x) / gridSize, aznumeric_cast(y) / gridSize }); } } - } - bool TerrainFeatureProcessor::InitializeRenderBuffers() + patchdata.m_indices.reserve(gridSize * gridSize * 6); // total number of quads, 2 triangles with 6 indices per quad. + + for (uint16_t y = 0; y < gridSize; ++y) + { + for (uint16_t x = 0; x < gridSize; ++x) + { + uint16_t topLeft = y * gridVertices + x; + uint16_t topRight = topLeft + 1; + uint16_t bottomLeft = (y + 1) * gridVertices + x; + uint16_t bottomRight = bottomLeft + 1; + + patchdata.m_indices.emplace_back(topLeft); + patchdata.m_indices.emplace_back(topRight); + patchdata.m_indices.emplace_back(bottomLeft); + patchdata.m_indices.emplace_back(bottomLeft); + patchdata.m_indices.emplace_back(topRight); + patchdata.m_indices.emplace_back(bottomRight); + } + } + } + + AZ::Outcome> TerrainFeatureProcessor::CreateBufferAsset( + const void* data, const AZ::RHI::BufferViewDescriptor& bufferViewDescriptor, const AZStd::string& bufferName) { - AZ::RHI::ResultCode result = AZ::RHI::ResultCode::Fail; - - // Create geometry buffers - m_indexBuffer = AZ::RHI::Factory::Get().CreateBuffer(); - m_vertexBuffer = AZ::RHI::Factory::Get().CreateBuffer(); + AZ::RPI::BufferAssetCreator creator; + creator.Begin(AZ::Uuid::CreateRandom()); - m_indexBuffer->SetName(AZ::Name("TerrainIndexBuffer")); - m_vertexBuffer->SetName(AZ::Name("TerrainVertexBuffer")); + AZ::RHI::BufferDescriptor bufferDescriptor; + bufferDescriptor.m_bindFlags = AZ::RHI::BufferBindFlags::InputAssembly | AZ::RHI::BufferBindFlags::ShaderRead; + bufferDescriptor.m_byteCount = static_cast(bufferViewDescriptor.m_elementSize) * static_cast(bufferViewDescriptor.m_elementCount); - AZStd::vector> buffers = { m_indexBuffer , m_vertexBuffer }; + creator.SetBuffer(data, bufferDescriptor.m_byteCount, bufferDescriptor); + creator.SetBufferViewDescriptor(bufferViewDescriptor); + creator.SetUseCommonPool(AZ::RPI::CommonBufferPoolType::StaticInputAssembly); - // Fill our buffers with the vertex/index data - for (size_t bufferIndex = 0; bufferIndex < buffers.size(); ++bufferIndex) + AZ::Data::Asset bufferAsset; + if (creator.End(bufferAsset)) { - AZ::RHI::Ptr buffer = buffers[bufferIndex]; + bufferAsset.SetHint(bufferName); + return AZ::Success(bufferAsset); + } + + return AZ::Failure(); + } - // Initialize the buffer + bool TerrainFeatureProcessor::InitializePatchModel() + { + AZ::RPI::ModelAssetCreator modelAssetCreator; + modelAssetCreator.Begin(AZ::Uuid::CreateRandom()); - AZ::RHI::BufferInitRequest bufferRequest; - bufferRequest.m_descriptor = AZ::RHI::BufferDescriptor{ AZ::RHI::BufferBindFlags::InputAssembly, DEFAULT_UploadBufferSize }; - bufferRequest.m_buffer = buffer.get(); + uint16_t gridSize = GridSize; + float gridSpacing = GridSpacing; - result = m_hostPool->InitBuffer(bufferRequest); + for (uint32_t i = 0; i < AZ::RPI::ModelLodAsset::LodCountMax && gridSize > 0; ++i) + { + PatchData patchData; + InitializeTerrainPatch(gridSize, gridSpacing, patchData); - if (result != AZ::RHI::ResultCode::Success) + auto positionBufferViewDesc = AZ::RHI::BufferViewDescriptor::CreateTyped(0, aznumeric_cast(patchData.m_positions.size()), AZ::RHI::Format::R32G32_FLOAT); + auto positionsOutcome = CreateBufferAsset(patchData.m_positions.data(), positionBufferViewDesc, "TerrainPatchPositions"); + + auto uvBufferViewDesc = AZ::RHI::BufferViewDescriptor::CreateTyped(0, aznumeric_cast(patchData.m_uvs.size()), AZ::RHI::Format::R32G32_FLOAT); + auto uvsOutcome = CreateBufferAsset(patchData.m_uvs.data(), uvBufferViewDesc, "TerrainPatchUvs"); + + auto indexBufferViewDesc = AZ::RHI::BufferViewDescriptor::CreateTyped(0, aznumeric_cast(patchData.m_indices.size()), AZ::RHI::Format::R16_UINT); + auto indicesOutcome = CreateBufferAsset(patchData.m_indices.data(), indexBufferViewDesc, "TerrainPatchIndices"); + + if (!positionsOutcome.IsSuccess() || !uvsOutcome.IsSuccess() || !indicesOutcome.IsSuccess()) { AZ_Error(TerrainFPName, false, "Failed to create GPU buffers for Terrain"); return false; } + + AZ::RPI::ModelLodAssetCreator modelLodAssetCreator; + modelLodAssetCreator.Begin(AZ::Uuid::CreateRandom()); - // Grab a pointer to the buffer's data - - m_hostPool->OrphanBuffer(*buffer); - - AZ::RHI::BufferMapResponse mapResponse; - m_hostPool->MapBuffer(AZ::RHI::BufferMapRequest(*buffer, 0, DEFAULT_UploadBufferSize), mapResponse); - - auto* mappedData = reinterpret_cast(mapResponse.m_data); + modelLodAssetCreator.BeginMesh(); + modelLodAssetCreator.AddMeshStreamBuffer(AZ::RHI::ShaderSemantic{ "POSITION" }, AZ::Name(), {positionsOutcome.GetValue(), positionBufferViewDesc}); + modelLodAssetCreator.AddMeshStreamBuffer(AZ::RHI::ShaderSemantic{ "UV" }, AZ::Name(), {uvsOutcome.GetValue(), uvBufferViewDesc}); + modelLodAssetCreator.SetMeshIndexBuffer({indicesOutcome.GetValue(), indexBufferViewDesc}); - //0th index should always be the index buffer - if (bufferIndex == 0) - { - // Fill the index buffer with our terrain patch indices - const uint64_t idxSize = m_gridIndices.size() * sizeof(uint16_t); - memcpy(mappedData, m_gridIndices.data(), idxSize); + AZ::Aabb aabb = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0, 0.0, 0.0), AZ::Vector3(GridMeters, GridMeters, 0.0)); + modelLodAssetCreator.SetMeshAabb(AZStd::move(aabb)); + modelLodAssetCreator.SetMeshName(AZ::Name("Terrain Patch")); + modelLodAssetCreator.EndMesh(); - m_indexBufferView = AZ::RHI::IndexBufferView( - *buffer, 0, static_cast(idxSize), AZ::RHI::IndexFormat::Uint16); - } - else - { - // Fill the vertex buffer with our terrain patch vertices - const uint64_t elementSize = m_gridVertices.size() * sizeof(Vertex); - memcpy(mappedData, m_gridVertices.data(), elementSize); - - m_vertexBufferView = AZ::RHI::StreamBufferView( - *buffer, 0, static_cast(elementSize), static_cast(sizeof(Vertex))); - } + AZ::Data::Asset modelLodAsset; + modelLodAssetCreator.End(modelLodAsset); + + modelAssetCreator.AddLodAsset(AZStd::move(modelLodAsset)); - m_hostPool->UnmapBuffer(*buffer); + gridSize = gridSize / 2; + gridSpacing *= 2.0f; } - return true; - } - - void TerrainFeatureProcessor::DestroyRenderBuffers() - { - m_indexBuffer.reset(); - m_vertexBuffer.reset(); + AZ::Data::Asset modelAsset; + bool success = modelAssetCreator.End(modelAsset); - m_indexBufferView = {}; - m_vertexBufferView = {}; + m_patchModel = AZ::RPI::Model::FindOrCreate(modelAsset); - for (ShaderState& shaderState : m_shaderStates) - { - shaderState.Reset(); - } + return success; } } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h index 382f473b25..160d3113c2 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h @@ -14,9 +14,11 @@ #include #include +#include #include #include +#include #include #include #include @@ -26,6 +28,15 @@ #include #include +namespace AZ::RPI +{ + namespace AssetUtils + { + class AsyncAssetLoader; + } + class Model; +} + namespace Terrain { class TerrainFeatureProcessor final @@ -57,28 +68,6 @@ namespace Terrain private: - // System-level references to the shader, pipeline, and shader-related information - enum ShaderType - { - Depth, - Forward, - Count, - }; - - struct ShaderState - { - AZ::Data::Instance m_shader; - AZ::RHI::ConstPtr m_pipelineState; - AZ::RHI::PipelineStateDescriptorForDraw m_pipelineStateDescriptor; - - void Reset() - { - m_shader.reset(); - m_pipelineState.reset(); - m_pipelineStateDescriptor = {}; - } - }; - struct ShaderTerrainData // Must align with struct in Object Srg { AZStd::array m_uvMin; @@ -87,62 +76,47 @@ namespace Terrain float m_sampleSpacing; float m_heightScale; }; + + struct VertexPosition + { + float m_posx; + float m_posy; + }; - // RPI::SceneNotificationBus overrides ... - void OnRenderPipelineAdded(AZ::RPI::RenderPipelinePtr pipeline) override; - void OnRenderPipelineRemoved(AZ::RPI::RenderPipeline* pipeline) override; - void OnRenderPipelinePassesChanged(AZ::RPI::RenderPipeline* renderPipeline) override; - - void InitializeAtomStuff(); - void ConfigurePipelineState(ShaderState& shaderState, bool assertOnFail); - - void InitializeTerrainPatch(); + struct VertexUv + { + float m_u; + float m_v; + }; - bool InitializeRenderBuffers(); - void DestroyRenderBuffers(); + struct PatchData + { + AZStd::vector m_positions; + AZStd::vector m_uvs; + AZStd::vector m_indices; + }; + + void Initialize(); + void InitializeTerrainPatch(uint16_t gridSize, float gridSpacing, PatchData& patchdata); + bool InitializePatchModel(); void ProcessSurfaces(const FeatureProcessor::RenderPacket& process); + + AZ::Outcome> CreateBufferAsset( + const void* data, const AZ::RHI::BufferViewDescriptor& bufferViewDescriptor, const AZStd::string& bufferName); // System-level parameters - const float m_gridSpacing{ 1.0f }; - const float m_gridMeters{ 32.0f }; - - // System-level cached reference to the Atom RHI - AZ::RHI::RHISystemInterface* m_rhiSystem = nullptr; + static constexpr float GridSpacing{ 1.0f }; + static constexpr uint32_t GridSize{ 64 }; // number of terrain quads (vertices are m_gridSize + 1) + static constexpr float GridMeters{ GridSpacing * GridSize }; - AZStd::array m_shaderStates; + AZStd::unique_ptr m_materialAssetLoader; + AZ::Data::Instance m_materialInstance; - AZ::RHI::ShaderInputImageIndex m_heightmapImageIndex; AZ::RHI::ShaderInputConstantIndex m_modelToWorldIndex; AZ::RHI::ShaderInputConstantIndex m_terrainDataIndex; - // Pos_float_2 + UV_float_2 - struct Vertex - { - float m_posx; - float m_posy; - float m_u; - float m_v; - - Vertex(float posx, float posy, float u, float v) - : m_posx(posx) - , m_posy(posy) - , m_u(u) - , m_v(v) - { - } - }; - - // System-level definition of a grid patch. (ex: 32m x 32m) - AZStd::vector m_gridVertices; - AZStd::vector m_gridIndices; - - // System-level data related to the grid patch - AZ::RHI::Ptr m_hostPool = nullptr; - AZ::RHI::Ptr m_indexBuffer; - AZ::RHI::Ptr m_vertexBuffer; - AZ::RHI::IndexBufferView m_indexBufferView; - AZ::RHI::StreamBufferView m_vertexBufferView; + AZ::Data::Instance m_patchModel; // Per-area data struct TerrainAreaData @@ -161,15 +135,9 @@ namespace Terrain struct SectorData { - AZ::Data::Instance m_srg; + AZ::Data::Instance m_srg; // Hold on to ref so it's not dropped AZ::Aabb m_aabb; - AZStd::unique_ptr m_drawPacket; - - SectorData(const AZ::RHI::DrawPacket* drawPacket, AZ::Aabb aabb, AZ::Data::Instance srg) - : m_srg(srg) - , m_aabb(aabb) - , m_drawPacket(drawPacket) - {} + AZStd::fixed_vector m_drawPackets; }; AZStd::vector m_sectorData; diff --git a/Tools/LyTestTools/tests/CMakeLists.txt b/Tools/LyTestTools/tests/CMakeLists.txt index a6d7a0734c..31766f3080 100644 --- a/Tools/LyTestTools/tests/CMakeLists.txt +++ b/Tools/LyTestTools/tests/CMakeLists.txt @@ -39,6 +39,14 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT TEST_SUITE smoke COMPONENT TestTools ) + + ly_add_pytest( + NAME LyTestTools_IntegTests_ProcessUtilsLinux_smoke_no_gpu + PATH ${CMAKE_CURRENT_LIST_DIR}/integ/test_process_utils_linux.py + TEST_SERIAL + TEST_SUITE smoke + COMPONENT TestTools + ) # Regression tests. ly_add_pytest( diff --git a/Tools/LyTestTools/tests/integ/test_process_utils_linux.py b/Tools/LyTestTools/tests/integ/test_process_utils_linux.py new file mode 100644 index 0000000000..23ad8f6653 --- /dev/null +++ b/Tools/LyTestTools/tests/integ/test_process_utils_linux.py @@ -0,0 +1,39 @@ +""" +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 +""" +import subprocess +import pytest + +import ly_test_tools.environment.process_utils as process_utils +import ly_test_tools.environment.waiter as waiter +from ly_test_tools import LINUX + +if LINUX: + pytestmark = pytest.mark.SUITE_smoke +else: + pytestmark = pytest.mark.skipif(not LINUX, reason="Only runs on Linux") + + +class TestProcessUtils(object): + + def test_KillLinuxProcess_ProcessStarted_KilledSuccessfully(self): + # Construct a simple timeout command + linux_executable = 'timeout' + command = [linux_executable, '5s', 'echo'] + + # Verification function for the waiter to call + def process_killed(): + return not process_utils.process_exists(linux_executable, ignore_extensions=True) + + # Create a new process with no output in a new session + with subprocess.Popen(command, stdout=subprocess.DEVNULL, start_new_session=True): + # Ensure that the process was started + assert process_utils.process_exists(linux_executable, ignore_extensions=True), \ + f"Process '{linux_executable}' was expected to exist, but could not be found." + # Kill the process using the process_utils module + process_utils.kill_processes_named(linux_executable, ignore_extensions=True) + # Verify that the process was killed + waiter.wait_for(process_killed, timeout=2) # Raises exception if the process is alive.