Merge branch 'development' into Atom/guthadam/material_editor_replace_modified_color_with_indicator

monroegm-disable-blank-issue-2
Guthrie Adams 4 years ago
commit e04aaf3f47

@ -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());

@ -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;
};
}

@ -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(

@ -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;
};
}

@ -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;

@ -82,6 +82,8 @@ namespace AzToolsFramework
, m_entityExpansionState()
, m_entityFilteredState()
{
m_focusModeInterface = AZ::Interface<FocusModeInterface>::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<AzToolsFramework::EditorEntityUiInterface>::Get();
AZ_Assert(m_editorEntityUiInterface != nullptr,
"EntityOutlinerListModel requires a EditorEntityUiInterface instance on Initialize.");
m_focusModeInterface = AZ::Interface<FocusModeInterface>::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;

@ -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);

@ -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<int>(255.0f * overlayTransparency)));
}

@ -12,6 +12,7 @@
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <Atom/Feature/RenderCommon.h>
#include <Atom/Feature/CoreLights/EsmShadowmapsPassData.h>
#include <Atom/Feature/CoreLights/PhotometricValue.h>
#include <Atom/Feature/CoreLights/ShadowConstants.h>
@ -33,8 +34,6 @@
#include <CoreLights/ShadowmapPass.h>
#include <CoreLights/ProjectedShadowmapsPass.h>
#include <RenderCommon.h>
namespace AZ
{
namespace Render

@ -6,10 +6,9 @@
*
*/
#include <RenderCommon.h>
#include <Atom/RHI/RHIUtils.h>
#include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
#include <Atom/Feature/RenderCommon.h>
#include <Atom/Feature/Mesh/MeshFeatureProcessor.h>
#include <Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessor.h>
#include <Atom/RPI.Public/Model/ModelLodUtils.h>

@ -9,7 +9,7 @@
#pragma once
#include <AzCore/Math/Transform.h>
#include <RenderCommon.h>
#include <Atom/Feature/RenderCommon.h>
#include <Atom/Feature/Mesh/MeshFeatureProcessorInterface.h>
#include <Atom/RPI.Public/Base.h>
#include <Atom/RPI.Public/Model/Model.h>

@ -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

@ -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

@ -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<QWidget*>(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<QWidget*>(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();
}

@ -30,104 +30,85 @@
<number>0</number>
</property>
<item>
<widget class="QWidget" name="m_sections">
<layout class="QVBoxLayout" name="m_sectionsLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="m_headingSection" native="true">
<layout class="QVBoxLayout" name="m_headingSectionLayout">
<property name="spacing">
<number>0</number>
<layout class="QVBoxLayout" name="m_headingContentsLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="m_groupSectionLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QScrollArea" name="m_groupScrollArea">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="leftMargin">
<number>0</number>
<property name="widgetResizable">
<bool>true</bool>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="m_groupSection" native="true">
<layout class="QVBoxLayout" name="m_groupSectionLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QScrollArea" name="m_groupScrollArea">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
<widget class="QWidget" name="m_groupContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>679</width>
<height>767</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="m_groupContentsLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="widgetResizable">
<bool>true</bool>
<property name="bottomMargin">
<number>0</number>
</property>
<widget class="QWidget" name="m_groupContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>683</width>
<height>763</height>
</rect>
</property>
<layout class="QVBoxLayout" name="m_groupContentsLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</widget>

@ -0,0 +1,11 @@
{
"description": "",
"materialType": "PbrTerrain.materialtype",
"parentMaterial": "",
"propertyLayoutVersion": 1,
"properties": {
"settings": {
"baseColor": [ 0.78, 0.59, 0.48 ]
}
}
}

@ -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": [
]
}

@ -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 <Atom/Features/SrgSemantics.azsli>
#include <scenesrg.srgi>
#include <viewsrg.srgi>
#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;
}

@ -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
}
}

@ -9,18 +9,6 @@
ShaderResourceGroup ObjectSrg : SRG_PerObject
{
Texture2D<float> 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<float> 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<float> 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));
}

@ -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 <Atom/Features/SrgSemantics.azsli>
#include <viewsrg.srgi>
#include <TerrainCommon.azsli>
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
#include <Atom/Features/PBR/ForwardPassSrg.azsli>
#include <Atom/Features/PBR/ForwardPassOutput.azsli>
#include <Atom/Features/PBR/AlphaUtils.azsli>
#include <Atom/Features/ColorManagement/TransformColor.azsli>
#include <Atom/Features/PBR/Lighting/StandardLighting.azsli>
#include <Atom/Features/Shadow/DirectionalLightShadow.azsli>
#include <Atom/Features/PBR/Decals.azsli>
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;
}

@ -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"
}

@ -8,6 +8,7 @@
#include <Atom/Features/SrgSemantics.azsli>
#include <viewsrg.srgi>
#include "TerrainCommon.azsli"
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
struct VSDepthOutput
{

@ -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"
}
]
}
}

@ -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

@ -20,28 +20,39 @@
#include <Atom/RPI.Public/RPIUtils.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Public/View.h>
#include <Atom/RPI.Public/MeshDrawPacket.h>
#include <Atom/RPI.Public/AuxGeom/AuxGeomFeatureProcessorInterface.h>
#include <Atom/RPI.Public/AuxGeom/AuxGeomDraw.h>
#include <Atom/RPI.Public/Buffer/BufferSystem.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Public/Image/StreamingImagePool.h>
#include <Atom/RPI.Public/Model/Model.h>
#include <Atom/RPI.Public/Material/Material.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
#include <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
#include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
#include <Atom/RPI.Reflect/Model/ModelLodAssetCreator.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include <Atom/RPI.Reflect/Image/ImageMipChainAssetCreator.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAssetCreator.h>
#include <Atom/RHI.Reflect/InputStreamLayout.h>
#include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
#include <Atom/Feature/RenderCommon.h>
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<AZ::RPI::AssetUtils::AsyncAssetLoader>();
*m_materialAssetLoader = AZ::RPI::AssetUtils::AsyncAssetLoader::Create<AZ::RPI::MaterialAsset>(materialFilePath, 0u,
[&](AZ::Data::Asset<AZ::Data::AssetData> 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<AZ::RPI::MaterialAsset>& materialAsset = static_cast<AZ::Data::Asset<AZ::RPI::MaterialAsset>>(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<AZ::RHI::ShaderResourceGroupLayout> 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<AZ::RPI::Image> heightmapImage = m_areaData.m_heightmapImage;
m_materialInstance->SetPropertyValue(heightmapPropertyIndex, heightmapImage);
m_materialInstance->Compile();
uint32_t numIndices = static_cast<uint32_t>(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<float, 2> uvMin = { 0.0f, 0.0f };
AZStd::array<float, 2> uvMax = { 1.0f, 1.0f };
{ // Update SRG
AZStd::array<float, 2> uvMin = { 0.0f, 0.0f };
AZStd::array<float, 2> 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<float, 2> uvStep =
{
1.0f / m_areaData.m_heightmapImageWidth, 1.0f / m_areaData.m_heightmapImageHeight,
};
AZStd::array<float, 2> 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<AZ::RHI::StreamBufferView>(&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<uint8_t>(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<uint8_t>(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<uint16_t>(startIndex + 1));
m_gridIndices.emplace_back(aznumeric_cast<uint16_t>(startIndex + 2));
m_gridIndices.emplace_back(aznumeric_cast<uint16_t>(startIndex + 1));
m_gridIndices.emplace_back(aznumeric_cast<uint16_t>(startIndex + 3));
m_gridIndices.emplace_back(aznumeric_cast<uint16_t>(startIndex + 2));
patchdata.m_positions.push_back({ aznumeric_cast<float>(x) * gridSpacing, aznumeric_cast<float>(y) * gridSpacing });
patchdata.m_uvs.push_back({ aznumeric_cast<float>(x) / gridSize, aznumeric_cast<float>(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<AZ::Data::Asset<AZ::RPI::BufferAsset>> 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<uint64_t>(bufferViewDescriptor.m_elementSize) * static_cast<uint64_t>(bufferViewDescriptor.m_elementCount);
AZStd::vector<AZ::RHI::Ptr<AZ::RHI::Buffer>> 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<AZ::RPI::BufferAsset> bufferAsset;
if (creator.End(bufferAsset))
{
AZ::RHI::Ptr<AZ::RHI::Buffer> 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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint8_t*>(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<uint32_t>(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<uint32_t>(elementSize), static_cast<uint32_t>(sizeof(Vertex)));
}
AZ::Data::Asset<AZ::RPI::ModelLodAsset> 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<AZ::RPI::ModelAsset> 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;
}
}

@ -14,9 +14,11 @@
#include <LmbrCentral/Shape/ShapeComponentBus.h>
#include <Atom/RPI.Public/FeatureProcessor.h>
#include <Atom/RPI.Public/Material/Material.h>
#include <Atom/RPI.Public/Shader/Shader.h>
#include <Atom/RPI.Public/Image/StreamingImage.h>
#include <Atom/RPI.Public/MeshDrawPacket.h>
#include <Atom/RHI/ShaderResourceGroup.h>
#include <Atom/RHI/BufferPool.h>
#include <Atom/RHI/DrawPacket.h>
@ -26,6 +28,15 @@
#include <Atom/RHI/RHISystemInterface.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
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<AZ::RPI::Shader> m_shader;
AZ::RHI::ConstPtr<AZ::RHI::PipelineState> 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<float, 2> 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<VertexPosition> m_positions;
AZStd::vector<VertexUv> m_uvs;
AZStd::vector<uint16_t> m_indices;
};
void Initialize();
void InitializeTerrainPatch(uint16_t gridSize, float gridSpacing, PatchData& patchdata);
bool InitializePatchModel();
void ProcessSurfaces(const FeatureProcessor::RenderPacket& process);
AZ::Outcome<AZ::Data::Asset<AZ::RPI::BufferAsset>> 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<ShaderState, ShaderType::Count> m_shaderStates;
AZStd::unique_ptr<AZ::RPI::AssetUtils::AsyncAssetLoader> m_materialAssetLoader;
AZ::Data::Instance<AZ::RPI::Material> 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<Vertex> m_gridVertices;
AZStd::vector<uint16_t> m_gridIndices;
// System-level data related to the grid patch
AZ::RHI::Ptr<AZ::RHI::BufferPool> m_hostPool = nullptr;
AZ::RHI::Ptr<AZ::RHI::Buffer> m_indexBuffer;
AZ::RHI::Ptr<AZ::RHI::Buffer> m_vertexBuffer;
AZ::RHI::IndexBufferView m_indexBufferView;
AZ::RHI::StreamBufferView m_vertexBufferView;
AZ::Data::Instance<AZ::RPI::Model> m_patchModel;
// Per-area data
struct TerrainAreaData
@ -161,15 +135,9 @@ namespace Terrain
struct SectorData
{
AZ::Data::Instance<AZ::RPI::ShaderResourceGroup> m_srg;
AZ::Data::Instance<AZ::RPI::ShaderResourceGroup> m_srg; // Hold on to ref so it's not dropped
AZ::Aabb m_aabb;
AZStd::unique_ptr<const AZ::RHI::DrawPacket> m_drawPacket;
SectorData(const AZ::RHI::DrawPacket* drawPacket, AZ::Aabb aabb, AZ::Data::Instance<AZ::RPI::ShaderResourceGroup> srg)
: m_srg(srg)
, m_aabb(aabb)
, m_drawPacket(drawPacket)
{}
AZStd::fixed_vector<AZ::RPI::MeshDrawPacket, AZ::RPI::ModelLodAsset::LodCountMax> m_drawPackets;
};
AZStd::vector<SectorData> m_sectorData;

@ -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(

@ -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.
Loading…
Cancel
Save