2
0
Эх сурвалжийг харах

merge from development

Signed-off-by: greerdv <[email protected]>
greerdv 3 жил өмнө
parent
commit
ed763c36db
100 өөрчлөгдсөн 9687 нэмэгдсэн , 274 устгасан
  1. 1 1
      AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_Null_Render_Component_02.py
  2. 11 1
      AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py
  3. 89 31
      AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GridAdded.py
  4. 1 0
      AutomatedTesting/Gem/PythonTests/Multiplayer/TestSuite_Main.py
  5. 8 0
      AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py
  6. 6 0
      AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main_Optimized.py
  7. 59 0
      AutomatedTesting/Gem/PythonTests/Prefab/tests/prefab_notifications/PrefabNotifications_PropagationNotificationsReceived.py
  8. 36 0
      AutomatedTesting/Gem/PythonTests/Prefab/tests/prefab_notifications/PrefabNotifications_RootPrefabLoadedNotificationsReceived.py
  9. 4 4
      AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/Terrain_SupportsPhysics.py
  10. 3 0
      AutomatedTesting/TestAssets/Animation/AlphabetP/P_Freeze_Transforms.fbx
  11. 3 0
      AutomatedTesting/TestAssets/Animation/AlphabetP/P_Freeze_Transforms_DotName.fbx
  12. 3 0
      AutomatedTesting/TestAssets/Animation/AlphabetP/P_No_Freeze_Transforms.fbx
  13. 3 0
      AutomatedTesting/TestAssets/Animation/AlphabetP/P_No_Freeze_Transforms_DotName.fbx
  14. 9 0
      AutomatedTesting/TestAssets/Animation/AlphabetP/Readme.txt
  15. 18 7
      Code/Framework/AzFramework/AzFramework/Physics/HeightfieldProviderBus.h
  16. 12 12
      Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp
  17. 9 9
      Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.h
  18. 31 0
      Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicNotificationHandler.cpp
  19. 50 0
      Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicNotificationHandler.h
  20. 4 3
      Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp
  21. 2 0
      Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake
  22. 5 1
      Code/Tools/AssetProcessor/native/ui/MainWindow.cpp
  23. 54 0
      Code/Tools/SceneAPI/SceneCore/Tests/Utilities/CoordinateSystemConverterTests.cpp
  24. 1 0
      Code/Tools/SceneAPI/SceneCore/scenecore_testing_files.cmake
  25. 1 1
      Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset
  26. 10 3
      Gems/Atom/RPI/Code/Source/RPI.Public/Buffer/BufferSystem.cpp
  27. 4 1
      Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp
  28. 92 85
      Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp
  29. 4 6
      Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.h
  30. 1 1
      Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialSystemComponent.cpp
  31. 104 54
      Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp
  32. 3 0
      Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h
  33. 0 1
      Gems/Blast/Code/CMakeLists.txt
  34. 10 8
      Gems/EMotionFX/Code/EMotionFX/Pipeline/RCExt/Motion/MotionDataBuilder.cpp
  35. 0 40
      Gems/EMotionFX/Code/Tests/CoordinateSystemConverterTests.cpp
  36. 152 0
      Gems/Meshlets/ASV_GPU_and_CPU_Demo/CMakeLists.txt
  37. 16 0
      Gems/Meshlets/ASV_GPU_and_CPU_Demo/DebugShaderMaterial_01.material
  38. 63 0
      Gems/Meshlets/ASV_GPU_and_CPU_Demo/DebugShaderPBR.materialtype
  39. 138 0
      Gems/Meshlets/ASV_GPU_and_CPU_Demo/DebugShaderPBR_ForwardPass.azsl
  40. 46 0
      Gems/Meshlets/ASV_GPU_and_CPU_Demo/DebugShaderPBR_ForwardPass.shader
  41. 419 0
      Gems/Meshlets/ASV_GPU_and_CPU_Demo/LowEndPipeline.pass
  42. 550 0
      Gems/Meshlets/ASV_GPU_and_CPU_Demo/MainPipeline.pass
  43. 517 0
      Gems/Meshlets/ASV_GPU_and_CPU_Demo/MeshletsExampleComponent.cpp
  44. 147 0
      Gems/Meshlets/ASV_GPU_and_CPU_Demo/MeshletsExampleComponent.h
  45. 190 0
      Gems/Meshlets/ASV_GPU_and_CPU_Demo/Readme.txt
  46. 200 0
      Gems/Meshlets/ASV_GPU_and_CPU_Demo/atomsampleviewergem_private_files.cmake
  47. 23 0
      Gems/Meshlets/ASV_GPU_and_CPU_Demo/enabled_gems.cmake
  48. 31 0
      Gems/Meshlets/Assets/Passes/MeshletsCompute.pass
  49. 50 0
      Gems/Meshlets/Assets/Passes/MeshletsParent.pass
  50. 16 0
      Gems/Meshlets/Assets/Passes/MeshletsPassRequest.azasset
  51. 21 0
      Gems/Meshlets/Assets/Passes/MeshletsPassTemplates.azasset
  52. 41 0
      Gems/Meshlets/Assets/Passes/MeshletsRender.pass
  53. 246 0
      Gems/Meshlets/Assets/Shaders/MeshletsCompute.azsl
  54. 14 0
      Gems/Meshlets/Assets/Shaders/MeshletsCompute.shader
  55. 129 0
      Gems/Meshlets/Assets/Shaders/MeshletsDebugRenderShader.azsl
  56. 42 0
      Gems/Meshlets/Assets/Shaders/MeshletsDebugRenderShader.shader
  57. 102 0
      Gems/Meshlets/Assets/Shaders/MeshletsPerObjectRenderSrg.azsli
  58. 21 0
      Gems/Meshlets/CMakeLists.txt
  59. 179 0
      Gems/Meshlets/Code/CMakeLists.txt
  60. 42 0
      Gems/Meshlets/Code/Include/Meshlets/MeshletsBus.h
  61. 525 0
      Gems/Meshlets/Code/Source/Meshlets/MeshletsAssets.cpp
  62. 137 0
      Gems/Meshlets/Code/Source/Meshlets/MeshletsAssets.h
  63. 181 0
      Gems/Meshlets/Code/Source/Meshlets/MeshletsData.h
  64. 68 0
      Gems/Meshlets/Code/Source/Meshlets/MeshletsDispatchItem.cpp
  65. 57 0
      Gems/Meshlets/Code/Source/Meshlets/MeshletsDispatchItem.h
  66. 439 0
      Gems/Meshlets/Code/Source/Meshlets/MeshletsFeatureProcessor.cpp
  67. 135 0
      Gems/Meshlets/Code/Source/Meshlets/MeshletsFeatureProcessor.h
  68. 800 0
      Gems/Meshlets/Code/Source/Meshlets/MeshletsRenderObject.cpp
  69. 192 0
      Gems/Meshlets/Code/Source/Meshlets/MeshletsRenderObject.h
  70. 281 0
      Gems/Meshlets/Code/Source/Meshlets/MeshletsRenderPass.cpp
  71. 98 0
      Gems/Meshlets/Code/Source/Meshlets/MeshletsRenderPass.h
  72. 233 0
      Gems/Meshlets/Code/Source/Meshlets/MeshletsUtilities.cpp
  73. 98 0
      Gems/Meshlets/Code/Source/Meshlets/MeshletsUtilities.h
  74. 162 0
      Gems/Meshlets/Code/Source/Meshlets/MultiDispatchComputePass.cpp
  75. 78 0
      Gems/Meshlets/Code/Source/Meshlets/MultiDispatchComputePass.h
  76. 213 0
      Gems/Meshlets/Code/Source/Meshlets/SharedBuffer.cpp
  77. 148 0
      Gems/Meshlets/Code/Source/Meshlets/SharedBuffer.h
  78. 131 0
      Gems/Meshlets/Code/Source/Meshlets/SharedBufferInterface.h
  79. 50 0
      Gems/Meshlets/Code/Source/MeshletsEditorModule.cpp
  80. 66 0
      Gems/Meshlets/Code/Source/MeshletsEditorSystemComponent.cpp
  81. 45 0
      Gems/Meshlets/Code/Source/MeshletsEditorSystemComponent.h
  82. 27 0
      Gems/Meshlets/Code/Source/MeshletsModule.cpp
  83. 48 0
      Gems/Meshlets/Code/Source/MeshletsModuleInterface.h
  84. 131 0
      Gems/Meshlets/Code/Source/MeshletsSystemComponent.cpp
  85. 67 0
      Gems/Meshlets/Code/Source/MeshletsSystemComponent.h
  86. 12 0
      Gems/Meshlets/Code/Tests/MeshletsEditorTest.cpp
  87. 11 0
      Gems/Meshlets/Code/Tests/MeshletsTest.cpp
  88. 12 0
      Gems/Meshlets/Code/meshlets_editor_files.cmake
  89. 11 0
      Gems/Meshlets/Code/meshlets_editor_shared_files.cmake
  90. 11 0
      Gems/Meshlets/Code/meshlets_editor_tests_files.cmake
  91. 47 0
      Gems/Meshlets/Code/meshlets_files.cmake
  92. 11 0
      Gems/Meshlets/Code/meshlets_shared_files.cmake
  93. 11 0
      Gems/Meshlets/Code/meshlets_tests_files.cmake
  94. 10 0
      Gems/Meshlets/External/Lib/meshoptimizer.txt
  95. 1050 0
      Gems/Meshlets/External/meshoptimizer.h
  96. 21 0
      Gems/Meshlets/gem.json
  97. 3 0
      Gems/Meshlets/preview.png
  98. 0 1
      Gems/PhysX/Code/CMakeLists.txt
  99. 19 2
      Gems/PhysX/Code/Editor/DebugDraw.cpp
  100. 2 2
      Gems/PhysX/Code/Include/PhysX/SystemComponentBus.h

+ 1 - 1
AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_Null_Render_Component_02.py

@@ -16,7 +16,7 @@ class TestAutomation(EditorTestSuite):
     @pytest.mark.test_case_id("C32078115")
     class AtomEditorComponents_GlobalSkylightIBLAdded(EditorBatchedTest):
         from Atom.tests import hydra_AtomEditorComponents_GlobalSkylightIBLAdded as test_module
-        
+
     @pytest.mark.test_case_id("C32078122")
     class AtomEditorComponents_GridAdded(EditorBatchedTest):
         from Atom.tests import hydra_AtomEditorComponents_GridAdded as test_module

+ 11 - 1
AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py

@@ -515,14 +515,24 @@ class AtomComponentProperties:
         """
         Grid component properties.
           - 'Grid Size': The size of the grid, default value is 32
-          - 'Secondary Grid Spacing': The spacing value for the secondary grid, i.e. 1.0
+          - 'Axis Color': Sets color of the grid axis using azlmbr.math.Color tuple, default value is 0,0,255 (blue)
+          - 'Primary Grid Spacing': Amount of space between grid lines, default value is 1.0
+          - 'Primary Color': Sets color of the primary grid lines using azlmbr.math.Color tuple,
+             default value is 64,64,64 (dark grey)
+          - 'Secondary Grid Spacing': Amount of space between sub-grid lines, default value is 0.25
+          - 'Secondary Color': Sets color of the secondary grid lines using azlmbr.math.Color tuple,
+             default value is 128,128,128 (light grey)
         :param property: From the last element of the property tree path. Default 'name' for component name string.
         :return: Full property path OR component name if no property specified.
         """
         properties = {
             'name': 'Grid',
             'Grid Size': 'Controller|Configuration|Grid Size',
+            'Axis Color': 'Controller|Configuration|Axis Color',
+            'Primary Grid Spacing': 'Controller|Configuration|Primary Grid Spacing',
+            'Primary Color': 'Controller|Configuration|Primary Color',
             'Secondary Grid Spacing': 'Controller|Configuration|Secondary Grid Spacing',
+            'Secondary Color': 'Controller|Configuration|Secondary Color',
         }
         return properties[property]
 

+ 89 - 31
AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GridAdded.py

@@ -5,43 +5,59 @@ For complete copyright and license terms please see the LICENSE at the root of t
 SPDX-License-Identifier: Apache-2.0 OR MIT
 """
 
+
 class Tests:
     creation_undo = (
         "UNDO Entity creation success",
-        "UNDO Entity creation failed")
+        "P0: UNDO Entity creation failed")
     creation_redo = (
         "REDO Entity creation success",
-        "REDO Entity creation failed")
+        "P0: REDO Entity creation failed")
     grid_entity_creation = (
         "Grid Entity successfully created",
-        "Grid Entity failed to be created")
+        "P0: Grid Entity failed to be created")
     grid_component_added = (
         "Entity has a Grid component",
-        "Entity failed to find Grid component")
+        "P0: Entity failed to find Grid component")
     grid_size = (
         "Grid Size value set successfully",
-        "Grid Size value could not be set")
+        "P0: Grid Size value could not be set")
     enter_game_mode = (
         "Entered game mode",
-        "Failed to enter game mode")
+        "P0: Failed to enter game mode")
     exit_game_mode = (
         "Exited game mode",
-        "Couldn't exit game mode")
+        "P0: Couldn't exit game mode")
     is_visible = (
         "Entity is visible",
-        "Entity was not visible")
+        "P0: Entity was not visible")
     is_hidden = (
         "Entity is hidden",
-        "Entity was not hidden")
+        "P0: Entity was not hidden")
     entity_deleted = (
         "Entity deleted",
-        "Entity was not deleted")
+        "P0: Entity was not deleted")
     deletion_undo = (
         "UNDO deletion success",
-        "UNDO deletion failed")
+        "P0: UNDO deletion failed")
     deletion_redo = (
         "REDO deletion success",
-        "REDO deletion failed")
+        "P0: REDO deletion failed")
+    axis_color = (
+        "Axis Color value set successfully",
+        "P1: Axis Color value could not be set")
+    primary_grid_spacing = (
+        "Primary Grid Spacing value set successfully",
+        "P1: Primary Grid Spacing value could not be set")
+    primary_color = (
+        "Primary Color value set successfully",
+        "P1: Primary Color value could not be set")
+    secondary_grid_spacing = (
+        "Secondary Grid Spacing value set successfully",
+        "P1: Secondary Grid Spacing value could not be set")
+    secondary_color = (
+        "Secondary Color value set successfully",
+        "P1: Secondary Color value could not be set")
 
 
 def AtomEditorComponents_Grid_AddedToEntity():
@@ -51,10 +67,11 @@ def AtomEditorComponents_Grid_AddedToEntity():
 
     Test setup:
     - Wait for Editor idle loop.
-    - Open the "Base" level.
+    - Open the "base_empty" level.
 
     Expected Behavior:
     The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components.
+    Property values for the component can be set.
     Creation and deletion undo/redo should also work.
 
     Test Steps:
@@ -62,21 +79,25 @@ def AtomEditorComponents_Grid_AddedToEntity():
     2) Add a Grid component to Grid entity.
     3) UNDO the entity creation and component addition.
     4) REDO the entity creation and component addition.
-    5) Grid Size changed.
-    6) Enter/Exit game mode.
-    7) Test IsHidden.
-    8) Test IsVisible.
-    9) Delete Grid entity.
-    10) UNDO deletion.
-    11) REDO deletion.
-    12) Look for errors.
+    5) Grid Size property value updated.
+    6) Axis Color property value updated.
+    7) Primary Grid Spacing property value updated.
+    8) Primary Color property value updated.
+    9) Secondary Grid Spacing property value updated.
+    10) Secondary Color property value updated.
+    11) Enter/Exit game mode.
+    12) Test IsHidden.
+    13) Test IsVisible.
+    14) Delete Grid entity.
+    15) UNDO deletion.
+    16) REDO deletion.
+    17) Look for errors.
 
     :return: None
     """
 
-    import os
-
     import azlmbr.legacy.general as general
+    import azlmbr.math as math
 
     from editor_python_test_tools.editor_entity_utils import EditorEntity
     from editor_python_test_tools.utils import Report, Tracer, TestHelper
@@ -123,42 +144,79 @@ def AtomEditorComponents_Grid_AddedToEntity():
         general.idle_wait_frames(1)
         Report.result(Tests.creation_redo, grid_entity.exists())
 
-        # 5. Grid Size changed
+        # 5. Grid Size property value updated.
         grid_component.set_component_property_value(
             AtomComponentProperties.grid('Grid Size'), value=64)
         current_grid_size = grid_component.get_component_property_value(
             AtomComponentProperties.grid('Grid Size'))
         Report.result(Tests.grid_size, current_grid_size == 64)
 
-        # 6. Enter/Exit game mode.
+        # 6. Axis Color property value updated.
+        green_color_value = math.Color(13.0, 255.0, 0.0, 1.0)
+        grid_component.set_component_property_value(
+            AtomComponentProperties.grid('Axis Color'), value=green_color_value)
+        Report.result(Tests.axis_color,
+                      grid_component.get_component_property_value(
+                          AtomComponentProperties.grid('Axis Color')) == green_color_value)
+
+        # 7. Primary Grid Spacing property value updated.
+        grid_component.set_component_property_value(
+            AtomComponentProperties.grid('Primary Grid Spacing'), value=0.5)
+        Report.result(Tests.primary_grid_spacing,
+                      grid_component.get_component_property_value(
+                          AtomComponentProperties.grid('Primary Grid Spacing')) == 0.5)
+
+        # 8. Primary Color property value updated.
+        brown_color_value = math.Color(129.0, 96.0, 0.0, 1.0)
+        grid_component.set_component_property_value(
+            AtomComponentProperties.grid('Primary Color'), value=brown_color_value)
+        Report.result(Tests.primary_color,
+                      grid_component.get_component_property_value(
+                          AtomComponentProperties.grid('Primary Color')) == brown_color_value)
+
+        # 9. Secondary Grid Spacing property value updated.
+        grid_component.set_component_property_value(AtomComponentProperties.grid('Secondary Grid Spacing'), value=0.75)
+        Report.result(Tests.secondary_grid_spacing,
+                      grid_component.get_component_property_value(
+                          AtomComponentProperties.grid('Secondary Grid Spacing')) == 0.75)
+
+        # 10. Secondary Color property value updated.
+        blue_color_value = math.Color(0.0, 35.0, 161.0, 1.0)
+        grid_component.set_component_property_value(
+            AtomComponentProperties.grid('Secondary Color'), value=blue_color_value)
+        Report.result(Tests.secondary_color,
+                      grid_component.get_component_property_value(
+                          AtomComponentProperties.grid('Secondary Color')) == blue_color_value)
+
+        # 11. Enter/Exit game mode.
         TestHelper.enter_game_mode(Tests.enter_game_mode)
         general.idle_wait_frames(1)
         TestHelper.exit_game_mode(Tests.exit_game_mode)
 
-        # 7. Test IsHidden.
+        # 12. Test IsHidden.
         grid_entity.set_visibility_state(False)
         Report.result(Tests.is_hidden, grid_entity.is_hidden() is True)
 
-        # 8. Test IsVisible.
+        # 13. Test IsVisible.
         grid_entity.set_visibility_state(True)
         general.idle_wait_frames(1)
         Report.result(Tests.is_visible, grid_entity.is_visible() is True)
 
-        # 9. Delete Grid entity.
+        # 14. Delete Grid entity.
         grid_entity.delete()
         Report.result(Tests.entity_deleted, not grid_entity.exists())
 
-        # 10. UNDO deletion.
+        # 15. UNDO deletion.
         general.undo()
         general.idle_wait_frames(1)
         Report.result(Tests.deletion_undo, grid_entity.exists())
 
-        # 11. REDO deletion.
+        # 16. REDO deletion.
         general.redo()
         general.idle_wait_frames(1)
         Report.result(Tests.deletion_redo, not grid_entity.exists())
 
-        # 12. Look for errors or asserts.
+        # 17. Look for errors or asserts.
         TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
         for error_info in error_tracer.errors:
             Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")

+ 1 - 0
AutomatedTesting/Gem/PythonTests/Multiplayer/TestSuite_Main.py

@@ -33,6 +33,7 @@ class TestAutomation(EditorTestSuite):
         def setup(cls, instance, request, workspace, editor, editor_test_results, launcher_platform):
             save_multiplayer_level_cache_folder_artifact(workspace, "autocomponent_networkinput")
 
+    @pytest.mark.xfail(reason="GHI #9869: Test periodically fails")
     class test_Multiplayer_AutoComponent_RPC(EditorSingleTest):
         from .tests import Multiplayer_AutoComponent_RPC as test_module
 

+ 8 - 0
AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py

@@ -89,3 +89,11 @@ class TestAutomation(TestAutomationBase):
     def test_DeleteEntity_UnderLevelPrefab(self, request, workspace, editor, launcher_platform):
         from Prefab.tests.delete_entity import DeleteEntity_UnderLevelPrefab as test_module
         self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False)
+
+    def test_PrefabNotifications_PropagationNotificationsReceived(self, request, workspace, editor, launcher_platform):
+        from .tests.prefab_notifications import PrefabNotifications_PropagationNotificationsReceived as test_module
+        self._run_prefab_test(request, workspace, editor, test_module)
+
+    def test_PrefabNotifications_RootPrefabLoadedNotificationsReceived(self, request, workspace, editor, launcher_platform):
+        from .tests.prefab_notifications import PrefabNotifications_RootPrefabLoadedNotificationsReceived as test_module
+        self._run_prefab_test(request, workspace, editor, test_module)

+ 6 - 0
AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main_Optimized.py

@@ -66,6 +66,12 @@ class TestAutomationNoAutoTestMode(EditorTestSuite):
     class test_DuplicatePrefab_ContainingASingleEntity(EditorSharedTest):
         from .tests.duplicate_prefab import DuplicatePrefab_ContainingASingleEntity as test_module
 
+    class test_PrefabNotifications_PropagationNotificationsReceived(EditorSharedTest):
+        from .tests.prefab_notifications import PrefabNotifications_PropagationNotificationsReceived as test_module
+
+    class test_PrefabNotifications_RootPrefabLoadedNotificationsReceived(EditorSharedTest):
+        from .tests.prefab_notifications import PrefabNotifications_RootPrefabLoadedNotificationsReceived as test_module
+
     class test_SC_Spawnables_SimpleSpawnAndDespawn(EditorSharedTest):
         from .tests.spawnables import SC_Spawnables_SimpleSpawnAndDespawn as test_module
 

+ 59 - 0
AutomatedTesting/Gem/PythonTests/Prefab/tests/prefab_notifications/PrefabNotifications_PropagationNotificationsReceived.py

@@ -0,0 +1,59 @@
+"""
+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
+"""
+
+propagationBegun = False
+propagationEnded = False
+
+def PrefabNotifications_PropagationNotificationsReceived():
+
+    from pathlib import Path
+
+    import azlmbr.prefab as prefab
+
+    from editor_python_test_tools.editor_entity_utils import EditorEntity
+    from editor_python_test_tools.prefab_utils import Prefab
+    import Prefab.tests.PrefabTestUtils as prefab_test_utils
+
+    CAR_PREFAB_FILE_NAME = Path(__file__).stem + 'car_prefab'
+
+    prefab_test_utils.open_base_tests_level()
+
+    # Creates a new entity at the root level
+    car_entity = EditorEntity.create_editor_entity("Car")
+    car_prefab_entities = [car_entity]
+
+    # Creates a prefab from the new entity
+    _, car = Prefab.create_prefab(
+        car_prefab_entities, CAR_PREFAB_FILE_NAME)
+
+    # Connects PrefabPublicNotificationBusHandler and add callbacks for 'OnPrefabInstancePropagationBegin'
+    # and 'OnPrefabInstancePropagationEnd'
+    def OnPrefabInstancePropagationBegin(parameters):
+        global propagationBegun
+        propagationBegun = True
+
+    def OnPrefabInstancePropagationEnd(parameters):
+        global propagationEnded
+        propagationEnded = True
+
+    handler = prefab.PrefabPublicNotificationBusHandler()
+    handler.connect()
+    handler.add_callback('OnPrefabInstancePropagationBegin', OnPrefabInstancePropagationBegin)
+    handler.add_callback('OnPrefabInstancePropagationEnd', OnPrefabInstancePropagationEnd)
+
+    # Duplicates the prefab instance to trigger callbacks
+    Prefab.duplicate_prefabs([car])
+
+    handler.disconnect()
+
+    assert propagationBegun, "Notification 'PrefabPublicNotifications::OnPrefabInstancePropagationBegin' is not sent."
+    assert propagationEnded, "Notification 'PrefabPublicNotifications::OnPrefabInstancePropagationEnd' is not sent."
+
+
+if __name__ == "__main__":
+    from editor_python_test_tools.utils import Report
+    Report.start_test(PrefabNotifications_PropagationNotificationsReceived)

+ 36 - 0
AutomatedTesting/Gem/PythonTests/Prefab/tests/prefab_notifications/PrefabNotifications_RootPrefabLoadedNotificationsReceived.py

@@ -0,0 +1,36 @@
+"""
+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
+"""
+
+rootPrefabLoaded = False
+
+def PrefabNotifications_RootPrefabLoadedNotificationsReceived():
+
+    from pathlib import Path
+
+    import azlmbr.prefab as prefab
+
+    import Prefab.tests.PrefabTestUtils as prefab_test_utils
+
+    # Connects PrefabPublicNotificationBusHandler and add callbacks for 'OnRootPrefabInstanceLoaded'
+    def OnRootPrefabInstanceLoaded(parameters):
+        global rootPrefabLoaded
+        rootPrefabLoaded = True
+
+    handler = prefab.PrefabPublicNotificationBusHandler()
+    handler.connect()
+    handler.add_callback('OnRootPrefabInstanceLoaded', OnRootPrefabInstanceLoaded)
+
+    prefab_test_utils.open_base_tests_level()
+
+    handler.disconnect()
+
+    assert rootPrefabLoaded, "Notification 'PrefabPublicNotifications::OnRootPrefabInstanceLoaded' is not sent."
+
+
+if __name__ == "__main__":
+    from editor_python_test_tools.utils import Report
+    Report.start_test(PrefabNotifications_RootPrefabLoadedNotificationsReceived)

+ 4 - 4
AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/Terrain_SupportsPhysics.py

@@ -129,8 +129,8 @@ def Terrain_SupportsPhysics():
 
         general.idle_wait_frames(1)
 
-        # 10) Enter game mode and test if the ball detects the heightfield collision within 3 seconds
-        TIMEOUT = 3.0
+        # 10) Enter game mode and test if the ball detects the heightfield collision within 5 seconds
+        TIMEOUT = 5.0
 
         class Collider:
             id = general.find_game_entity("Ball")
@@ -138,7 +138,7 @@ def Terrain_SupportsPhysics():
 
         terrain_id = general.find_game_entity("TestEntity1")
  
-        def on_collision_begin(args):
+        def on_collision_persist(args):
             other_id = args[0]
             if other_id.Equal(terrain_id):
                 Report.info("Ball intersected with heightfield")
@@ -146,7 +146,7 @@ def Terrain_SupportsPhysics():
 
         handler = azlmbr.physics.CollisionNotificationBusHandler()
         handler.connect(Collider.id)
-        handler.add_callback("OnCollisionBegin", on_collision_begin)
+        handler.add_callback("OnCollisionPersist", on_collision_persist)
 
         helper.wait_for_condition(lambda: Collider.touched_ground, TIMEOUT)
         Report.result(Tests.test_collision, Collider.touched_ground)

+ 3 - 0
AutomatedTesting/TestAssets/Animation/AlphabetP/P_Freeze_Transforms.fbx

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4bb27c7e024f4eb0fc82fbc7f0d5ae259937359f80baefe21d970881ee649e0b
+size 95244

+ 3 - 0
AutomatedTesting/TestAssets/Animation/AlphabetP/P_Freeze_Transforms_DotName.fbx

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7b451ac43a5d182c6e7e4fa8736de0a3a806273619cb1c0581a21396fd0dfbcb
+size 81452

+ 3 - 0
AutomatedTesting/TestAssets/Animation/AlphabetP/P_No_Freeze_Transforms.fbx

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f326f68122d53f81408846bfb143d3138638161956885e7db2a4775ae044992f
+size 94988

+ 3 - 0
AutomatedTesting/TestAssets/Animation/AlphabetP/P_No_Freeze_Transforms_DotName.fbx

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b6a6803217662c1ecbd78152011933ec7ad51a56a1064cef8b346c6bdff101ac
+size 81436

+ 9 - 0
AutomatedTesting/TestAssets/Animation/AlphabetP/Readme.txt

@@ -0,0 +1,9 @@
+Alphabet Letter P asset test cases:
+
+Freeze transform vs Non Freeze Transform
+Description: The non freeze transform version has an arbitrary transform on the root joint, while the freeze transform version has identical transform on the root joint.
+Expectation: A straight up letter P that animated with slight mesh deformation. Both asset should be identical to each other, including skeleton, mesh, skinning and animation.
+
+Dot name vs non dot name
+Description: The dot name version has a dot character in the joint name. SceneAPI will recongnize the dot character as an invalid character and translate it to a valid character. (e.g xxx.xx to xxx_xx)
+Expectation: The dot named version looks identical to the non dot named version. No error in AP is detected, but a warning in the AP for the dot named version suggesting that invalid name has been renamed to valid name.

+ 18 - 7
Code/Framework/AzFramework/AzFramework/Physics/HeightfieldProviderBus.h

@@ -42,12 +42,12 @@ namespace Physics
 
         AZ_TYPE_INFO(HeightMaterialPoint, "{DF167ED4-24E6-4F7B-8AB7-42622F7DBAD3}");
         float m_height{ 0.0f }; //!< Holds the height of this point in the heightfield relative to the heightfield entity location.
-        QuadMeshType m_quadMeshType{ QuadMeshType::SubdivideUpperLeftToBottomRight }; //!< By default, create two triangles like this |\|, where this point is in the upper left corner.
+        QuadMeshType m_quadMeshType{ QuadMeshType::Hole }; //!< By default, create an empty point.
         uint8_t m_materialIndex{ 0 }; //!< The surface material index for the upper left corner of this quad.
         uint16_t m_padding{ 0 }; //!< available for future use.
     };
 
-    using UpdateHeightfieldSampleFunction = AZStd::function<void(int32_t, int32_t, const Physics::HeightMaterialPoint&)>;
+    using UpdateHeightfieldSampleFunction = AZStd::function<void(size_t column, size_t row, const Physics::HeightMaterialPoint& point)>;
 
     //! An interface to provide heightfield values.
     //! This EBus supports multiple concurrent requests from different threads.
@@ -66,15 +66,15 @@ namespace Physics
         //! Returns the height field gridsize.
         //! @param numColumns contains the size of the grid in the x direction.
         //! @param numRows contains the size of the grid in the y direction.
-        virtual void GetHeightfieldGridSize(int32_t& numColumns, int32_t& numRows) const = 0;
+        virtual void GetHeightfieldGridSize(size_t& numColumns, size_t& numRows) const = 0;
 
         //! Returns the height field gridsize columns.
         //! @return the size of the grid in the x direction.
-        virtual int32_t GetHeightfieldGridColumns() const = 0;
+        virtual size_t GetHeightfieldGridColumns() const = 0;
 
         //! Returns the height field gridsize rows.
         //! @return the size of the grid in the y direction.
-        virtual int32_t GetHeightfieldGridRows() const = 0;
+        virtual size_t GetHeightfieldGridRows() const = 0;
 
         //! Returns the height field min and max height bounds.
         //! @param minHeightBounds contains the minimum height that the heightfield can contain.
@@ -111,8 +111,19 @@ namespace Physics
         //! @return the rows*columns vector of the heights and materials.
         virtual AZStd::vector<Physics::HeightMaterialPoint> GetHeightsAndMaterials() const = 0;
 
-        //! Updates the list of heights and materials within the region. Pass Null region to update the entire list.
-        virtual void UpdateHeightsAndMaterials(const UpdateHeightfieldSampleFunction& updateHeightsMaterialsCallback, const AZ::Aabb& region) const = 0;
+        //! Return the specific heightfield row/column data that exists inside a given AABB region.
+        //! @param region The input region to get row/column data for
+        //! @param startColumn [out] The starting heightfield column index for that region
+        //! @param startRow [out] The starting heightfield row index for that region
+        //! @param numColumns [out] The number of columns that exist within the region
+        //! @param numRows [out] The number of rows that exist within the region
+        virtual void GetHeightfieldIndicesFromRegion(
+            const AZ::Aabb& region, size_t& startColumn, size_t& startRow, size_t& numColumns, size_t& numRows) const = 0;
+
+        //! Updates the list of heights and materials within the region.
+        virtual void UpdateHeightsAndMaterials(
+            const UpdateHeightfieldSampleFunction& updateHeightsMaterialsCallback,
+            size_t startColumn, size_t startRow, size_t numColumns, size_t numRows) const = 0;
     };
 
     using HeightfieldProviderRequestsBus = AZ::EBus<HeightfieldProviderRequests>;

+ 12 - 12
Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp

@@ -388,17 +388,17 @@ namespace Physics
         m_gridResolution = gridResolution;
     }
 
-    int32_t HeightfieldShapeConfiguration::GetNumColumnVertices() const
+    size_t HeightfieldShapeConfiguration::GetNumColumnVertices() const
     {
         return m_numColumns;
     }
 
-    void HeightfieldShapeConfiguration::SetNumColumnVertices(int32_t numColumns)
+    void HeightfieldShapeConfiguration::SetNumColumnVertices(size_t numColumns)
     {
         AZ_Assert(
             (numColumns == 0) || (numColumns >= 2),
             "A non-empty heightfield must have at least 2 column vertices to define 1 square. Num columns provided: %d", numColumns);
-        m_numColumns = numColumns;
+        m_numColumns = aznumeric_cast<uint32_t>(numColumns);
 
         if (m_numColumns < 2)
         {
@@ -406,17 +406,17 @@ namespace Physics
         }
     }
 
-    int32_t HeightfieldShapeConfiguration::GetNumRowVertices() const
+    size_t HeightfieldShapeConfiguration::GetNumRowVertices() const
     {
         return m_numRows;
     }
 
-    void HeightfieldShapeConfiguration::SetNumRowVertices(int32_t numRows)
+    void HeightfieldShapeConfiguration::SetNumRowVertices(size_t numRows)
     {
         AZ_Assert(
             (numRows == 0) || (numRows >= 2),
             "A non-empty heightfield must have at least 2 row vertices to define 1 square. Num rows provided: %d", numRows);
-        m_numRows = numRows;
+        m_numRows = aznumeric_cast<uint32_t>(numRows);
 
         if (m_numRows < 2)
         {
@@ -424,16 +424,16 @@ namespace Physics
         }
     }
 
-    int32_t HeightfieldShapeConfiguration::GetNumColumnSquares() const
+    size_t HeightfieldShapeConfiguration::GetNumColumnSquares() const
     {
         // If we have N vertices, we have N - 1 squares ( ex: *--*--* is 3 vertices but 2 squares)
-        return AZStd::max(0, m_numColumns - 1);
+        return (m_numColumns > 1) ? m_numColumns - 1 : 0;
     }
 
-    int32_t HeightfieldShapeConfiguration::GetNumRowSquares() const
+    size_t HeightfieldShapeConfiguration::GetNumRowSquares() const
     {
         // If we have N vertices, we have N - 1 squares ( ex: *--*--* is 3 vertices but 2 squares)
-        return AZStd::max(0, m_numRows - 1);
+        return (m_numRows > 1) ? m_numRows - 1 : 0;
     }
 
     const AZStd::vector<Physics::HeightMaterialPoint>& HeightfieldShapeConfiguration::GetSamples() const
@@ -441,9 +441,9 @@ namespace Physics
         return m_samples;
     }
 
-    void HeightfieldShapeConfiguration::ModifySample(int32_t row, int32_t column, const Physics::HeightMaterialPoint& point)
+    void HeightfieldShapeConfiguration::ModifySample(size_t column, size_t row, const Physics::HeightMaterialPoint& point)
     {
-        const int32_t index = row * m_numColumns + column;
+        const size_t index = row * m_numColumns + column;
         if (row < m_numRows && column < m_numColumns && index < m_samples.size())
         {
             m_samples[index] = point;

+ 9 - 9
Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.h

@@ -233,14 +233,14 @@ namespace Physics
         void SetCachedNativeHeightfield(void* cachedNativeHeightfield);
         const AZ::Vector2& GetGridResolution() const;
         void SetGridResolution(const AZ::Vector2& gridSpacing);
-        int32_t GetNumColumnVertices() const;
-        int32_t GetNumColumnSquares() const;
-        void SetNumColumnVertices(int32_t numColumns);
-        int32_t GetNumRowVertices() const;
-        int32_t GetNumRowSquares() const;
-        void SetNumRowVertices(int32_t numRows);
+        size_t GetNumColumnVertices() const;
+        size_t GetNumColumnSquares() const;
+        void SetNumColumnVertices(size_t numColumns);
+        size_t GetNumRowVertices() const;
+        size_t GetNumRowSquares() const;
+        void SetNumRowVertices(size_t numRows);
         const AZStd::vector<Physics::HeightMaterialPoint>& GetSamples() const;
-        void ModifySample(int32_t row, int32_t column, const Physics::HeightMaterialPoint& point);
+        void ModifySample(size_t column, size_t row, const Physics::HeightMaterialPoint& point);
         void SetSamples(const AZStd::vector<Physics::HeightMaterialPoint>& samples);
         float GetMinHeightBounds() const;
         void SetMinHeightBounds(float minBounds);
@@ -251,9 +251,9 @@ namespace Physics
         //! The number of meters between each heightfield sample in x and y.
         AZ::Vector2 m_gridResolution{ 1.0f };
         //! The number of columns in the heightfield sample grid.
-        int32_t m_numColumns{ 0 };
+        uint32_t m_numColumns{ 0 };
         //! The number of rows in the heightfield sample grid.
-        int32_t m_numRows{ 0 };
+        uint32_t m_numRows{ 0 };
         //! The minimum and maximum heights that can be used by this heightfield.
         //! This can be used by the physics system to choose a more optimal heightfield data type internally (ex: int16, uint8)
         float m_minHeightBounds{AZStd::numeric_limits<float>::lowest()};

+ 31 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicNotificationHandler.cpp

@@ -0,0 +1,31 @@
+/*
+ * 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 <AzToolsFramework/Prefab/PrefabPublicNotificationHandler.h>
+
+namespace AzToolsFramework
+{
+    namespace Prefab
+    {
+        void PrefabPublicNotificationHandler::Reflect(AZ::ReflectContext* context)
+        {
+            if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
+            {
+                behaviorContext->EBus<PrefabPublicNotificationBus>("PrefabPublicNotificationBus")
+                    ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
+                    ->Attribute(AZ::Script::Attributes::Category, "Prefab")
+                    ->Attribute(AZ::Script::Attributes::Module, "prefab")
+                    ->Handler<PrefabPublicNotificationHandler>()
+                    ->Event("OnPrefabInstancePropagationBegin", &PrefabPublicNotifications::OnPrefabInstancePropagationBegin)
+                    ->Event("OnPrefabInstancePropagationEnd", &PrefabPublicNotifications::OnPrefabInstancePropagationEnd)
+                    ->Event("OnRootPrefabInstanceLoaded", &PrefabPublicNotifications::OnRootPrefabInstanceLoaded)
+                    ;
+            }
+        }
+    } // namespace Prefab
+} // namespace AzToolsFramework

+ 50 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicNotificationHandler.h

@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/RTTI/BehaviorContext.h>
+#include <AzCore/RTTI/ReflectContext.h>
+
+#include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h>
+
+namespace AzToolsFramework
+{
+    namespace Prefab
+    {
+        class PrefabPublicNotificationHandler final
+            : public PrefabPublicNotificationBus::Handler
+            , public AZ::BehaviorEBusHandler
+        {
+        public:
+
+            AZ_EBUS_BEHAVIOR_BINDER(PrefabPublicNotificationHandler, "{F6F8C610-F780-45FA-8DC2-742E3FA427B5}", AZ::SystemAllocator,
+                OnPrefabInstancePropagationBegin,
+                OnPrefabInstancePropagationEnd,
+                OnRootPrefabInstanceLoaded);
+
+            static void Reflect(AZ::ReflectContext* context);
+
+            void OnPrefabInstancePropagationBegin() override
+            {
+                Call(FN_OnPrefabInstancePropagationBegin);
+            }
+
+            void OnPrefabInstancePropagationEnd() override
+            {
+                Call(FN_OnPrefabInstancePropagationEnd);
+            }
+
+            void OnRootPrefabInstanceLoaded() override
+            {
+                Call(FN_OnRootPrefabInstanceLoaded);
+            }
+        };
+    } // namespace Prefab
+} // namespace AzToolsFramework

+ 4 - 3
Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp

@@ -19,6 +19,7 @@
 #include <AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.h>
 #include <AzToolsFramework/Prefab/Spawnable/PrefabConversionPipeline.h>
 #include <AzToolsFramework/Prefab/PrefabDomUtils.h>
+#include <AzToolsFramework/Prefab/PrefabPublicNotificationHandler.h>
 #include <AzToolsFramework/Entity/EditorEntityContextBus.h>
 
 namespace AzToolsFramework
@@ -60,6 +61,7 @@ namespace AzToolsFramework
             AzToolsFramework::Prefab::PrefabConversionUtils::PrefabCatchmentProcessor::Reflect(context);
             AzToolsFramework::Prefab::PrefabConversionUtils::EditorInfoRemover::Reflect(context);
             PrefabPublicRequestHandler::Reflect(context);
+            PrefabPublicNotificationHandler::Reflect(context);
             PrefabFocusHandler::Reflect(context);
             PrefabLoader::Reflect(context);
             PrefabSystemScriptingHandler::Reflect(context);
@@ -71,13 +73,12 @@ namespace AzToolsFramework
 
             if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
             {
-
                 behaviorContext->EBus<PrefabLoaderScriptingBus>("PrefabLoaderScriptingBus")
                     ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
                     ->Attribute(AZ::Script::Attributes::Module, "prefab")
                     ->Attribute(AZ::Script::Attributes::Category, "Prefab")
-                    ->Event("SaveTemplateToString", &PrefabLoaderScriptingBus::Events::SaveTemplateToString);
-                ;
+                    ->Event("SaveTemplateToString", &PrefabLoaderScriptingBus::Events::SaveTemplateToString)
+                    ;
             }
 
             AZ::JsonRegistrationContext* jsonRegistration = azrtti_cast<AZ::JsonRegistrationContext*>(context);

+ 2 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake

@@ -731,6 +731,8 @@ set(FILES
     Prefab/PrefabPublicHandler.cpp
     Prefab/PrefabPublicInterface.h
     Prefab/PrefabPublicNotificationBus.h
+    Prefab/PrefabPublicNotificationHandler.h
+    Prefab/PrefabPublicNotificationHandler.cpp
     Prefab/PrefabPublicRequestBus.h
     Prefab/PrefabPublicRequestHandler.h
     Prefab/PrefabPublicRequestHandler.cpp

+ 5 - 1
Code/Tools/AssetProcessor/native/ui/MainWindow.cpp

@@ -517,7 +517,11 @@ void MainWindow::BuilderTabSelectionChanged(const QItemSelection& selected, cons
 {
     if (selected.size() > 0)
     {
-        const auto& proxyIndex = selected.indexes().at(0);
+        const auto proxyIndex = selected.indexes().at(0);
+        if (!proxyIndex.isValid())
+        {
+            return;
+        }
         const auto& index = m_builderListSortFilterProxy->mapToSource(proxyIndex);
 
         AssetProcessor::BuilderInfoList builders;

+ 54 - 0
Code/Tools/SceneAPI/SceneCore/Tests/Utilities/CoordinateSystemConverterTests.cpp

@@ -0,0 +1,54 @@
+/*
+ * 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 <AzTest/AzTest.h>
+#include <AzCore/Math/Matrix3x4.h>
+#include <SceneAPI/SceneCore/Utilities/CoordinateSystemConverter.h>
+
+namespace EMotionFX
+{
+    const AZ::Vector3 sourceBasisVectors[3] = {
+        AZ::Vector3(1.0f, 0.0f, 0.0f),
+        AZ::Vector3(0.0f, 1.0f, 0.0f),
+        AZ::Vector3(0.0f, 0.0f, 1.0f),
+    };
+    const AZ::Vector3 targetBasisVectors[3] = {
+        AZ::Vector3(-1.0f, 0.0f, 0.0f),
+        AZ::Vector3(0.0f, 1.0f, 0.0f),
+        AZ::Vector3(0.0f, 0.0f, -1.0f),
+    };
+    const AZ::u32 targetBasisIndices[3] = { 0, 1, 2 };
+
+    TEST(CoordinateSystemConverterTests, TransformsCorrectlyCreatedFromBasisVectors)
+    {
+        AZ::SceneAPI::CoordinateSystemConverter coordinateSystemConverter =
+            AZ::SceneAPI::CoordinateSystemConverter::CreateFromBasisVectors(sourceBasisVectors, targetBasisVectors, targetBasisIndices);
+
+        EXPECT_TRUE(coordinateSystemConverter.GetSourceTransform().GetBasisX().IsClose(sourceBasisVectors[0]));
+        EXPECT_TRUE(coordinateSystemConverter.GetSourceTransform().GetBasisY().IsClose(sourceBasisVectors[1]));
+        EXPECT_TRUE(coordinateSystemConverter.GetSourceTransform().GetBasisZ().IsClose(sourceBasisVectors[2]));
+        EXPECT_TRUE(coordinateSystemConverter.GetSourceTransform().GetTranslation().IsClose(AZ::Vector3::CreateZero()));
+        EXPECT_TRUE(coordinateSystemConverter.GetTargetTransform().GetBasisX().IsClose(targetBasisVectors[0]));
+        EXPECT_TRUE(coordinateSystemConverter.GetTargetTransform().GetBasisY().IsClose(targetBasisVectors[1]));
+        EXPECT_TRUE(coordinateSystemConverter.GetTargetTransform().GetBasisZ().IsClose(targetBasisVectors[2]));
+        EXPECT_TRUE(coordinateSystemConverter.GetTargetTransform().GetTranslation().IsClose(AZ::Vector3::CreateZero()));
+    }
+
+    TEST(CoordinateSystemConverterTests, ConverterSimpleRotation)
+    {
+        AZ::SceneAPI::CoordinateSystemConverter coordinateSystemConverter =
+            AZ::SceneAPI::CoordinateSystemConverter::CreateFromBasisVectors(sourceBasisVectors, targetBasisVectors, targetBasisIndices);
+
+        const AZ::Matrix3x4 testMatrix = AZ::Matrix3x4::CreateFromQuaternion(AZ::Quaternion(1.0f, 0.0f, 0.0f, 0.0f));
+        const AZ::Transform testTransform = AZ::Transform::CreateFromMatrix3x4(testMatrix);
+        const AZ::Transform convertedTransform = coordinateSystemConverter.ConvertTransform(testTransform);
+
+        EXPECT_TRUE(convertedTransform.GetRotation().IsClose(AZ::Quaternion(0.0f, 0.0f, 1.0f, 0.0f)));
+    }
+} // namespace EMotionFX
+

+ 1 - 0
Code/Tools/SceneAPI/SceneCore/scenecore_testing_files.cmake

@@ -30,5 +30,6 @@ set(FILES
     Tests/Containers/Utilities/FiltersTests.cpp
     Tests/Utilities/SceneGraphSelectorTests.cpp
     Tests/Utilities/PatternMatcherTests.cpp
+    Tests/Utilities/CoordinateSystemConverterTests.cpp
     Tests/Export/MaterialIOTests.cpp
 )

+ 1 - 1
Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset

@@ -216,7 +216,7 @@
                 "Name": "NewDepthOfFieldCompositeTemplate",
                 "Path": "Passes/NewDepthOfFieldComposite.pass"
             },
-			{
+            {
                 "Name": "EsmShadowmapsTemplate",
                 "Path": "Passes/EsmShadowmaps.pass"
             },

+ 10 - 3
Gems/Atom/RPI/Code/Source/RPI.Public/Buffer/BufferSystem.cpp

@@ -121,13 +121,20 @@ namespace AZ
                 bufferPoolDesc.m_hostMemoryAccess = RHI::HostMemoryAccess::Read;
                 break;
             case CommonBufferPoolType::ReadWrite:
-                // Add CopyRead flag too since it's often we need to readback gpu attachment buffers.
-                bufferPoolDesc.m_bindFlags = RHI::BufferBindFlags::ShaderWrite | RHI::BufferBindFlags::ShaderRead | RHI::BufferBindFlags::CopyRead;
+                // Add CopyRead flag too since it's often we need to read back GPU attachment buffers.
+                bufferPoolDesc.m_bindFlags =
+//                  [To Do] - the following line (and possibly InputAssembly / DynamicInputAssembly) will need to
+//                  be added to support future indirect buffer usage for GPU driven render pipeline
+//                    RHI::BufferBindFlags::Indirect |  
+                    RHI::BufferBindFlags::ShaderWrite | RHI::BufferBindFlags::ShaderRead | RHI::BufferBindFlags::CopyRead;
                 bufferPoolDesc.m_heapMemoryLevel = RHI::HeapMemoryLevel::Device;
                 bufferPoolDesc.m_hostMemoryAccess = RHI::HostMemoryAccess::Write;
                 break;
             case CommonBufferPoolType::ReadOnly:
-                bufferPoolDesc.m_bindFlags = RHI::BufferBindFlags::ShaderRead;
+//                  [To Do] - the following line (and possibly InputAssembly / DynamicInputAssembly) will need to
+//                  be added to support future indirect buffer usage for GPU driven render pipeline
+                bufferPoolDesc.m_bindFlags = // RHI::BufferBindFlags::Indirect |
+                    RHI::BufferBindFlags::ShaderRead;
                 bufferPoolDesc.m_heapMemoryLevel = RHI::HeapMemoryLevel::Device;
                 bufferPoolDesc.m_hostMemoryAccess = RHI::HostMemoryAccess::Write;
                 break;

+ 4 - 1
Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp

@@ -638,7 +638,10 @@ namespace MaterialEditor
                     if (propertyIndexInBounds)
                     {
                         AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition);
-                        
+                        propertyConfig.m_description +=
+                            "\n\n<img src=\':/Icons/changed_property.svg\'> An indicator icon will be shown to the left of properties with "
+                            "overridden values that are different from the parent material, or material type if there is no parent.";
+
                         // (Does DynamicPropertyConfig really even need m_groupName? It doesn't seem to be used anywhere)
                         propertyConfig.m_groupName = m_groups.back()->m_name;
                         propertyConfig.m_groupDisplayName = m_groups.back()->m_displayName;

+ 92 - 85
Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp

@@ -35,6 +35,7 @@ AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnin
 #include <QLabel>
 #include <QMenu>
 #include <QToolButton>
+#include <QToolTip>
 #include <QWidget>
 AZ_POP_DISABLE_WARNING
 
@@ -115,7 +116,6 @@ namespace AZ
                 // Add material functors that are in the top-level functors list. Other functors are also added per-property-group elsewhere.
                 AddEditorMaterialFunctors(m_editData.m_materialTypeSourceData.m_materialFunctorSourceData, AZ::RPI::MaterialNameContext{});
 
-
                 Populate();
                 LoadOverridesFromEntity();
                 return true;
@@ -212,14 +212,9 @@ namespace AZ
                     return;
                 }
 
-                QFileInfo materialFileInfo(AZ::RPI::AssetUtils::GetProductPathByAssetId(m_editData.m_materialAsset.GetId()).c_str());
-                QFileInfo materialSourceFileInfo(m_editData.m_materialSourcePath.c_str());
-                QFileInfo materialTypeSourceFileInfo(m_editData.m_materialTypeSourcePath.c_str());
-                QFileInfo materialParentSourceFileInfo(
-                    AZ::RPI::AssetUtils::GetSourcePathByAssetId(m_editData.m_materialParentAsset.GetId()).c_str());
-
                 AZStd::string entityName;
-                AZ::ComponentApplicationBus::BroadcastResult(entityName, &AZ::ComponentApplicationBus::Events::GetEntityName, m_primaryEntityId);
+                AZ::ComponentApplicationBus::BroadcastResult(
+                    entityName, &AZ::ComponentApplicationBus::Events::GetEntityName, m_primaryEntityId);
 
                 AZStd::string slotName;
                 MaterialComponentRequestBus::EventResult(
@@ -233,28 +228,60 @@ namespace AZ
                 materialInfo += m_materialAssignmentId.IsDefault() || m_materialAssignmentId.IsSlotIdOnly()
                     ? tr("<tr><td><b>Material Slot LOD&emsp;</b></td><td>%1</td></tr>").arg(-1)
                     : tr("<tr><td><b>Material Slot LOD&emsp;</b></td><td>%1</td></tr>").arg(m_materialAssignmentId.m_lodIndex);
-                if (!materialFileInfo.fileName().isEmpty())
+
+                if (m_editData.m_materialAsset.GetId().IsValid())
                 {
-                    materialInfo += tr("<tr><td><b>Material&emsp;</b></td><td>%1</td></tr>").arg(materialFileInfo.fileName());
+                    AZ::Data::AssetInfo assetInfo;
+                    AZ::Data::AssetCatalogRequestBus::BroadcastResult(
+                        assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, m_editData.m_materialAsset.GetId());
+
+                    materialInfo += tr("<tr><td><b>Material Asset&emsp;</b></td><td>%1</td></tr>").arg(assetInfo.m_relativePath.c_str());
                 }
-                if (!materialTypeSourceFileInfo.fileName().isEmpty())
+                if (!m_editData.m_materialSourcePath.empty())
                 {
-                    materialInfo +=
-                        tr("<tr><td><b>Material Type&emsp;</b></td><td>%1</td></tr>").arg(materialTypeSourceFileInfo.fileName());
+                    // Inserting links that will be used to open the material/type in the material editor
+                    const auto& materialSourceFileName = GetFileName(m_editData.m_materialSourcePath);
+                    if (IsSourceMaterial(m_editData.m_materialSourcePath))
+                    {
+                        materialInfo += tr("<tr><td><b>Material Source&emsp;</b></td><td><a href=\"%1\">%2</a></td></tr>")
+                                            .arg(m_editData.m_materialSourcePath.c_str())
+                                            .arg(materialSourceFileName.c_str());
+                    }
+                    else
+                    {
+                        // Materials that come from other sources like FBX files will not have the link
+                        materialInfo += tr("<tr><td><b>Material Source&emsp;</b></td><td>%1</td></tr>")
+                                            .arg(materialSourceFileName.c_str());
+                    }
                 }
-                if (!materialSourceFileInfo.fileName().isEmpty())
+                if (IsSourceMaterial(m_editData.m_materialParentSourcePath))
                 {
-                    materialInfo += tr("<tr><td><b>Material Source&emsp;</b></td><td>%1</td></tr>").arg(materialSourceFileInfo.fileName());
+                    // Inserting links that will be used to open the material/type in the material editor
+                    const auto& materialParentSourceFileName = GetFileName(m_editData.m_materialParentSourcePath);
+                    materialInfo += tr("<tr><td><b>Material Parent&emsp;</b></td><td><a href=\"%1\">%2</a></td></tr>")
+                                        .arg(m_editData.m_materialParentSourcePath.c_str())
+                                        .arg(materialParentSourceFileName.c_str());
                 }
-                if (!materialParentSourceFileInfo.fileName().isEmpty())
+                if (!m_editData.m_materialTypeSourcePath.empty())
                 {
-                    materialInfo +=
-                        tr("<tr><td><b>Material Parent&emsp;</b></td><td>%1</td></tr>").arg(materialParentSourceFileInfo.fileName());
+                    // Inserting links that will be used to open the material/type in the material editor
+                    const auto& materialTypeSourceFileName = GetFileName(m_editData.m_materialTypeSourcePath);
+                    materialInfo += tr("<tr><td><b>Material Type&emsp;</b></td><td><a href=\"%1\">%2</a></td></tr>")
+                                        .arg(m_editData.m_materialTypeSourcePath.c_str())
+                                        .arg(materialTypeSourceFileName.c_str());
                 }
                 materialInfo += tr("</table>");
 
                 m_overviewText->setText(materialInfo);
                 m_overviewText->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignTop);
+                m_overviewText->setOpenExternalLinks(false);
+                connect(m_overviewText, &QLabel::linkActivated, this, [](const QString& link) {
+                    EditorMaterialSystemComponentRequestBus::Broadcast(
+                        &EditorMaterialSystemComponentRequestBus::Events::OpenMaterialEditor, link.toUtf8().constData());
+                });
+                connect(m_overviewText, &QLabel::linkHovered, this, [](const QString& link) {
+                    QToolTip::showText(QCursor::pos(), link);
+                });
 
                 // Update the overview image with the last rendered preview of the primary entity's material.
                 QPixmap pixmap;
@@ -353,6 +380,9 @@ namespace AZ
                             groupNameContext.ContextualizeProperty(propertyConfig.m_id);
                             
                             AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition);
+                            propertyConfig.m_description +=
+                                "\n\n<img src=\':/Icons/changed_property.svg\'> An indicator icon will be shown to the left of properties "
+                                "with overridden values that are different from the assigned material.";
 
                             const auto& propertyIndex = 
                                 m_editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id);
@@ -433,7 +463,7 @@ namespace AZ
 
                         // This first converts to an acceptable runtime type in case the value came from script
                         const auto propertyIndex = m_materialInstance->FindPropertyIndex(property.GetId());
-                        if (!propertyIndex.IsNull())
+                        if (propertyIndex.IsValid())
                         {
                             const auto runtimeValue = AtomToolsFramework::ConvertToRuntimeType(editValue);
                             if (runtimeValue.IsValid())
@@ -614,7 +644,7 @@ namespace AZ
                 }
 
                 const auto propertyIndex = m_materialInstance->FindPropertyIndex(property.GetId());
-                if (!propertyIndex.IsNull())
+                if (propertyIndex.IsValid())
                 {
                     m_dirtyPropertyFlags.set(propertyIndex.GetIndex());
 
@@ -636,7 +666,7 @@ namespace AZ
             bool MaterialPropertyInspector::IsInstanceNodePropertyModifed(const AzToolsFramework::InstanceDataNode* node) const
             {
                 const auto property = AtomToolsFramework::FindAncestorInstanceDataNodeByType<AtomToolsFramework::DynamicProperty>(node);
-                return property && !AtomToolsFramework::ArePropertyValuesEqual(property->GetValue(), property->GetConfig().m_parentValue);
+                return property && !AtomToolsFramework::ArePropertyValuesEqual(property->GetValue(), property->GetConfig().m_originalValue);
             }
 
             const char* MaterialPropertyInspector::GetInstanceNodePropertyIndicator(const AzToolsFramework::InstanceDataNode* node) const
@@ -648,29 +678,32 @@ namespace AZ
                 return ":/Icons/blank.png";
             }
 
-            bool MaterialPropertyInspector::SaveMaterial() const
+            AZStd::string MaterialPropertyInspector::GetRelativePath(const AZStd::string& path) const
             {
-                const auto& defaultPath = AtomToolsFramework::GetUniqueFilePath(AZStd::string::format(
-                    "%s/Assets/untitled.%s", AZ::Utils::GetProjectPath().c_str(), AZ::RPI::MaterialSourceData::Extension));
-
-                const auto& saveFilePath = AtomToolsFramework::GetSaveFilePath(defaultPath);
-                if (saveFilePath.empty())
-                {
-                    return false;
-                }
+                bool pathFound = false;
+                AZStd::string rootFolder;
+                AZStd::string relativePath;
+                AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
+                    pathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GenerateRelativeSourcePath, path, relativePath,
+                    rootFolder);
+                return relativePath;
+            }
 
-                if (!EditorMaterialComponentUtil::SaveSourceMaterialFromEditData(saveFilePath, m_editData))
-                {
-                    AZ_Warning("AZ::Render::EditorMaterialComponentInspector", false, "Failed to save material data.");
-                    return false;
-                }
+            AZStd::string MaterialPropertyInspector::GetFileName(const AZStd::string& path) const
+            {
+                AZStd::string fileName;
+                AZ::StringFunc::Path::GetFullFileName(path.c_str(), fileName);
+                return fileName;
+            }
 
-                return true;
+            bool MaterialPropertyInspector::IsSourceMaterial(const AZStd::string& path) const
+            {
+                return !path.empty() && AZ::StringFunc::Path::IsExtension(path.c_str(), AZ::RPI::MaterialSourceData::Extension);
             }
 
-            bool MaterialPropertyInspector::SaveMaterialToSource() const
+            bool MaterialPropertyInspector::SaveMaterial(const AZStd::string& path) const
             {
-                const auto& saveFilePath = AtomToolsFramework::GetSaveFilePath(m_editData.m_materialSourcePath);
+                const auto& saveFilePath = AtomToolsFramework::GetSaveFilePath(path);
                 if (saveFilePath.empty())
                 {
                     return false;
@@ -685,46 +718,37 @@ namespace AZ
                 return true;
             }
 
-            bool MaterialPropertyInspector::HasMaterialSource() const
-            {
-                return IsLoaded() && !m_editData.m_materialSourcePath.empty() &&
-                    AZ::StringFunc::Path::IsExtension(m_editData.m_materialSourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension);
-            }
-
-            bool MaterialPropertyInspector::HasMaterialParentSource() const
-            {
-                return IsLoaded() && !m_editData.m_materialParentSourcePath.empty() &&
-                    AZ::StringFunc::Path::IsExtension(
-                        m_editData.m_materialParentSourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension);
-            }
-
-            void MaterialPropertyInspector::OpenMaterialSourceInEditor() const
+            void MaterialPropertyInspector::OpenMenu()
             {
-                if (HasMaterialSource())
+                if (!IsLoaded())
                 {
-                    EditorMaterialSystemComponentRequestBus::Broadcast(
-                        &EditorMaterialSystemComponentRequestBus::Events::OpenMaterialEditor, m_editData.m_materialSourcePath);
+                    return;
                 }
-            }
 
-            void MaterialPropertyInspector::OpenMaterialParentSourceInEditor() const
-            {
-                if (HasMaterialParentSource())
+                QAction* action = nullptr;
+
+                QMenu menu(this);
+
+                action = menu.addAction(tr("Save As..."), [this] {
+                    const auto& defaultPath = AtomToolsFramework::GetUniqueFilePath(AZStd::string::format(
+                        "%s/Assets/untitled.%s", AZ::Utils::GetProjectPath().c_str(), AZ::RPI::MaterialSourceData::Extension));
+                    SaveMaterial(defaultPath);
+                });
+
+                if (IsSourceMaterial(m_editData.m_materialSourcePath))
                 {
-                    EditorMaterialSystemComponentRequestBus::Broadcast(
-                        &EditorMaterialSystemComponentRequestBus::Events::OpenMaterialEditor, m_editData.m_materialParentSourcePath);
+                    const auto& materialSourceFileName = GetFileName(m_editData.m_materialSourcePath);
+                    action = menu.addAction(tr("Save Over \"%1\"...").arg(materialSourceFileName.c_str()), [this] {
+                        SaveMaterial(m_editData.m_materialSourcePath);
+                    });
                 }
-            }
 
-            void MaterialPropertyInspector::OpenMenu()
-            {
-                QAction* action = nullptr;
+                menu.addSeparator();
 
-                QMenu menu(this);
                 action = menu.addAction("Clear Overrides", [this] {
                     AzToolsFramework::ScopedUndoBatch undoBatch("Clear material property overrides.");
-                        m_editData.m_materialPropertyOverrideMap.clear();
-                        for (const AZ::EntityId& entityId : m_entityIdsToEdit)
+                    m_editData.m_materialPropertyOverrideMap.clear();
+                    for (const AZ::EntityId& entityId : m_entityIdsToEdit)
                     {
                         AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(
                             &AzToolsFramework::ToolsApplicationRequests::Bus::Events::AddDirtyEntity, entityId);
@@ -736,23 +760,6 @@ namespace AZ
                     m_updateUI = true;
                     m_updatePreview = true;
                 });
-                action->setEnabled(IsLoaded());
-
-                menu.addSeparator();
-
-                action = menu.addAction("Save Material", [this] { SaveMaterial(); });
-                action->setEnabled(IsLoaded());
-
-                action = menu.addAction("Save Material To Source", [this] { SaveMaterialToSource(); });
-                action->setEnabled(HasMaterialSource());
-
-                menu.addSeparator();
-
-                action = menu.addAction("Open Source Material In Editor", [this] { OpenMaterialSourceInEditor(); });
-                action->setEnabled(HasMaterialSource());
-
-                action = menu.addAction("Open Parent Material In Editor", [this] { OpenMaterialParentSourceInEditor(); });
-                action->setEnabled(HasMaterialParentSource());
 
                 menu.exec(QCursor::pos());
             }

+ 4 - 6
Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.h

@@ -70,12 +70,10 @@ namespace AZ
                 //! Builds all of the properties and generates the user interface for the inspector
                 void Populate();
 
-                bool SaveMaterial() const;
-                bool SaveMaterialToSource() const;
-                bool HasMaterialSource() const;
-                bool HasMaterialParentSource() const;
-                void OpenMaterialSourceInEditor() const;
-                void OpenMaterialParentSourceInEditor() const;
+                AZStd::string GetRelativePath(const AZStd::string& path) const;
+                AZStd::string GetFileName(const AZStd::string& path) const;
+                bool IsSourceMaterial(const AZStd::string& path) const;
+                bool SaveMaterial(const AZStd::string& path) const;
                 void OpenMenu();
                 const EditorMaterialComponentUtil::MaterialEditData& GetEditData() const;
 

+ 1 - 1
Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialSystemComponent.cpp

@@ -293,7 +293,7 @@ namespace AZ
             AzToolsFramework::ViewPaneOptions inspectorOptions;
             inspectorOptions.canHaveMultipleInstances = true;
             inspectorOptions.preferedDockingArea = Qt::NoDockWidgetArea;
-            inspectorOptions.paneRect = QRect(50, 50, 400, 700);
+            inspectorOptions.paneRect = QRect(50, 50, 600, 1000);
             inspectorOptions.showInMenu = false;
             inspectorOptions.showOnToolsToolbar = false;
             AzToolsFramework::RegisterViewPane<AZ::Render::EditorMaterialComponentInspector::MaterialPropertyInspector>(

+ 104 - 54
Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp

@@ -6,12 +6,14 @@
  *
  */
 
-#include <Material/MaterialComponentController.h>
-#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
 #include <Atom/RPI.Reflect/Asset/AssetUtils.h>
+#include <Atom/RPI.Reflect/Image/AttachmentImageAsset.h>
+#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
+#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
+#include <AtomCore/Instance/InstanceDatabase.h>
 #include <AzCore/Asset/AssetSerializer.h>
 #include <AzCore/Serialization/SerializeContext.h>
-#include <AtomCore/Instance/InstanceDatabase.h>
+#include <Material/MaterialComponentController.h>
 
 namespace AZ
 {
@@ -89,17 +91,17 @@ namespace AZ
 
         void MaterialComponentController::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
         {
-            provided.push_back(AZ_CRC("MaterialProviderService", 0x64849a6b));
+            provided.push_back(AZ_CRC_CE("MaterialProviderService"));
         }
 
         void MaterialComponentController::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
         {
-            incompatible.push_back(AZ_CRC("MaterialProviderService", 0x64849a6b));
+            incompatible.push_back(AZ_CRC_CE("MaterialProviderService"));
         }
 
         void MaterialComponentController::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
         {
-            required.push_back(AZ_CRC("MaterialReceiverService", 0x0d1a6a74));
+            required.push_back(AZ_CRC_CE("MaterialReceiverService"));
         }
 
         MaterialComponentController::MaterialComponentController(const MaterialComponentConfig& config)
@@ -192,39 +194,44 @@ namespace AZ
         {
             Data::AssetBus::MultiHandler::BusDisconnect();
 
-            bool anyQueued = false;
-            auto queueAsset = [&anyQueued, this](AZ::Data::Asset<AZ::RPI::MaterialAsset>& materialAsset) -> bool
+            // Build tables of all referenced materials so that we can look up default values for their material assets and properties
+            m_defaultMaterialMap.clear();
+            m_activeMaterialMap.clear();
+            m_uniqueMaterialMap.clear();
+            for (const auto& [materialAssignmentId, materialAssignment] : GetOriginalMaterialAssignments())
             {
-                if (materialAsset.GetId().IsValid() && !this->Data::AssetBus::MultiHandler::BusIsConnectedId(materialAsset.GetId()))
-                {
-                    anyQueued = true;
-                    materialAsset.QueueLoad();
-                    this->Data::AssetBus::MultiHandler::BusConnect(materialAsset.GetId());
-                    return true;
-                }
-                return false;
-            };
+                auto& defaultMaterial = m_defaultMaterialMap[materialAssignmentId];
+                defaultMaterial = materialAssignment.m_materialAsset;
 
-            for (auto& materialPair : m_configuration.m_materials)
-            {
-                if (materialPair.second.m_materialInstancePreCreated)
-                {
-                    continue;
-                }
+                auto& activeMaterial = m_activeMaterialMap[materialAssignmentId];
+                activeMaterial = defaultMaterial;
+                m_uniqueMaterialMap[activeMaterial.GetId()] = activeMaterial;
 
-                materialPair.second.m_defaultMaterialAsset = {};
-                if (!queueAsset(materialPair.second.m_materialAsset))
+                // The active material map is initialized with the default materials then entries are replaced if there is a valid override in the configuration
+                auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
+                if (materialIt != m_configuration.m_materials.end())
                 {
-                    // Only assign and load the default material if there was no material override and there are propoerties to apply
-                    if (!materialPair.second.m_propertyOverrides.empty() || !materialPair.second.m_matModUvOverrides.empty())
+                    materialIt->second.m_defaultMaterialAsset = defaultMaterial;
+                    if (materialIt->second.m_materialAsset.GetId().IsValid())
                     {
-                        materialPair.second.m_defaultMaterialAsset = AZ::Data::Asset<AZ::RPI::MaterialAsset>(
-                            GetDefaultMaterialAssetId(materialPair.first), AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid());
-                        queueAsset(materialPair.second.m_defaultMaterialAsset);
+                        activeMaterial = materialIt->second.m_materialAsset;
+                        m_uniqueMaterialMap[activeMaterial.GetId()] = activeMaterial;
                     }
                 }
             }
 
+            // Begin loading all unique, referenced material assets
+            bool anyQueued = false;
+            for (auto& [assetId, uniqueMaterial] : m_uniqueMaterialMap)
+            {
+                if (uniqueMaterial.GetId().IsValid())
+                {
+                    anyQueued = true;
+                    uniqueMaterial.QueueLoad();
+                    AZ::Data::AssetBus::MultiHandler::BusConnect(uniqueMaterial.GetId());
+                }
+            }
+
             if (!anyQueued)
             {
                 ReleaseMaterials();
@@ -252,6 +259,22 @@ namespace AZ
                 }
             };
 
+            // Update all of the material asset containers to reference the newly loaded asset
+            for (auto& materialPair : m_defaultMaterialMap)
+            {
+                updateAsset(materialPair.second);
+            }
+
+            for (auto& materialPair : m_activeMaterialMap)
+            {
+                updateAsset(materialPair.second);
+            }
+
+            for (auto& materialPair : m_uniqueMaterialMap)
+            {
+                updateAsset(materialPair.second);
+            }
+
             for (auto& materialPair : m_configuration.m_materials)
             {
                 updateAsset(materialPair.second.m_materialAsset);
@@ -260,8 +283,8 @@ namespace AZ
 
             if (allReady)
             {
-                //Do not start updating materials and properties until all materials are loaded and ready
-                //This prevents property changes from being queued and notifications from being sent with pending materials
+                // Only start updating materials and instances after all assets that can be loaded have been loaded.
+                // This ensures that property changes and notifications only occur once everything is fully loaded.
                 for (auto& materialPair : m_configuration.m_materials)
                 {
                     materialPair.second.RebuildInstance();
@@ -276,6 +299,9 @@ namespace AZ
         {
             Data::AssetBus::MultiHandler::BusDisconnect();
 
+            m_defaultMaterialMap.clear();
+            m_activeMaterialMap.clear();
+            m_uniqueMaterialMap.clear();
             for (auto& materialPair : m_configuration.m_materials)
             {
                 materialPair.second.Release();
@@ -301,12 +327,13 @@ namespace AZ
 
         AZ::Data::AssetId MaterialComponentController::GetActiveMaterialAssetId(const MaterialAssignmentId& materialAssignmentId) const
         {
-            const AZ::Data::AssetId materialAssetId = GetMaterialOverride(materialAssignmentId);
-            return materialAssetId.IsValid() ? materialAssetId : GetDefaultMaterialAssetId(materialAssignmentId);
+            auto materialIt = m_activeMaterialMap.find(materialAssignmentId);
+            return materialIt != m_activeMaterialMap.end() ? materialIt->second.GetId() : AZ::Data::AssetId();
         }
 
         AZ::Data::AssetId MaterialComponentController::GetDefaultMaterialAssetId(const MaterialAssignmentId& materialAssignmentId) const
         {
+            // Temporarily reverting the logic for this function. It should be entirely based around m_defaultMaterialMap.
             RPI::ModelMaterialSlotMap modelMaterialSlots;
             MaterialReceiverRequestBus::EventResult(
                 modelMaterialSlots, m_entityId, &MaterialReceiverRequestBus::Events::GetModelMaterialSlots);
@@ -360,38 +387,46 @@ namespace AZ
             if (!m_configuration.m_materials.empty())
             {
                 m_configuration.m_materials.clear();
-                QueueMaterialUpdateNotification();
+                LoadMaterials();
             }
         }
 
         void MaterialComponentController::ClearModelMaterialOverrides()
         {
-            AZStd::erase_if(m_configuration.m_materials, [](const auto& materialPair) {
+            const auto numErased = AZStd::erase_if(m_configuration.m_materials, [](const auto& materialPair) {
                 return materialPair.first.IsSlotIdOnly();
             });
-            QueueMaterialUpdateNotification();
+            if (numErased > 0)
+            {
+                LoadMaterials();
+            }
         }
 
         void MaterialComponentController::ClearLodMaterialOverrides()
         {
-            AZStd::erase_if(m_configuration.m_materials, [](const auto& materialPair) {
+            const auto numErased = AZStd::erase_if(m_configuration.m_materials, [](const auto& materialPair) {
                 return materialPair.first.IsLodAndSlotId();
             });
-            QueueMaterialUpdateNotification();
+            if (numErased > 0)
+            {
+                LoadMaterials();
+            }
         }
 
         void MaterialComponentController::ClearIncompatibleMaterialOverrides()
         {
-            const MaterialAssignmentMap& originalMaterials = GetOriginalMaterialAssignments();
-            AZStd::erase_if(m_configuration.m_materials, [&originalMaterials](const auto& materialPair) {
-                return originalMaterials.find(materialPair.first) == originalMaterials.end();
+            const auto numErased = AZStd::erase_if(m_configuration.m_materials, [this](const auto& materialPair) {
+                return m_defaultMaterialMap.find(materialPair.first) == m_defaultMaterialMap.end();
             });
-            QueueMaterialUpdateNotification();
+            if (numErased > 0)
+            {
+                LoadMaterials();
+            }
         }
 
         void MaterialComponentController::ClearInvalidMaterialOverrides()
         {
-            AZStd::erase_if(m_configuration.m_materials, [](const auto& materialPair) {
+            const auto numErased = AZStd::erase_if(m_configuration.m_materials, [](const auto& materialPair) {
                 if (materialPair.second.m_materialAsset.GetId().IsValid())
                 {
                     AZ::Data::AssetInfo assetInfo;
@@ -402,7 +437,10 @@ namespace AZ
                 }
                 return false;
             });
-            QueueMaterialUpdateNotification();
+            if (numErased > 0)
+            {
+                LoadMaterials();
+            }
         }
 
         void MaterialComponentController::RepairInvalidMaterialOverrides()
@@ -417,8 +455,7 @@ namespace AZ
                         materialPair.second.m_materialAsset.GetId());
                     if (!assetInfo.m_assetId.IsValid())
                     {
-                        materialPair.second.m_materialAsset = AZ::Data::Asset<AZ::RPI::MaterialAsset>(
-                            GetDefaultMaterialAssetId(materialPair.first), AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid());
+                        materialPair.second.m_materialAsset = {};
                     }
                 }
             }
@@ -453,6 +490,7 @@ namespace AZ
                 }
             }
 
+            LoadMaterials();
             return propertiesUpdated;
         }
 
@@ -492,7 +530,7 @@ namespace AZ
         {
             if (m_configuration.m_materials.erase(materialAssignmentId) > 0)
             {
-                QueueMaterialUpdateNotification();
+                LoadMaterials();
             }
         }
 
@@ -523,18 +561,26 @@ namespace AZ
         AZStd::any MaterialComponentController::GetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const
         {
             const auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
-            if (materialIt == m_configuration.m_materials.end())
+            if (materialIt != m_configuration.m_materials.end())
             {
-                return {};
+                const auto propertyIt = materialIt->second.m_propertyOverrides.find(AZ::Name(propertyName));
+                if (propertyIt != materialIt->second.m_propertyOverrides.end())
+                {
+                    return propertyIt->second;
+                }
             }
 
-            const auto propertyIt = materialIt->second.m_propertyOverrides.find(AZ::Name(propertyName));
-            if (propertyIt == materialIt->second.m_propertyOverrides.end())
+            const auto activeIt = m_activeMaterialMap.find(materialAssignmentId);
+            if (activeIt != m_activeMaterialMap.end() && activeIt->second.IsReady())
             {
-                return {};
+                const auto index = activeIt->second->GetMaterialPropertiesLayout()->FindPropertyIndex(AZ::Name(propertyName));
+                if (index.IsValid())
+                {
+                    return AZ::RPI::MaterialPropertyValue::ToAny(activeIt->second->GetPropertyValues()[index.GetIndex()]);
+                }
             }
 
-            return propertyIt->second;
+            return {};
         }
 
         void MaterialComponentController::ClearPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName)
@@ -690,6 +736,10 @@ namespace AZ
                     {
                         value = AZStd::any_cast<AZ::Data::Asset<AZ::Data::AssetData>>(value).GetId();
                     }
+                    else if (value.is<AZ::Data::Asset<AZ::RPI::AttachmentImageAsset>>())
+                    {
+                        value = AZStd::any_cast<AZ::Data::Asset<AZ::RPI::AttachmentImageAsset>>(value).GetId();
+                    }
                     else if (value.is<AZ::Data::Asset<AZ::RPI::StreamingImageAsset>>())
                     {
                         value = AZStd::any_cast<AZ::Data::Asset<AZ::RPI::StreamingImageAsset>>(value).GetId();

+ 3 - 0
Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h

@@ -107,6 +107,9 @@ namespace AZ
 
             EntityId m_entityId;
             MaterialComponentConfig m_configuration;
+            AZStd::unordered_map<MaterialAssignmentId, AZ::Data::Asset<AZ::RPI::MaterialAsset>> m_defaultMaterialMap;
+            AZStd::unordered_map<MaterialAssignmentId, AZ::Data::Asset<AZ::RPI::MaterialAsset>> m_activeMaterialMap;
+            AZStd::unordered_map<AZ::Data::AssetId, AZ::Data::Asset<AZ::RPI::MaterialAsset>> m_uniqueMaterialMap;
             AZStd::unordered_set<MaterialAssignmentId> m_materialsWithDirtyProperties;
             bool m_queuedMaterialUpdateNotification = false;
         };

+ 0 - 1
Gems/Blast/Code/CMakeLists.txt

@@ -82,7 +82,6 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS)
         BUILD_DEPENDENCIES
             PUBLIC
                 AZ::AzToolsFramework
-                AZ::GFxFramework
                 AZ::SceneCore
                 AZ::SceneData
                 Legacy::Editor.Headers

+ 10 - 8
Gems/EMotionFX/Code/EMotionFX/Pipeline/RCExt/Motion/MotionDataBuilder.cpp

@@ -347,11 +347,12 @@ namespace EMotionFX
                         // For additive motion, we stores the relative transform.
                         boneTransform = sampleFrameTransformInverse * boneTransform;
                     }
-                    
+
                     SceneAPIMatrixType boneTransformNoScale(boneTransform);
-                    const AZ::Vector3 position = coordSysConverter.ConvertVector3(boneTransform.GetTranslation());
-                    const AZ::Quaternion rotation = coordSysConverter.ConvertQuaternion(AZ::Quaternion::CreateFromMatrix3x4(boneTransformNoScale));
-                    const AZ::Vector3 scale = coordSysConverter.ConvertScale(boneTransformNoScale.ExtractScale());
+                    const AZ::Vector3 scale = boneTransformNoScale.ExtractScale();
+                    const AZ::Transform convertedTransform = AZ::Transform::CreateFromMatrix3x4(coordSysConverter.ConvertMatrix3x4(boneTransformNoScale));
+                    const AZ::Vector3 position = convertedTransform.GetTranslation();
+                    const AZ::Quaternion rotation = convertedTransform.GetRotation();
                     
                     // Set the pose when this is the first frame.
                     // This is used as optimization so that poses or non-animated submotions do not need any key tracks.
@@ -375,12 +376,13 @@ namespace EMotionFX
 
                 // Set the bind pose transform.
                 SceneAPIMatrixType bindBoneTransformNoScale(bindSpaceLocalTransform);
-                const AZ::Vector3    bindPos   = coordSysConverter.ConvertVector3(bindSpaceLocalTransform.GetTranslation());
                 const AZ::Vector3    bindScale = coordSysConverter.ConvertScale(bindBoneTransformNoScale.ExtractScale());
-                const AZ::Quaternion bindRot   = coordSysConverter.ConvertQuaternion(AZ::Quaternion::CreateFromMatrix3x4(bindBoneTransformNoScale)).GetNormalized();
+                const AZ::Transform convertedbindTransform = AZ::Transform::CreateFromMatrix3x4(coordSysConverter.ConvertMatrix3x4(bindBoneTransformNoScale));
+                const AZ::Vector3    bindPosition   = convertedbindTransform.GetTranslation();
+                const AZ::Quaternion bindRotation = convertedbindTransform.GetRotation();
 
-                motionData->SetJointBindPosePosition(jointDataIndex, bindPos);
-                motionData->SetJointBindPoseRotation(jointDataIndex, bindRot);
+                motionData->SetJointBindPosePosition(jointDataIndex, bindPosition);
+                motionData->SetJointBindPoseRotation(jointDataIndex, bindRotation);
                 EMFX_SCALECODE
                 (
                     motionData->SetJointBindPoseScale(jointDataIndex, bindScale);

+ 0 - 40
Gems/EMotionFX/Code/Tests/CoordinateSystemConverterTests.cpp

@@ -1,40 +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 <AzTest/AzTest.h>
-#include <EMotionFX/Pipeline/RCExt/CoordinateSystemConverter.h>
-
-namespace EMotionFX
-{
-    TEST(CoordinateSystemConverterTests, TransformsCorrectlyCreatedFromBasisVectors)
-    {
-        const AZ::Vector3 sourceBasisVectors[3] = {
-            AZ::Vector3(0.2544f, 0.224f, 0.9408f),
-            AZ::Vector3(0.9408f, 0.168f, -0.2944f),
-            AZ::Vector3(-0.224f, 0.96f, -0.168f),
-        };
-        const AZ::Vector3 targetBasisVectors[3] = {
-            AZ::Vector3(0.5616f, 0.7488f, 0.352f),
-            AZ::Vector3(-0.2112f, -0.2816f, 0.936f),
-            AZ::Vector3(0.8f, -0.6f, 0.0f),
-        };
-        const AZ::u32 targetBasisIndices[3] = { 0, 1, 2 };
-        Pipeline::CoordinateSystemConverter coordinateSystemConverter =
-            Pipeline::CoordinateSystemConverter::CreateFromBasisVectors(sourceBasisVectors, targetBasisVectors, targetBasisIndices);
-
-        EXPECT_TRUE(coordinateSystemConverter.GetSourceTransform().GetBasisX().IsClose(sourceBasisVectors[0]));
-        EXPECT_TRUE(coordinateSystemConverter.GetSourceTransform().GetBasisY().IsClose(sourceBasisVectors[1]));
-        EXPECT_TRUE(coordinateSystemConverter.GetSourceTransform().GetBasisZ().IsClose(sourceBasisVectors[2]));
-        EXPECT_TRUE(coordinateSystemConverter.GetSourceTransform().GetTranslation().IsClose(AZ::Vector3::CreateZero()));
-        EXPECT_TRUE(coordinateSystemConverter.GetTargetTransform().GetBasisX().IsClose(targetBasisVectors[0]));
-        EXPECT_TRUE(coordinateSystemConverter.GetTargetTransform().GetBasisY().IsClose(targetBasisVectors[1]));
-        EXPECT_TRUE(coordinateSystemConverter.GetTargetTransform().GetBasisZ().IsClose(targetBasisVectors[2]));
-        EXPECT_TRUE(coordinateSystemConverter.GetTargetTransform().GetTranslation().IsClose(AZ::Vector3::CreateZero()));
-    }
-} // namespace EMotionFX
-

+ 152 - 0
Gems/Meshlets/ASV_GPU_and_CPU_Demo/CMakeLists.txt

@@ -0,0 +1,152 @@
+#
+# 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
+#
+#
+
+ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Source/Platform/${PAL_PLATFORM_NAME})
+
+ly_add_target(
+    NAME AtomSampleViewer.Lib.Static STATIC
+    NAMESPACE Gem
+    FILES_CMAKE
+        atomsampleviewergem_lib_files.cmake
+    INCLUDE_DIRECTORIES
+        PUBLIC
+            Lib
+    BUILD_DEPENDENCIES
+        PUBLIC
+            AZ::AzGameFramework
+            Gem::Atom_AtomBridge.Static
+            Gem::Atom_RPI.Public
+            Gem::Atom_Utils.Static
+)
+
+ly_add_target(
+    NAME AtomSampleViewer.Private.Static STATIC
+    NAMESPACE Gem
+    FILES_CMAKE
+        atomsampleviewergem_private_files.cmake
+        ${pal_dir}/atomsampleviewer_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
+    PLATFORM_INCLUDE_FILES
+        ${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
+    INCLUDE_DIRECTORIES
+        PUBLIC
+            Lib
+            Source
+            ${pal_dir}
+    BUILD_DEPENDENCIES
+        PUBLIC
+            AZ::AzGameFramework
+            Gem::Atom_AtomBridge.Static
+            Gem::Atom_Feature_Common.Static
+            Gem::Atom_Component_DebugCamera.Static
+            Gem::AtomSampleViewer.Lib.Static
+            Gem::Meshlets.Static
+            Gem::Profiler.Static
+)
+
+ly_add_target(
+    NAME AtomSampleViewer ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
+    NAMESPACE Gem
+    FILES_CMAKE
+        atomsampleviewergem_private_shared_files.cmake
+        ../../atomsampleviewer_asset_files.cmake
+    PLATFORM_INCLUDE_FILES
+        ${pal_dir}/additional_${PAL_PLATFORM_NAME_LOWERCASE}_runtime_library.cmake
+    BUILD_DEPENDENCIES
+        PRIVATE
+            AZ::AzGameFramework
+            Gem::Atom_AtomBridge.Static
+            Gem::AtomSampleViewer.Private.Static
+            Gem::ImGui.imguilib
+            Gem::Meshlets.Static
+)
+
+# if enabled, AtomSampleViewer is used by the Client and ServerLauncher
+ly_create_alias(NAME AtomSampleViewer.Clients  NAMESPACE Gem TARGETS Gem::AtomSampleViewer)
+ly_create_alias(NAME AtomSampleViewer.Servers  NAMESPACE Gem TARGETS Gem::AtomSampleViewer)
+
+if(PAL_TRAIT_BUILD_HOST_TOOLS)
+
+    ly_add_target(
+        NAME AtomSampleViewer.Tools.Static STATIC
+        NAMESPACE Gem
+        FILES_CMAKE
+            atomsampleviewergem_tools_files.cmake
+        INCLUDE_DIRECTORIES
+            PUBLIC
+                .
+                Tools
+        BUILD_DEPENDENCIES
+            PRIVATE
+                AZ::AzGameFramework
+                Gem::Atom_AtomBridge.Static
+                Gem::AtomSampleViewer.Lib.Static
+            PUBLIC
+                Gem::Atom_RPI.Edit
+    )
+
+    ly_add_target(
+        NAME AtomSampleViewer.Tools GEM_MODULE
+
+        NAMESPACE Gem
+        FILES_CMAKE
+            atomsampleviewergem_tools_shared_files.cmake
+        COMPILE_DEFINITIONS
+            PUBLIC
+        INCLUDE_DIRECTORIES
+            PRIVATE
+                .
+                Source
+                Source/Platform/${PAL_PLATFORM_NAME}
+        BUILD_DEPENDENCIES
+            PUBLIC
+                AZ::AzCore
+                Gem::AtomSampleViewer.Tools.Static
+        RUNTIME_DEPENDENCIES
+            Gem::AtomSampleViewer
+    )
+
+    # The AtomSampleViewer.Tools target is the real GEM_MODULE target made above, but the AssetBuilder/AssetProcessor
+    # also needs that target, so alias the "Builders" variant to it
+    ly_create_alias(NAME AtomSampleViewer.Builders NAMESPACE Gem TARGETS Gem::AtomSampleViewer.Tools)
+endif()
+
+################################################################################
+# Tests
+################################################################################
+if(PAL_TRAIT_BUILD_SUPPORTS_TESTS)
+    ly_add_target(
+        NAME AtomSampleViewer.Tests MODULE
+
+        NAMESPACE Gem
+        FILES_CMAKE
+            atomsampleviewer_tests_files.cmake
+        INCLUDE_DIRECTORIES
+            PRIVATE
+                Tests
+        BUILD_DEPENDENCIES
+            PRIVATE
+                AZ::AzTest
+                Gem::AtomSampleViewer
+    )
+    ly_add_googletest(
+        NAME Gem::AtomSampleViewer.Tests
+        TARGET Gem::AtomSampleViewer.Tests
+    )
+endif()
+
+
+################################################################################
+# Gem dependencies
+################################################################################
+
+ly_enable_gems(PROJECT_NAME AtomSampleViewer GEM_FILE enabled_gems.cmake)
+
+# If we build a server, then apply the gems to the server
+if(PAL_TRAIT_BUILD_SERVER_SUPPORTED)
+    set_property(GLOBAL APPEND PROPERTY LY_LAUNCHER_SERVER_PROJECTS AtomSampleViewer)
+endif()

+ 16 - 0
Gems/Meshlets/ASV_GPU_and_CPU_Demo/DebugShaderMaterial_01.material

@@ -0,0 +1,16 @@
+{
+    "description": "",
+    "parentMaterial": "",
+    "materialType": "Materials/Types/DebugShaderPBR.materialtype",
+    "materialTypeVersion": 3,
+    "properties": {
+        "settings": {
+            "color": [
+                1.0,
+                0.0,
+                0.0,
+                1.0
+            ]
+        }
+    }
+}

+ 63 - 0
Gems/Meshlets/ASV_GPU_and_CPU_Demo/DebugShaderPBR.materialtype

@@ -0,0 +1,63 @@
+{
+    "description": "Base Material with properties used to define Standard PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model.",
+    "version": 3,
+    "propertyLayout": {
+        "groups": [
+            {
+                "name": "settings",
+                "displayName": "Settings"
+            }
+        ],
+        "properties": {
+            "settings": [
+                {
+                    "name": "color",
+                    "displayName": "Color",
+                    "type": "Color",
+                    "defaultValue": [ 1.0, 1.0, 1.0 ],
+                    "connection": {
+                        "type": "ShaderInput",
+                        "name": "m_baseColor"
+                    }
+                },
+                {
+                    "name": "metallic",
+                    "displayName": "Metallic",
+                    "type": "Float",
+                    "defaultValue": 0.0,
+                    "min": 0.0,
+                    "max": 1.0,
+                    "connection": {
+                        "type": "ShaderInput",
+                        "name": "m_metallic"
+                    }
+                },
+                {
+                    "name": "roughness",
+                    "displayName": "Roughness",
+                    "type": "Float",
+                    "defaultValue": 1.0,
+                    "min": 0.0,
+                    "max": 1.0,
+                    "connection": {
+                        "type": "ShaderInput",
+                        "name": "m_roughness"
+                    }
+                }
+            ]
+        }
+    },
+    "shaders": [
+        {
+            "file": "./DebugShaderPBR_ForwardPass.shader"
+        },
+        {
+            "file": "Shaders/Shadow/Shadowmap.shader"
+        },
+        {
+            "file": "Shaders/Depth/DepthPass.shader"
+        }
+    ],
+    "functors": [
+    ]
+}

+ 138 - 0
Gems/Meshlets/ASV_GPU_and_CPU_Demo/DebugShaderPBR_ForwardPass.azsl

@@ -0,0 +1,138 @@
+/*
+ * 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 <viewsrg.srgi>
+#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
+#include <Atom/Features/PBR/ForwardPassSrg.azsli>
+#include <Atom/Features/PBR/ForwardPassOutput.azsli>
+#include <Atom/Features/PBR/AlphaUtils.azsli>
+#include <Atom/Features/SrgSemantics.azsli>
+#include <Atom/Features/ColorManagement/TransformColor.azsli>
+#include <Atom/Features/PBR/Lighting/StandardLighting.azsli>
+#include <Atom/Features/PBR/Decals.azsli>
+
+ShaderResourceGroup DebugShaderPBRSrg : SRG_PerMaterial
+{
+    float3 m_baseColor;
+    float m_metallic;
+    float m_roughness;
+}
+
+/*
+* For better understanding of the meshlet data please review MeshletsData.h and 
+* specifically review MeshletDescriptor for understanding.
+* MESHLETS: meshlets descriptor structure containing vertices and indices offsets
+* MESHLETS_TRIANGLES: compressed indices of the local triangles per meshlet - each byte is a local index, 4th is not used.
+* MESHLETS_LOOKUP: vertices indirect index look up table.
+*/
+struct VSInput
+{
+    float3 m_position : POSITION;
+    float3 m_normal : NORMAL;
+    float4 m_tangent : TANGENT; 
+    float3 m_bitangent : BITANGENT; 
+    float2 m_uv : UV0;
+
+    uint4 m_meshlets : MESHLETS;
+    uint  m_meshletsTriangles : MESHLETS_TRIANGLES;
+    uint m_meshletsVtxLookup : MESHLETS_LOOKUP;
+};
+
+struct VSOutput
+{
+    float4 m_position : SV_Position;
+    float3 m_normal: NORMAL;
+    float3 m_tangent : TANGENT; 
+    float3 m_bitangent : BITANGENT; 
+    float3 m_worldPosition : UV0;
+    float2 m_uv : UV1;
+};
+
+#include <Atom/Features/Vertex/VertexHelper.azsli>
+
+VSOutput DebugShaderPBR_MainPassVS(VSInput IN)
+{
+    VSOutput OUT;
+ 
+    float3 worldPosition = mul(ObjectSrg::GetWorldMatrix(), float4(IN.m_position, 1.0)).xyz;
+ 
+    VertexHelper(IN, OUT, worldPosition);
+
+    OUT.m_uv = IN.m_uv;
+
+    return OUT;
+}
+
+ForwardPassOutput DebugShaderPBR_MainPassPS(VSOutput IN)
+{
+    // ------- Surface -------
+
+    Surface surface;
+    
+    // Position, Normal, Roughness
+    surface.position = IN.m_worldPosition.xyz;
+    surface.normal = normalize(IN.m_normal);
+    surface.vertexNormal = normalize(IN.m_normal);
+    surface.roughnessLinear = DebugShaderPBRSrg::m_roughness;
+    surface.CalculateRoughnessA();
+
+    // Albedo, SpecularF0
+    const float specularF0Factor = 0.5f;
+    surface.SetAlbedoAndSpecularF0(DebugShaderPBRSrg::m_baseColor, specularF0Factor, DebugShaderPBRSrg::m_metallic);
+
+    // Clear Coat, Transmission
+    surface.clearCoat.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);
+
+    // 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, IN.m_position);
+
+    // Apply Image Based Lighting (IBL)
+    ApplyIBL(surface, lightingData);
+
+    // Finalize Lighting
+    lightingData.FinalizeLighting();
+
+    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;
+
+    // Debug purposes - displays UV coordiantes as color. For meshlets this will 
+    // display the different meshlets groups.
+    OUT.m_diffuseColor = float4(IN.m_uv.rg, 0, 1);
+
+    return OUT;
+}
+

+ 46 - 0
Gems/Meshlets/ASV_GPU_and_CPU_Demo/DebugShaderPBR_ForwardPass.shader

@@ -0,0 +1,46 @@
+{
+    "Source" : "./DebugShaderPBR_ForwardPass.azsl",
+
+    "DepthStencilState" :
+    {
+        "Depth" :
+        {
+            "Enable" : true,
+            "CompareFunc" : "GreaterEqual"
+        },
+        "Stencil" :
+        {
+            "Enable" : true,
+            "ReadMask" : "0x00",
+            "WriteMask" : "0xFF",
+            "FrontFace" :
+            {
+                "Func" : "Always",
+                "DepthFailOp" : "Keep",
+                "FailOp" : "Keep",
+                "PassOp" : "Replace"
+            }
+        }
+    },
+
+    "CompilerHints" : { 
+        "DisableOptimizations" : false
+    },
+
+    "ProgramSettings":
+    {
+      "EntryPoints":
+      [
+        {
+          "name": "DebugShaderPBR_MainPassVS",
+          "type": "Vertex"
+        },
+        {
+          "name": "DebugShaderPBR_MainPassPS",
+          "type": "Fragment"
+        }
+      ]
+    },
+
+    "DrawList" : "forward"
+}

+ 419 - 0
Gems/Meshlets/ASV_GPU_and_CPU_Demo/LowEndPipeline.pass

@@ -0,0 +1,419 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "LowEndPipelineTemplate",
+            "PassClass": "ParentPass",
+            "Slots": [
+                {
+                    "Name": "SwapChainOutput",
+                    "SlotType": "InputOutput"
+                }
+            ],
+            "PassData": {
+                "$type": "PassData",
+                "PipelineGlobalConnections": [
+                    {
+                        "GlobalName": "SwapChainOutput",
+                        "Slot": "SwapChainOutput"
+                    }
+                ]
+            },
+            "PassRequests": [
+                {
+                    "Name": "MorphTargetPass",
+                    "TemplateName": "MorphTargetPassTemplate"
+                },
+                {
+                    "Name": "SkinningPass",
+                    "TemplateName": "SkinningPassTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "SkinnedMeshOutputStream",
+                            "AttachmentRef": {
+                                "Pass": "MorphTargetPass",
+                                "Attachment": "MorphTargetDeltaOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "DepthPrePass",
+                    "TemplateName": "DepthMSAAParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "SkinnedMeshes",
+                            "AttachmentRef": {
+                                "Pass": "SkinningPass",
+                                "Attachment": "SkinnedMeshOutputStream"
+                            }
+                        },
+                        {
+                            "LocalSlot": "SwapChainOutput",
+                            "AttachmentRef": {
+                                "Pass": "Parent",
+                                "Attachment": "SwapChainOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "LightCullingPass",
+                    "TemplateName": "LightCullingParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "SkinnedMeshes",
+                            "AttachmentRef": {
+                                "Pass": "SkinningPass",
+                                "Attachment": "SkinnedMeshOutputStream"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DepthMSAA",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthMSAA"
+                            }
+                        },
+                        {
+                            "LocalSlot": "SwapChainOutput",
+                            "AttachmentRef": {
+                                "Pass": "Parent",
+                                "Attachment": "SwapChainOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "ShadowPass",
+                    "TemplateName": "ShadowParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "SkinnedMeshes",
+                            "AttachmentRef": {
+                                "Pass": "SkinningPass",
+                                "Attachment": "SkinnedMeshOutputStream"
+                            }
+                        },
+                        {
+                            "LocalSlot": "SwapChainOutput",
+                            "AttachmentRef": {
+                                "Pass": "Parent",
+                                "Attachment": "SwapChainOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "Depth",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthMSAA" 
+                            }
+                        }                           
+                    ]
+                },
+                {
+                    "Name": "ForwardPass",
+                    "TemplateName": "LowEndForwardPassTemplate",
+                    "Connections": [
+                        // Inputs...
+                        {
+                            "LocalSlot": "DirectionalLightShadowmap",
+                            "AttachmentRef": {
+                                "Pass": "ShadowPass",
+                                "Attachment": "DirectionalShadowmap"
+                            }
+                        },
+                        {
+                            "LocalSlot": "ExponentialShadowmapDirectional",
+                            "AttachmentRef": {
+                                "Pass": "ShadowPass",
+                                "Attachment": "DirectionalESM"
+                            }
+                        },
+                        {
+                            "LocalSlot": "ProjectedShadowmap",
+                            "AttachmentRef": {
+                                "Pass": "ShadowPass",
+                                "Attachment": "ProjectedShadowmap"
+                            }
+                        },
+                        {
+                            "LocalSlot": "ExponentialShadowmapProjected",
+                            "AttachmentRef": {
+                                "Pass": "ShadowPass",
+                                "Attachment": "ProjectedESM"
+                            }
+                        },
+                        {
+                            "LocalSlot": "TileLightData",
+                            "AttachmentRef": {
+                                "Pass": "LightCullingPass",
+                                "Attachment": "TileLightData"
+                            }
+                        },
+                        {
+                            "LocalSlot": "LightListRemapped",
+                            "AttachmentRef": {
+                                "Pass": "LightCullingPass",
+                                "Attachment": "LightListRemapped"
+                            }
+                        },
+                        // Input/Outputs...
+                        {
+                            "LocalSlot": "DepthStencilInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthMSAA"
+                            }
+                        }
+                    ],
+                    "PassData": {
+                        "$type": "RasterPassData",
+                        "DrawListTag": "lowEndForward",
+                        "PipelineViewTag": "MainCamera",
+                        "PassSrgShaderAsset": {
+                            "FilePath": "Shaders/ForwardPassSrg.shader"
+                        }
+                    }
+                },
+                {
+                    "Name": "SkyBoxPass",
+                    "TemplateName": "SkyBoxTemplate",
+                    "Enabled": true,
+                    "Connections": [
+                        {
+                            "LocalSlot": "SpecularInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "ForwardPass",
+                                "Attachment": "LightingOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "SkyBoxDepth",
+                            "AttachmentRef": {
+                                "Pass": "ForwardPass",
+                                "Attachment": "DepthStencilInputOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "MSAAResolvePass",
+                    "TemplateName": "MSAAResolveColorTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "Input",
+                            "AttachmentRef": {
+                                "Pass": "SkyBoxPass",
+                                "Attachment": "SpecularInputOutput"
+                            }
+                        }
+                    ]
+                },
+
+                //================================================
+                {
+                    "Name": "MeshletsComputePass",
+                    "TemplateName": "MeshletsComputePassTemplate",
+                    "Connections": [
+                    ],
+                    "PassData": {
+                        "$type": "ComputePassData",
+                        "ShaderAsset": {
+                            "FilePath": "Shaders/MeshletsCompute.shader"
+                        }
+                    }
+                },
+                {
+                    "Name": "MeshletsRenderPass",
+                    "TemplateName": "MeshletsRenderPassTemplate",
+                    "Connections": [
+                        // Input/Outputs...
+                        {
+                            "LocalSlot": "DepthStencilInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "Depth"
+                                //                                "Attachment": "DepthMSAA"
+                            }
+                        },
+                        {
+                            "LocalSlot": "RenderTargetInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "MSAAResolvePass",
+                                "Attachment": "Output"
+                            }
+                        }
+                    ],
+                    "PassData": {
+                        "$type": "RasterPassData",
+                        "DrawListTag": "MeshletsDrawList",
+                        "PipelineViewTag": "MainCamera",
+                        "PassSrgShaderAsset": {
+                            "FilePath": "Shaders/MeshletsDebugRenderShader.shader"
+                        }
+                    }
+                },
+                //================================================
+
+                {
+                    "Name": "TransparentPass",
+                    "TemplateName": "TransparentParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "DirectionalShadowmap",
+                            "AttachmentRef": {
+                                "Pass": "ShadowPass",
+                                "Attachment": "DirectionalShadowmap"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DirectionalESM",
+                            "AttachmentRef": {
+                                "Pass": "ShadowPass",
+                                "Attachment": "DirectionalESM"
+                            }
+                        },
+                        {
+                            "LocalSlot": "ProjectedShadowmap",
+                            "AttachmentRef": {
+                                "Pass": "ShadowPass",
+                                "Attachment": "ProjectedShadowmap"
+                            }
+                        },
+                        {
+                            "LocalSlot": "ProjectedESM",
+                            "AttachmentRef": {
+                                "Pass": "ShadowPass",
+                                "Attachment": "ProjectedESM"
+                            }
+                        },
+                        {
+                            "LocalSlot": "TileLightData",
+                            "AttachmentRef": {
+                                "Pass": "LightCullingPass",
+                                "Attachment": "TileLightData"
+                            }
+                        },
+                        {
+                            "LocalSlot": "LightListRemapped",
+                            "AttachmentRef": {
+                                "Pass": "LightCullingPass",
+                                "Attachment": "LightListRemapped"
+                            }
+                        },
+                        {
+                            "LocalSlot": "InputLinearDepth",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthLinear"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DepthStencil",
+                            "AttachmentRef": {
+                                "Pass": "MeshletsRenderPass",
+                                "Attachment": "DepthStencilInputOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "InputOutput",
+                            "AttachmentRef": {
+                                "Pass": "MeshletsRenderPass",
+                                "Attachment": "RenderTargetInputOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "LightAdaptation",
+                    "TemplateName": "LightAdaptationParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "LightingInput",
+                            "AttachmentRef": {
+                                "Pass": "TransparentPass",
+                                "Attachment": "InputOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "SwapChainOutput",
+                            "AttachmentRef": {
+                                "Pass": "Parent",
+                                "Attachment": "SwapChainOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "AuxGeomPass",
+                    "TemplateName": "AuxGeomPassTemplate",
+                    "Enabled": true,
+                    "Connections": [
+                        {
+                            "LocalSlot": "ColorInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "LightAdaptation",
+                                "Attachment": "Output"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DepthInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "Depth"
+                            }
+                        }
+                    ],
+                    "PassData": {
+                        "$type": "RasterPassData",
+                        "DrawListTag": "auxgeom",
+                        "PipelineViewTag": "MainCamera"
+                    }
+                },
+                {
+                    "Name": "UIPass",
+                    "TemplateName": "UIParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "InputOutput",
+                            "AttachmentRef": {
+                                "Pass": "AuxGeomPass",
+                                "Attachment": "ColorInputOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DepthInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "Depth"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "CopyToSwapChain",
+                    "TemplateName": "FullscreenCopyTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "Input",
+                            "AttachmentRef": {
+                                "Pass": "UIPass",
+                                "Attachment": "InputOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "Output",
+                            "AttachmentRef": {
+                                "Pass": "Parent",
+                                "Attachment": "SwapChainOutput"
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}

+ 550 - 0
Gems/Meshlets/ASV_GPU_and_CPU_Demo/MainPipeline.pass

@@ -0,0 +1,550 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "MainPipeline",
+            "PassClass": "ParentPass",
+            "Slots": [
+                {
+                    "Name": "SwapChainOutput",
+                    "SlotType": "InputOutput"
+                }
+            ],
+            "PassData": {
+                "$type": "PassData",
+                "PipelineGlobalConnections": [
+                    {
+                        "GlobalName": "SwapChainOutput",
+                        "Slot": "SwapChainOutput"
+                    }
+                ]
+            },
+            "PassRequests": [
+                {
+                    "Name": "MorphTargetPass",
+                    "TemplateName": "MorphTargetPassTemplate"
+                },
+                {
+                    "Name": "SkinningPass",
+                    "TemplateName": "SkinningPassTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "SkinnedMeshOutputStream",
+                            "AttachmentRef": {
+                                "Pass": "MorphTargetPass",
+                                "Attachment": "MorphTargetDeltaOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "RayTracingAccelerationStructurePass",
+                    "TemplateName": "RayTracingAccelerationStructurePassTemplate"
+                },
+                {
+                    "Name": "DiffuseProbeGridUpdatePass",
+                    "TemplateName": "DiffuseProbeGridUpdatePassTemplate",
+                    "ExecuteAfter": [
+                        "RayTracingAccelerationStructurePass"
+                    ]
+                },
+                {
+                    "Name": "DepthPrePass",
+                    "TemplateName": "DepthMSAAParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "SkinnedMeshes",
+                            "AttachmentRef": {
+                                "Pass": "SkinningPass",
+                                "Attachment": "SkinnedMeshOutputStream"
+                            }
+                        },
+                        {
+                            "LocalSlot": "SwapChainOutput",
+                            "AttachmentRef": {
+                                "Pass": "PipelineGlobal",
+                                "Attachment": "SwapChainOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "MotionVectorPass",
+                    "TemplateName": "MotionVectorParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "SkinnedMeshes",
+                            "AttachmentRef": {
+                                "Pass": "SkinningPass",
+                                "Attachment": "SkinnedMeshOutputStream"
+                            }
+                        },
+                        {
+                            "LocalSlot": "Depth",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "Depth"
+                            }
+                        },
+                        {
+                            "LocalSlot": "SwapChainOutput",
+                            "AttachmentRef": {
+                                "Pass": "PipelineGlobal",
+                                "Attachment": "SwapChainOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "LightCullingPass",
+                    "TemplateName": "LightCullingParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "SkinnedMeshes",
+                            "AttachmentRef": {
+                                "Pass": "SkinningPass",
+                                "Attachment": "SkinnedMeshOutputStream"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DepthMSAA",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthMSAA"
+                            }
+                        },
+                        {
+                            "LocalSlot": "SwapChainOutput",
+                            "AttachmentRef": {
+                                "Pass": "PipelineGlobal",
+                                "Attachment": "SwapChainOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "ShadowPass",
+                    "TemplateName": "ShadowParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "SkinnedMeshes",
+                            "AttachmentRef": {
+                                "Pass": "SkinningPass",
+                                "Attachment": "SkinnedMeshOutputStream"
+                            }
+                        },
+                        {
+                            "LocalSlot": "SwapChainOutput",
+                            "AttachmentRef": {
+                                "Pass": "PipelineGlobal",
+                                "Attachment": "SwapChainOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "Depth",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthMSAA" 
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "OpaquePass",
+                    "TemplateName": "OpaqueParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "DirectionalShadowmap",
+                            "AttachmentRef": {
+                                "Pass": "ShadowPass",
+                                "Attachment": "DirectionalShadowmap"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DirectionalESM",
+                            "AttachmentRef": {
+                                "Pass": "ShadowPass",
+                                "Attachment": "DirectionalESM"
+                            }
+                        },
+                        {
+                            "LocalSlot": "ProjectedShadowmap",
+                            "AttachmentRef": {
+                                "Pass": "ShadowPass",
+                                "Attachment": "ProjectedShadowmap"
+                            }
+                        },
+                        {
+                            "LocalSlot": "ProjectedESM",
+                            "AttachmentRef": {
+                                "Pass": "ShadowPass",
+                                "Attachment": "ProjectedESM"
+                            }
+                        },
+                        {
+                            "LocalSlot": "TileLightData",
+                            "AttachmentRef": {
+                                "Pass": "LightCullingPass",
+                                "Attachment": "TileLightData"
+                            }
+                        },
+                        {
+                            "LocalSlot": "LightListRemapped",
+                            "AttachmentRef": {
+                                "Pass": "LightCullingPass",
+                                "Attachment": "LightListRemapped"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DepthLinear",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthLinear"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DepthStencil",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthMSAA"
+                            }
+                        },
+                        {
+                            "LocalSlot": "SwapChainOutput",
+                            "AttachmentRef": {
+                                "Pass": "PipelineGlobal",
+                                "Attachment": "SwapChainOutput"
+                            }
+                        }
+                    ]
+                },
+
+                //================================================
+                {
+                    "Name": "MeshletsComputePass",
+                    "TemplateName": "MeshletsComputePassTemplate",
+                    "Connections": [
+                    ],
+                    "PassData": {
+                        "$type": "ComputePassData",
+                        "ShaderAsset": {
+                            "FilePath": "Shaders/MeshletsCompute.shader"
+                        }
+                    }
+                },
+                {
+                    "Name": "MeshletsRenderPass",
+                    "TemplateName": "MeshletsRenderPassTemplate",
+                    "Connections": [
+                        // Input/Outputs...
+                        {
+                            "LocalSlot": "DepthStencilInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "Depth"
+                            }
+                        },
+                        {
+                            "LocalSlot": "RenderTargetInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "OpaquePass",
+                                "Attachment": "Output"
+                            }
+                        }
+                    ],
+                    "PassData": {
+                        "$type": "RasterPassData",
+                        "DrawListTag": "MeshletsDrawList",
+                        "PipelineViewTag": "MainCamera",
+                        "PassSrgShaderAsset": {
+                            "FilePath": "Shaders/MeshletsDebugRenderShader.shader"
+                        }
+                    }
+                },
+                //================================================
+
+                {
+                    "Name": "TransparentPass",
+                    "TemplateName": "TransparentParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "DirectionalShadowmap",
+                            "AttachmentRef": {
+                                "Pass": "ShadowPass",
+                                "Attachment": "DirectionalShadowmap"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DirectionalESM",
+                            "AttachmentRef": {
+                                "Pass": "ShadowPass",
+                                "Attachment": "DirectionalESM"
+                            }
+                        },
+                        {
+                            "LocalSlot": "ProjectedShadowmap",
+                            "AttachmentRef": {
+                                "Pass": "ShadowPass",
+                                "Attachment": "ProjectedShadowmap"
+                            }
+                        },
+                        {
+                            "LocalSlot": "ProjectedESM",
+                            "AttachmentRef": {
+                                "Pass": "ShadowPass",
+                                "Attachment": "ProjectedESM"
+                            }
+                        },
+                        {
+                            "LocalSlot": "TileLightData",
+                            "AttachmentRef": {
+                                "Pass": "LightCullingPass",
+                                "Attachment": "TileLightData"
+                            }
+                        },
+                        {
+                            "LocalSlot": "LightListRemapped",
+                            "AttachmentRef": {
+                                "Pass": "LightCullingPass",
+                                "Attachment": "LightListRemapped"
+                            }
+                        },
+                        {
+                            "LocalSlot": "InputLinearDepth",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthLinear"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DepthStencil",
+                            "AttachmentRef": {
+                                "Pass": "MeshletsRenderPass",
+                                "Attachment": "DepthStencilInputOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "InputOutput",
+                            "AttachmentRef": {
+                                "Pass": "MeshletsRenderPass",
+                                "Attachment": "RenderTargetInputOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "DeferredFogPass",
+                    "TemplateName": "DeferredFogPassTemplate",
+                    "Enabled": false,
+                    "Connections": [
+                        {
+                            "LocalSlot": "InputLinearDepth",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthLinear"
+                            }
+                        },
+                        {
+                            "LocalSlot": "InputDepthStencil",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "Depth"
+                            }
+                        },
+                        {
+                            "LocalSlot": "RenderTargetInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "TransparentPass",
+                                "Attachment": "InputOutput"
+                            }
+                        }
+                    ],
+                    "PassData": {
+                        "$type": "FullscreenTrianglePassData",
+                        "ShaderAsset": {
+                            "FilePath": "Shaders/ScreenSpace/DeferredFog.shader"
+                        },
+                        "PipelineViewTag": "MainCamera"
+                    }
+                },
+                {
+                    "Name": "ReflectionCopyFrameBufferPass",
+                    "TemplateName": "ReflectionCopyFrameBufferPassTemplate",
+                    "Enabled": false,
+                    "Connections": [
+                        {
+                            "LocalSlot": "Input",
+                            "AttachmentRef": {
+                                "Pass": "DeferredFogPass",
+                                "Attachment": "RenderTargetInputOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "PostProcessPass",
+                    "TemplateName": "PostProcessParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "LightingInput",
+                            "AttachmentRef": {
+                                "Pass": "DeferredFogPass",
+                                "Attachment": "RenderTargetInputOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "Depth",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "Depth"
+                            }
+                        },
+                        {
+                            "LocalSlot": "MotionVectors",
+                            "AttachmentRef": {
+                                "Pass": "MotionVectorPass",
+                                "Attachment": "MotionVectorOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "SwapChainOutput",
+                            "AttachmentRef": {
+                                "Pass": "PipelineGlobal",
+                                "Attachment": "SwapChainOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "DiffuseProbeGridVisualizationCompositePass",
+                    "TemplateName": "DiffuseProbeGridVisualizationCompositePassTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "VisualizationInput",
+                            "AttachmentRef": {
+                                "Pass": "OpaquePass",
+                                "Attachment": "DiffuseProbeGridVisualization"
+                            }
+                        },
+                        {
+                            "LocalSlot": "Depth",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "Depth"
+                            }
+                        },
+                        {
+                            "LocalSlot": "ColorInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "PostProcessPass",
+                                "Attachment": "Output"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "AuxGeomPass",
+                    "TemplateName": "AuxGeomPassTemplate",
+                    "Enabled": true,
+                    "Connections": [
+                        {
+                            "LocalSlot": "ColorInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "DiffuseProbeGridVisualizationCompositePass",
+                                "Attachment": "ColorInputOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DepthInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "Depth"
+                            }
+                        }
+                    ],
+                    "PassData": {
+                        "$type": "RasterPassData",
+                        "DrawListTag": "auxgeom",
+                        "PipelineViewTag": "MainCamera"
+                    }
+                },
+                {
+                    "Name": "DebugOverlayPass",
+                    "TemplateName": "DebugOverlayParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "TileLightData",
+                            "AttachmentRef": {
+                                "Pass": "LightCullingPass",
+                                "Attachment": "TileLightData"
+                            }
+                        },
+                        {
+                            "LocalSlot": "RawLightingInput",
+                            "AttachmentRef": {
+                                "Pass": "PostProcessPass",
+                                "Attachment": "RawLightingOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "LuminanceMipChainInput",
+                            "AttachmentRef": {
+                                "Pass": "PostProcessPass",
+                                "Attachment": "LuminanceMipChainOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "InputOutput",
+                            "AttachmentRef": {
+                                "Pass": "AuxGeomPass",
+                                "Attachment": "ColorInputOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "UIPass",
+                    "TemplateName": "UIParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "InputOutput",
+                            "AttachmentRef": {
+                                "Pass": "DebugOverlayPass",
+                                "Attachment": "InputOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DepthInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "Depth"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "CopyToSwapChain",
+                    "TemplateName": "FullscreenCopyTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "Input",
+                            "AttachmentRef": {
+                                "Pass": "UIPass",
+                                "Attachment": "InputOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "Output",
+                            "AttachmentRef": {
+                                "Pass": "PipelineGlobal",
+                                "Attachment": "SwapChainOutput"
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}

+ 517 - 0
Gems/Meshlets/ASV_GPU_and_CPU_Demo/MeshletsExampleComponent.cpp

@@ -0,0 +1,517 @@
+/*
+ * 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 <MeshletsExampleComponent.h>
+
+#include <Atom/Component/DebugCamera/ArcBallControllerComponent.h>
+#include <Atom/Component/DebugCamera/NoClipControllerComponent.h>
+
+#include <Atom/RHI/Device.h>
+#include <Atom/RHI/Factory.h>
+
+#include <Atom/RPI.Public/View.h>
+
+#include <Atom/RPI.Reflect/Model/ModelAsset.h>
+#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
+#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
+
+#include <AzCore/Asset/AssetManagerBus.h>
+#include <AzCore/Component/Entity.h>
+#include <AzCore/IO/IOUtils.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <AzCore/std/smart_ptr/make_shared.h>
+#include <AzCore/std/sort.h>
+
+#include <AzFramework/Components/TransformComponent.h>
+#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
+
+#include <SampleComponentManager.h>
+#include <SampleComponentConfig.h>
+#include <EntityUtilityFunctions.h>
+
+#include <Automation/ScriptableImGui.h>
+#include <Automation/ScriptRunnerBus.h>
+
+#include <RHI/BasicRHIComponent.h>
+
+#include <MeshletsFeatureProcessor.h>
+
+namespace AtomSampleViewer
+{
+    const char* MeshletsExampleComponent::CameraControllerNameTable[CameraControllerCount] =
+    {
+        "ArcBall",
+        "NoClip"
+    };
+
+    void MeshletsExampleComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<MeshletsExampleComponent, AZ::Component>()
+                ->Version(0)
+                ;
+        }
+    }
+
+    MeshletsExampleComponent::MeshletsExampleComponent()
+        : m_materialBrowser("@user@/MeshExampleComponent/material_browser.xml")
+        , m_modelBrowser("@user@/MeshExampleComponent/model_browser.xml")
+        , m_imguiSidebar("@user@/MeshExampleComponent/sidebar.xml")
+    {
+        m_changedHandler = AZ::Render::MeshFeatureProcessorInterface::ModelChangedEvent::Handler
+        {
+            [&](AZ::Data::Instance<AZ::RPI::Model> model)
+            {
+                ScriptRunnerRequestBus::Broadcast(&ScriptRunnerRequests::ResumeScript);
+
+                // This handler will be connected to the feature processor so that when the model is updated, the camera
+                // controller will reset. This ensures the camera is a reasonable distance from the model when it resizes.
+                ResetCameraController();
+
+                UpdateGroundPlane();
+            }
+        };
+    }
+
+    void MeshletsExampleComponent::DefaultWindowCreated()
+    {
+        AZ::Render::Bootstrap::DefaultWindowBus::BroadcastResult(m_windowContext, &AZ::Render::Bootstrap::DefaultWindowBus::Events::GetDefaultWindowContext);
+    }
+
+    void MeshletsExampleComponent::CreateLowEndPipeline()
+    {
+        AZ::RPI::RenderPipelineDescriptor pipelineDesc;
+        pipelineDesc.m_mainViewTagName = "MainCamera";
+        pipelineDesc.m_name = "LowEndPipeline";
+        pipelineDesc.m_rootPassTemplate = "LowEndPipelineTemplate";     // The active pipeline for this example
+        pipelineDesc.m_renderSettings.m_multisampleState.m_samples = 4;
+
+        m_lowEndPipeline = AZ::RPI::RenderPipeline::CreateRenderPipelineForWindow(pipelineDesc, *m_windowContext);
+    }
+
+    void MeshletsExampleComponent::DestroyLowEndPipeline()
+    {
+        m_lowEndPipeline = nullptr;
+    }
+
+    void MeshletsExampleComponent::ActivateLowEndPipeline()
+    {
+        m_originalPipeline = m_scene->GetDefaultRenderPipeline();
+        m_scene->AddRenderPipeline(m_lowEndPipeline);
+        m_lowEndPipeline->SetDefaultView(m_originalPipeline->GetDefaultView());
+        m_scene->RemoveRenderPipeline(m_originalPipeline->GetId());
+
+        m_imguiScope = AZ::Render::ImGuiActiveContextScope::FromPass({ m_lowEndPipeline->GetId().GetCStr(), "ImGuiPass" });
+    }
+
+    void MeshletsExampleComponent::DeactivateLowEndPipeline()
+    {
+        m_imguiScope = {}; // restores previous ImGui context.
+
+        m_scene->AddRenderPipeline(m_originalPipeline);
+        m_scene->RemoveRenderPipeline(m_lowEndPipeline->GetId());
+    }
+
+    void MeshletsExampleComponent::Activate()
+    {
+        UseArcBallCameraController();
+
+        m_materialBrowser.SetFilter([this](const AZ::Data::AssetInfo& assetInfo)
+        {
+            if (!AzFramework::StringFunc::Path::IsExtension(assetInfo.m_relativePath.c_str(), "azmaterial"))
+            {
+                return false;
+            }
+            if (m_showModelMaterials)
+            {
+                return true;
+            }
+            // Return true only if the azmaterial was generated from a ".material" file.
+            // Materials with subid == 0, are 99.99% guaranteed to be generated from a ".material" file.
+            // Without this assurance We would need to call  AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourceUUID()
+            // to figure out what's the source of this azmaterial. But, Atom can not include AzToolsFramework.
+            return assetInfo.m_assetId.m_subId == 0;
+        });
+
+        m_modelBrowser.SetFilter([](const AZ::Data::AssetInfo& assetInfo)
+        {
+            return assetInfo.m_assetType == azrtti_typeid<AZ::RPI::ModelAsset>();
+        });
+
+        m_materialBrowser.Activate();
+        m_modelBrowser.Activate();
+        m_imguiSidebar.Activate();
+
+        InitLightingPresets(true);
+
+        AZ::Data::Asset<AZ::RPI::MaterialAsset> groundPlaneMaterialAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath<AZ::RPI::MaterialAsset>(DefaultPbrMaterialPath, AZ::RPI::AssetUtils::TraceLevel::Error);
+        m_groundPlaneMaterial = AZ::RPI::Material::FindOrCreate(groundPlaneMaterialAsset);
+        m_groundPlaneModelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>("objects/plane.azmodel", AZ::RPI::AssetUtils::TraceLevel::Assert);
+
+        AZ::TickBus::Handler::BusConnect();
+        AZ::Render::Bootstrap::DefaultWindowNotificationBus::Handler::BusConnect();
+        CreateLowEndPipeline();
+    }
+
+    void MeshletsExampleComponent::Deactivate()
+    {
+        if (m_useLowEndPipeline)
+        {
+            DeactivateLowEndPipeline();
+        }
+        DestroyLowEndPipeline();
+        AZ::Render::Bootstrap::DefaultWindowNotificationBus::Handler::BusDisconnect();
+        AZ::TickBus::Handler::BusDisconnect();
+
+        m_imguiSidebar.Deactivate();
+
+        m_materialBrowser.Deactivate();
+        m_modelBrowser.Deactivate();
+
+        RemoveController();
+        
+        GetMeshFeatureProcessor()->ReleaseMesh(m_meshHandle);
+        GetMeshFeatureProcessor()->ReleaseMesh(m_groundPlandMeshHandle);
+
+        if (m_meshetsModel)
+        {
+            GetMeshFeatureProcessor()->ReleaseMesh(m_meshletsMeshHandle);
+            delete m_meshetsModel;
+            m_meshetsModel = nullptr;
+
+            if (GetMeshletsFeatureProcessor())
+            {
+                m_meshletsFeatureProcessor->RemoveMeshletsRenderObject(m_meshetsRenderObject);
+                // No deletion - this will be done by the feature processor
+                m_meshetsRenderObject = nullptr;
+            }
+        }
+
+        m_modelAsset = {};
+        m_groundPlaneModelAsset = {};
+
+        m_materialOverrideInstance = nullptr;
+
+        ShutdownLightingPresets();
+    }
+
+    AZ::Meshlets::MeshletsFeatureProcessor* MeshletsExampleComponent::GetMeshletsFeatureProcessor()
+    {
+        if (!m_meshletsFeatureProcessor && m_scene)
+        {
+            m_meshletsFeatureProcessor = m_scene->GetFeatureProcessor<AZ::Meshlets::MeshletsFeatureProcessor>();
+        }
+
+        return m_meshletsFeatureProcessor;
+    }
+
+    void MeshletsExampleComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
+    {
+        bool modelNeedsUpdate = false;
+
+        // Switch pipeline before any imGui actions (switching pipelines switches imGui scope)
+        if (m_switchPipeline)
+        {
+            if (m_useLowEndPipeline)
+            {
+                ActivateLowEndPipeline();
+            }
+            else
+            {
+                DeactivateLowEndPipeline();
+            }
+
+            m_switchPipeline = false;
+        }
+
+        if (m_imguiSidebar.Begin())
+        {
+            ImGuiLightingPreset();
+
+            ImGuiAssetBrowser::WidgetSettings assetBrowserSettings;
+
+            m_switchPipeline = ScriptableImGui::Checkbox("Use Low End Pipeline", &m_useLowEndPipeline) || m_switchPipeline;
+
+            modelNeedsUpdate |= ScriptableImGui::Checkbox("Enable Material Override", &m_enableMaterialOverride);
+           
+            if (ScriptableImGui::Checkbox("Show Ground Plane", &m_showGroundPlane))
+            {
+                if (m_showGroundPlane)
+                {
+                    CreateGroundPlane();
+                    UpdateGroundPlane();
+                }
+                else
+                {
+                    RemoveGroundPlane();
+                }
+            }
+
+            if (ScriptableImGui::Checkbox("Show Model Materials", &m_showModelMaterials))
+            {
+                modelNeedsUpdate = true;
+                m_materialBrowser.SetNeedsRefresh();
+            }
+
+            assetBrowserSettings.m_labels.m_root = "Materials";
+            modelNeedsUpdate |= m_materialBrowser.Tick(assetBrowserSettings);
+
+            ImGui::Spacing();
+            ImGui::Separator();
+            ImGui::Spacing();
+
+            assetBrowserSettings.m_labels.m_root = "Models";
+            bool modelChanged = m_modelBrowser.Tick(assetBrowserSettings);
+            modelNeedsUpdate |= modelChanged;
+
+            if (modelChanged)
+            {
+                // Reset LOD override when the model changes.
+                m_lodConfig.m_lodType = AZ::RPI::Cullable::LodType::Default;
+            }
+
+            ImGui::Spacing();
+            ImGui::Separator();
+            ImGui::Spacing();
+
+            // Camera controls
+            {
+                int32_t* currentControllerTypeIndex = reinterpret_cast<int32_t*>(&m_currentCameraControllerType);
+
+                ImGui::LabelText("##CameraControllerLabel", "Camera Controller:");
+                if (ScriptableImGui::Combo("##CameraController", currentControllerTypeIndex, CameraControllerNameTable, CameraControllerCount))
+                {
+                    ResetCameraController();
+                }
+            }
+
+            ImGui::Spacing();
+            ImGui::Separator();
+            ImGui::Spacing();
+
+            if (m_materialOverrideInstance && ImGui::Button("Material Details..."))
+            {
+                m_imguiMaterialDetails.SetMaterial(m_materialOverrideInstance);
+                m_imguiMaterialDetails.OpenDialog();
+            }
+
+            m_imguiSidebar.End();
+        }
+
+        m_imguiMaterialDetails.Tick();
+
+        if (modelNeedsUpdate)
+        {
+            ModelChange();
+        }
+    }
+
+    void MeshletsExampleComponent::ModelChange()
+    {
+        if (!m_modelBrowser.GetSelectedAssetId().IsValid())
+        {
+            m_modelAsset = {};
+            GetMeshFeatureProcessor()->ReleaseMesh(m_meshHandle);
+            GetMeshFeatureProcessor()->ReleaseMesh(m_meshletsMeshHandle);
+
+            if (GetMeshletsFeatureProcessor())
+            {
+                m_meshletsFeatureProcessor->RemoveMeshletsRenderObject(m_meshetsRenderObject);
+                // No deletion - this will be done by the feature processor
+                m_meshetsRenderObject = nullptr;
+            }
+
+            return;
+        }
+
+        // If a material hasn't been selected, just choose the first one
+        // If for some reason no materials are available log an error
+        AZ::Data::AssetId selectedMaterialAssetId = m_materialBrowser.GetSelectedAssetId();
+        if (!selectedMaterialAssetId.IsValid())
+        {
+            selectedMaterialAssetId = AZ::RPI::AssetUtils::GetAssetIdForProductPath(DefaultPbrMaterialPath, AZ::RPI::AssetUtils::TraceLevel::Error);
+
+            if (!selectedMaterialAssetId.IsValid())
+            {
+                AZ_Error("MeshExampleComponent", false, "Failed to select model, no material available to render with.");
+                return;
+            }
+        }
+
+        if (m_enableMaterialOverride && selectedMaterialAssetId.IsValid())
+        {
+            AZ::Data::Asset<AZ::RPI::MaterialAsset> materialAsset;
+            materialAsset.Create(selectedMaterialAssetId);
+            m_materialOverrideInstance = AZ::RPI::Material::FindOrCreate(materialAsset);
+        }
+        else
+        {
+            m_materialOverrideInstance = nullptr;
+        }
+
+
+        if (m_modelAsset.GetId() != m_modelBrowser.GetSelectedAssetId())
+        {
+            ScriptRunnerRequestBus::Broadcast(&ScriptRunnerRequests::PauseScript);
+
+            m_modelAsset.Create(m_modelBrowser.GetSelectedAssetId());
+
+            GetMeshFeatureProcessor()->ReleaseMesh(m_meshHandle);
+
+            if (m_meshetsModel)
+            {   // delete the meshlet model so it will be recreated on the next tick
+                if (GetMeshletsFeatureProcessor())
+                {
+                    m_meshletsFeatureProcessor->RemoveMeshletsRenderObject(m_meshetsRenderObject);
+                    // No deletion - this will be done by the feature processor
+                    m_meshetsRenderObject = nullptr;
+                }
+
+                GetMeshFeatureProcessor()->ReleaseMesh(m_meshletsMeshHandle);
+                delete m_meshetsModel;
+                m_meshetsModel = nullptr;
+            }
+
+            m_meshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ m_modelAsset }, m_materialOverrideInstance);
+
+            GetMeshFeatureProcessor()->SetTransform(m_meshHandle, AZ::Transform::CreateIdentity());
+            GetMeshFeatureProcessor()->ConnectModelChangeEventHandler(m_meshHandle, m_changedHandler);
+            GetMeshFeatureProcessor()->SetMeshLodConfiguration(m_meshHandle, m_lodConfig);
+        }
+        else
+        {
+            GetMeshFeatureProcessor()->SetMaterialAssignmentMap(m_meshHandle, m_materialOverrideInstance);
+        }
+    }
+    
+    void MeshletsExampleComponent::CreateGroundPlane()
+    {
+        m_groundPlandMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ m_groundPlaneModelAsset }, m_groundPlaneMaterial);
+    }
+
+    void MeshletsExampleComponent::UpdateGroundPlane()
+    {
+        if (m_groundPlandMeshHandle.IsValid())
+        {
+            AZ::Transform groundPlaneTransform = AZ::Transform::CreateIdentity();
+
+            if (m_modelAsset)
+            {
+                AZ::Vector3 modelCenter;
+                float modelRadius;
+                m_modelAsset->GetAabb().GetAsSphere(modelCenter, modelRadius);
+
+                static const float GroundPlaneRelativeScale = 4.0f;
+                static const float GroundPlaneOffset = 0.01f;
+
+                groundPlaneTransform.SetUniformScale(GroundPlaneRelativeScale * modelRadius);
+                groundPlaneTransform.SetTranslation(AZ::Vector3(0.0f, 0.0f, m_modelAsset->GetAabb().GetMin().GetZ() - GroundPlaneOffset));
+            }
+
+            GetMeshFeatureProcessor()->SetTransform(m_groundPlandMeshHandle, groundPlaneTransform);
+        }
+    }
+
+    void MeshletsExampleComponent::RemoveGroundPlane()
+    {
+        GetMeshFeatureProcessor()->ReleaseMesh(m_groundPlandMeshHandle);
+    }
+
+    void MeshletsExampleComponent::OnEntityDestruction(const AZ::EntityId& entityId)
+    {
+        AZ::EntityBus::MultiHandler::BusDisconnect(entityId);
+    }
+
+    void MeshletsExampleComponent::UseArcBallCameraController()
+    {
+        AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Enable,
+            azrtti_typeid<AZ::Debug::ArcBallControllerComponent>());
+    }
+
+    void MeshletsExampleComponent::UseNoClipCameraController()
+    {
+        AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Enable,
+            azrtti_typeid<AZ::Debug::NoClipControllerComponent>());
+    }
+
+    void MeshletsExampleComponent::RemoveController()
+    {
+        AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Disable);
+    }
+
+    void MeshletsExampleComponent::SetArcBallControllerParams()
+    {
+        if (!m_modelBrowser.GetSelectedAssetId().IsValid() || !m_modelAsset.IsReady())
+        {
+            return;
+        }
+
+        if (!m_meshetsModel)
+        {
+            m_meshetsModel = new AZ::Meshlets::MeshletsModel(m_modelAsset);
+            if (m_meshetsModel->GetMeshletsModel())
+            {
+                static constexpr const char meshletDebugMaterialPath[] = "objects/debugshadermaterial_01.azmaterial";
+
+                AZ::Data::Asset<AZ::RPI::MaterialAsset> meshletDebugMaterialAsset =
+                    AZ::RPI::AssetUtils::LoadAssetByProductPath<AZ::RPI::MaterialAsset>(meshletDebugMaterialPath, AZ::RPI::AssetUtils::TraceLevel::Error);
+
+                m_meshletsDebugMaterial = AZ::RPI::Material::FindOrCreate(meshletDebugMaterialAsset);
+
+                m_meshletsModelAsset = m_meshetsModel->GetMeshletsModel()->GetModelAsset();
+                m_meshletsMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ m_meshletsModelAsset }, m_meshletsDebugMaterial);// m_materialOverrideInstance);
+
+                AZ::Transform translation = AZ::Transform::CreateTranslation(AZ::Vector3(0.75, 1.5, 0));
+                GetMeshFeatureProcessor()->SetTransform(m_meshletsMeshHandle, translation);
+            }
+
+            if (GetMeshletsFeatureProcessor())
+            {
+                m_meshetsRenderObject = new AZ::Meshlets::MeshletsRenderObject(m_modelAsset, m_meshletsFeatureProcessor);
+                if (m_meshetsRenderObject->GetMeshletsCount())
+                {
+                    m_meshletObjectId = m_meshletsFeatureProcessor->AddMeshletsRenderObject(m_meshetsRenderObject);
+
+                    AZ::Transform translation = AZ::Transform::CreateTranslation(AZ::Vector3(-0.75, 1.5, 0));
+                    m_meshletsFeatureProcessor->SetTransform(m_meshletObjectId, translation);
+                }
+            }
+            AZ_Error("Meshlets", m_meshletsFeatureProcessor && m_meshetsRenderObject->GetMeshletsCount(),
+                "Could not get MeshletsFeatureProcessor or meshlets were not generated");
+        }
+
+        // Adjust the arc-ball controller so that it has bounds that make sense for the current model
+        
+        AZ::Vector3 center;
+        float radius;
+        m_modelAsset->GetAabb().GetAsSphere(center, radius);
+
+        const float startingDistance = radius * ArcballRadiusDefaultModifier;
+        const float minDistance = radius * ArcballRadiusMinModifier;
+        const float maxDistance = radius * ArcballRadiusMaxModifier;
+
+        AZ::Debug::ArcBallControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::ArcBallControllerRequestBus::Events::SetCenter, center);
+        AZ::Debug::ArcBallControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::ArcBallControllerRequestBus::Events::SetDistance, startingDistance);
+        AZ::Debug::ArcBallControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::ArcBallControllerRequestBus::Events::SetMinDistance, minDistance);
+        AZ::Debug::ArcBallControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::ArcBallControllerRequestBus::Events::SetMaxDistance, maxDistance);
+    }
+    void MeshletsExampleComponent::ResetCameraController()
+    {
+        RemoveController();
+        if (m_currentCameraControllerType == CameraControllerType::ArcBall)
+        {
+            UseArcBallCameraController();
+            SetArcBallControllerParams();
+        }
+        else if (m_currentCameraControllerType == CameraControllerType::NoClip)
+        {
+            UseNoClipCameraController();
+        }
+    }
+} // namespace AtomSampleViewer

+ 147 - 0
Gems/Meshlets/ASV_GPU_and_CPU_Demo/MeshletsExampleComponent.h

@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <CommonSampleComponentBase.h>
+
+
+#include <AzCore/Component/EntityBus.h>
+#include <AzCore/Component/TickBus.h>
+
+#include <Utils/Utils.h>
+#include <Utils/ImGuiSidebar.h>
+#include <Utils/ImGuiMaterialDetails.h>
+#include <Utils/ImGuiAssetBrowser.h>
+
+#include <Atom/Bootstrap/DefaultWindowBus.h>
+#include <Atom/Feature/ImGui/ImGuiUtils.h>
+#include <Atom/Feature/SkyBox/SkyBoxFeatureProcessorInterface.h>
+
+#include <MeshletsAssets.h>
+#include <MeshletsRenderObject.h>
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        class MeshletsFeatureProcessor;
+    }
+}
+
+namespace AtomSampleViewer
+{
+    class MeshletsExampleComponent final
+        : public CommonSampleComponentBase
+        , public AZ::Render::Bootstrap::DefaultWindowNotificationBus::Handler
+        , public AZ::TickBus::Handler
+    {
+    public:
+        AZ_COMPONENT(MeshletsExampleComponent, "{BFE93321-91A4-4087-BABE-8B475087BBAD}", CommonSampleComponentBase);
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        MeshletsExampleComponent();
+        ~MeshletsExampleComponent() override = default;
+
+        // AZ::Component
+        void Activate() override;
+        void Deactivate() override;
+
+    private:
+        // AZ::TickBus::Handler
+        void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
+
+        // AZ::EntityBus::MultiHandler
+        void OnEntityDestruction(const AZ::EntityId& entityId) override;
+
+        void ModelChange();
+
+        void CreateGroundPlane();
+        void UpdateGroundPlane();
+        void RemoveGroundPlane();
+
+        void UseArcBallCameraController();
+        void UseNoClipCameraController();
+        void RemoveController();
+
+        void SetArcBallControllerParams();
+        void ResetCameraController();
+
+        void DefaultWindowCreated() override;
+
+        void CreateLowEndPipeline();
+        void DestroyLowEndPipeline();
+
+        void ActivateLowEndPipeline();
+        void DeactivateLowEndPipeline();
+
+        AZ::Meshlets::MeshletsFeatureProcessor* GetMeshletsFeatureProcessor();
+
+        AZ::RPI::RenderPipelinePtr m_lowEndPipeline;
+        AZ::RPI::RenderPipelinePtr m_originalPipeline;
+
+        AZStd::shared_ptr<AZ::RPI::WindowContext> m_windowContext;
+        AZ::Render::ImGuiActiveContextScope m_imguiScope;
+
+        enum class CameraControllerType : int32_t 
+        {
+            ArcBall = 0,
+            NoClip,
+            Count
+        };
+        static const uint32_t CameraControllerCount = static_cast<uint32_t>(CameraControllerType::Count);
+        static const char* CameraControllerNameTable[CameraControllerCount];
+        CameraControllerType m_currentCameraControllerType = CameraControllerType::ArcBall;
+
+        AZ::Render::MeshFeatureProcessorInterface::ModelChangedEvent::Handler m_changedHandler;
+        
+        static constexpr float ArcballRadiusMinModifier = 0.01f;
+        static constexpr float ArcballRadiusMaxModifier = 4.0f;
+        static constexpr float ArcballRadiusDefaultModifier = 2.0f;
+        
+        AZ::RPI::Cullable::LodConfiguration m_lodConfig;
+
+        bool m_enableMaterialOverride = true;
+
+        // If false, only azmaterials generated from ".material" files will be listed.
+        // Otherwise, all azmaterials, regardless of its source (e.g ".fbx"), will
+        // be shown in the material list.
+        bool m_showModelMaterials = false;
+
+        bool m_showGroundPlane = false;
+
+        bool m_cameraControllerDisabled = false;
+
+        bool m_useLowEndPipeline = false;
+        bool m_switchPipeline = false;
+
+        AZ::Meshlets::MeshletsFeatureProcessor* m_meshletsFeatureProcessor = nullptr;
+
+        AZ::Data::Instance<AZ::RPI::Material> m_materialOverrideInstance; //< Holds a copy of the material instance being used when m_enableMaterialOverride is true.
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_meshHandle;
+        AZ::Data::Asset<AZ::RPI::ModelAsset> m_modelAsset;
+
+        // This is the data stored for the copied mesh with the new generated meshlets structure.
+        AZ::Data::Instance<AZ::RPI::Material> m_meshletsDebugMaterial;
+        AZ::Meshlets::MeshletsModel* m_meshetsModel = nullptr;
+        AZ::Meshlets::MeshletsRenderObject* m_meshetsRenderObject = nullptr;
+        AZ::Data::Asset<AZ::RPI::ModelAsset> m_meshletsModelAsset;
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_meshletsMeshHandle;
+        AZ::Render::TransformServiceFeatureProcessorInterface::ObjectId m_meshletObjectId;
+
+        AZ::Data::Asset<AZ::RPI::ModelAsset> m_groundPlaneModelAsset;
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_groundPlandMeshHandle;
+        AZ::Data::Instance<AZ::RPI::Material> m_groundPlaneMaterial;
+
+        ImGuiSidebar m_imguiSidebar;
+        ImGuiMaterialDetails m_imguiMaterialDetails;
+        ImGuiAssetBrowser m_materialBrowser;
+        ImGuiAssetBrowser m_modelBrowser;
+    };
+} // namespace AtomSampleViewer

+ 190 - 0
Gems/Meshlets/ASV_GPU_and_CPU_Demo/Readme.txt

@@ -0,0 +1,190 @@
+/*
+ * 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
+ *
+ */
+
+Atom Meshlets Gem POC
+=====================
+
+Author: Adi Bar-Lev, May 2022
+Contributors: 
+Dependencies: meshlets generation is done by an excellent open source library - 
+    the MeshOptimizer library: https://github.com/zeux/meshoptimizer
+
+
+Demo Show Case
+--------------
+MeshletsExampleComponent will generate two meshlet models that will be displayed
+along side the original one with UVs grouped by meshlet and can be associated with 
+color.
+The first colored meshlets model is the reference model done by generating the meshlets 
+in the CPU at run time model load and by using the material DebugShaderMaterial_01 based on the 
+shader DebugShaderPBR_ForwardPass - all copied to this folder.
+The second Meshlets model is using the meshlets data from the CPU and processing it 
+on the fly using dispatch compute that creates the index buffer and the UV coloring - this
+is then sent to a direct draw render that will displays it.
+The latest is the POC that can be enhance to become a parallel GPU driven pipeline.
+
+Short Intro
+===========
+The Meshlets Gem is a POC for rendering meshes solely on the GPU using 
+meshlets / mesh clusters.
+This is done to promote maximum parallelism in processing relative small pieces 
+of the mesh and gain performance by doind fast culling that due to the size and locality
+of these clusters is much more efficient than culling the entire mesh.
+The work is inspired by Ubisoft 2015 presentation and recently Nanity by Epic.
+The current POC does not do the indirect dispatch and render and lacks the culling 
+stage - these are the main two steps missing to make it beyond a POC.
+
+What does it do?
+================
+- Splits the mesh and creates the data structures requires for meshlets
+- Prepares the GPU, buffers and data required by the GPU for such render pipeline
+- Dispatch Compute groups that generates the index buffer and debug color UVs
+- Direct draw the output of the compute using direct buffer access for the vertex streams
+
+Known Bug:
+==========
+- In the latest version there seem to be an occasional missing meshlet group, hence creating 
+    an area with missing polygons, this might be a very simple counting bug. Will be fixed in
+    future versions.
+
+Moving Forward
+==============
+- Create and prepare culling data compute culling data
+- Create a new compute stage that will cull the meshlets based on one of the following:
+    - Previous meshlets reprojected Z (requires pre-pass and adding delta objects)
+    - Previous Hi-Z (cracks can occur - needs to compensate)
+    - Best occluders
+    - other..
+- Based on the culling create new indexing table (into the index buffer) for the visible meshlets
+- Run the current meshlet compute on visible meshlets only 
+    - Calculate the indices and populate the index buffer
+- Add actual PBR render connected to Atom's lighting
+- Change the mesh loader and processing as an AssetProcessor builder job to be done offline
+
+Remark: The POC seem to still have a small bug - in some meshes, small amount of polygons 
+    might not render. Due to the selective nature of how it looks, it might be as simple 
+    as missing a meshlet group.
+
+Intent
+======
+This POC intent is not to replace the existing render pipeline. The provided gem is a POC towards a GPU 
+driven render pipeline and is an opportunity for the O3DE community to take this gem as reference and enhance.
+Removing the CPU from the equation is considered by many the path to the future of rendering, and using 
+meshlets techniques increases performance as they eliminate the majority of the invisible meshes in the scene 
+that otherwise would have sent to render due to partial visibility of the meshes. All in all this technique 
+represents a much more optimal rendering approach, one that was successfully used by Ubisoft and is a large part of Nanite today.
+This approach is parallel to the mesh render pipeline that exists in Atom today and is an opportunity for 
+O3DE community to look into and hopefully advance in the future.
+In no way it comes to replace the existing mesh render pipeline in Atom that is state of the art and coming to maturity.
+
+References:
+===========
+Ubisoft - Siggraph 2015: https://advances.realtimerendering.com/s2015/aaltonenhaar_siggraph2015_combined_final_footer_220dpi.pdf
+
+Meshlets Intro - NVidia: https://developer.nvidia.com/blog/introduction-turing-mesh-shaders/
+
+Cluster Culling 2016: https://gpuopen.com/learn/geometryfx-1-2-cluster-culling/
+
+Epic Nanite 2021: https://quip-amazon.com/-/blob/RKb9AAL3zt8/G7A6Cxor0gvn_WPGltB-8g?name=Karis_Nanite_SIGGRAPH_Advances_2021_final.pdf&s=Gn3dAhbv9gN6
+
+Activision - Geometry Rendering Pipeline architecture 2021: https://research.activision.com/publications/2021/09/geometry-rendering-pipeline-architecture
+ 
+
+Quick Build and Run Direction
+=============================
+1. Go over the files in this directory - you'd needd to folow the instructions below and copy 
+    the files to various locations. Create a Meshlets branch so you'd do it once only.
+2. Add the gem directory to the file engine.json:
+    "external_subdirectories": [
+        "Gems/Meshlets",
+        ...
+3. Run cmake again
+4. Compile meshoptimizer.lib as static library and add it to the Meshlets gem
+5. Build the ASV solution
+6. Run the standalone ASV and choose the Meshlets demo from the RPI demos sub-menu 
+7. When selecting a model, a secondary CPU meshlet model will be generated and 
+    displayed alongside, and in the GPU demo, a third one created by the GPU will 
+    be displayed as well.
+
+
+Adding the meshoptimizer library to the Gem
+===========================================
+The meshoptimizer library is not included as part of the O3DE.
+When adding and compiling this Gem, compile the meshoptimizer 
+library and add it as part of Meshlets.Static project (for example, in 
+Visual Studio you simply add the library file to the project).
+Once this is done, the Gem should compile and link properly to allow you 
+to run the ASV sample 'Meshlets' (created with the MeshletsExampleComponent)
+
+The meshoptimizer library and source can be found in the following Github link:
+https://github.com/zeux/meshoptimizer
+
+
+Connecting the Gem to the project folder (AtomSamplesViewer for example):
+=========================================================================
+1. Add Meshlets Shader Assets directories to the file 
+    <project_folder>/config/shader_global_build_options.json
+    
+            "PreprocessorOptions" : {
+            "predefinedMacros": ["AZSL=17"],
+                // The root of the current project is always the first include path.
+                // These include paths are already part of the automatic include folders list.
+                // By specifying them here, we are boosting the priority of these folders above all the other automatic include folders.
+                // (This is not necessary for the project, but just shown as a usage example.)
+            "projectIncludePaths": [
+                "Gems/Atom/RPI/Assets/ShaderLib",
+                "Gems/Atom/Feature/Common/Assets/ShaderLib",
+                "Gems/Meshlets/Assets/Shaders"
+            ]
+
+2. Not required: you can choose to add Meshlets Assets in 
+    <project_folder>/Registry/assets_scan_folders.setreg
+            "Meshlets":
+            {
+                "SourcePaths":
+                [
+                    "Gems/Meshlets/Assets"
+                ]
+            }
+    Remark: this is NOT required in this case as Meshlets can process regular Atom meshes
+
+3. Enable Meshlets gem in project_folder - AtomSampleViewer/Gem/code/enabled_gems.cmake
+            (
+                set(ENABLED_GEMS
+                ...
+                Meshlets
+            )
+            
+4. Add the current passes to MainPipeline.pass - 
+    the pass file was added to this folder as a reference how to add the meshlets passes.
+
+5. Copy the shader debug files (DebugShaderPBR_ForwardPass.*) to the shader directory:
+    [o3de]\AtomSampleViewer\Materials\Types\*
+
+6. Add the material file 'debugshadermaterial_01.material' to the material directory:
+    [o3de]\AtomSampleViewer\Materials\DebugShaderMaterial_01.material
+    
+7. Complie meshoptimizer to a library, copy the compiled library (you can compile and place it under Gems\Meshlets\External\Lib) 
+    and add it to your Meshlets.static project.
+
+
+Including the Meshlets sample in ASV
+====================================
+1. Add the following two files under the directory [O3de dir]\AtomSampleViewer\Gem\Code\Source
+    MeshletsExampleComponent.h
+    MeshletsExampleComponent.cpp
+
+2. Alter SampleComponentManager.cpp to include the following lines:
+    In the header files section:
+    #include <MeshletsExampleComponent.h>
+
+    In method SampleComponentManager::GetSamples()
+                NewRPISample<MeshletsExampleComponent>("Meshlets"),
+            
+3. Add the source files to the make file 'atomsampleviewergem_private_files.cmake'
+

+ 200 - 0
Gems/Meshlets/ASV_GPU_and_CPU_Demo/atomsampleviewergem_private_files.cmake

@@ -0,0 +1,200 @@
+#
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+#
+#
+
+set(FILES
+    Source/MeshletsExampleComponent.cpp
+    Source/MeshletsExampleComponent.h    
+    Source/MeshletsIndirectDrawExampleComponent.cpp
+    Source/MeshletsIndirectDrawExampleComponent.h
+    Source/AtomSampleViewerOptions.h
+    Source/AtomSampleViewerSystemComponent.cpp
+    Source/AtomSampleViewerSystemComponent.h
+    Source/AtomSampleViewerRequestBus.h
+    Source/SampleComponentManager.cpp
+    Source/SampleComponentManager.h
+    Source/SampleComponentManagerBus.h
+    Source/SampleComponentConfig.cpp
+    Source/SampleComponentConfig.h
+    Source/Automation/AssetStatusTracker.cpp
+    Source/Automation/AssetStatusTracker.h
+    Source/Automation/ImageComparisonConfig.h
+    Source/Automation/ImageComparisonConfig.cpp
+    Source/Automation/ScriptableImGui.cpp
+    Source/Automation/ScriptableImGui.h
+    Source/Automation/ScriptManager.cpp
+    Source/Automation/ScriptManager.h
+    Source/Automation/ScriptRepeaterBus.h
+    Source/Automation/ScriptRunnerBus.h
+    Source/Automation/ScriptReporter.cpp
+    Source/Automation/ScriptReporter.h
+    Source/RHI/AlphaToCoverageExampleComponent.cpp
+    Source/RHI/AlphaToCoverageExampleComponent.h
+    Source/RHI/AsyncComputeExampleComponent.h
+    Source/RHI/AsyncComputeExampleComponent.cpp
+    Source/RHI/BasicRHIComponent.cpp
+    Source/RHI/BasicRHIComponent.h
+    Source/RHI/BindlessPrototypeExampleComponent.h
+    Source/RHI/BindlessPrototypeExampleComponent.cpp
+    Source/RHI/ComputeExampleComponent.cpp
+    Source/RHI/ComputeExampleComponent.h
+    Source/RHI/CopyQueueComponent.cpp
+    Source/RHI/CopyQueueComponent.h
+    Source/RHI/DualSourceBlendingComponent.cpp
+    Source/RHI/DualSourceBlendingComponent.h
+    Source/RHI/IndirectRenderingExampleComponent.cpp
+    Source/RHI/IndirectRenderingExampleComponent.h
+    Source/RHI/InputAssemblyExampleComponent.cpp
+    Source/RHI/InputAssemblyExampleComponent.h
+    Source/RHI/MRTExampleComponent.h
+    Source/RHI/MRTExampleComponent.cpp
+    Source/RHI/MSAAExampleComponent.h
+    Source/RHI/MSAAExampleComponent.cpp
+    Source/RHI/MultiThreadComponent.cpp
+    Source/RHI/MultiThreadComponent.h
+    Source/RHI/MultipleViewsComponent.cpp
+    Source/RHI/MultipleViewsComponent.h
+    Source/RHI/MultiViewportSwapchainComponent.cpp
+    Source/RHI/MultiViewportSwapchainComponent.h
+    Source/RHI/QueryExampleComponent.h
+    Source/RHI/QueryExampleComponent.cpp
+    Source/RHI/StencilExampleComponent.cpp
+    Source/RHI/StencilExampleComponent.h
+    Source/RHI/SwapchainExampleComponent.cpp
+    Source/RHI/SwapchainExampleComponent.h
+    Source/RHI/SphericalHarmonicsExampleComponent.cpp
+    Source/RHI/SphericalHarmonicsExampleComponent.h
+    Source/RHI/SubpassExampleComponent.cpp
+    Source/RHI/SubpassExampleComponent.h
+    Source/RHI/Texture3dExampleComponent.cpp
+    Source/RHI/Texture3dExampleComponent.h
+    Source/RHI/TextureArrayExampleComponent.cpp
+    Source/RHI/TextureArrayExampleComponent.h
+    Source/RHI/TextureExampleComponent.cpp
+    Source/RHI/TextureExampleComponent.h
+    Source/RHI/TextureMapExampleComponent.cpp
+    Source/RHI/TextureMapExampleComponent.h
+    Source/RHI/TriangleExampleComponent.cpp
+    Source/RHI/TriangleExampleComponent.h
+    Source/RHI/TrianglesConstantBufferExampleComponent.h
+    Source/RHI/TrianglesConstantBufferExampleComponent.cpp
+    Source/RHI/RayTracingExampleComponent.cpp
+    Source/RHI/RayTracingExampleComponent.h
+    Source/RHI/MatrixAlignmentTestExampleComponent.cpp
+    Source/RHI/MatrixAlignmentTestExampleComponent.h
+    Source/Performance/HighInstanceExampleComponent.cpp
+    Source/Performance/HighInstanceExampleComponent.h
+    Source/Performance/100KDrawable_SingleView_ExampleComponent.cpp
+    Source/Performance/100KDrawable_SingleView_ExampleComponent.h
+    Source/Performance/100KDraw_10KDrawable_MultiView_ExampleComponent.cpp
+    Source/Performance/100KDraw_10KDrawable_MultiView_ExampleComponent.h
+    Source/AreaLightExampleComponent.cpp
+    Source/AreaLightExampleComponent.h
+    Source/AssetLoadTestComponent.cpp
+    Source/AssetLoadTestComponent.h
+    Source/AuxGeomExampleComponent.cpp
+    Source/AuxGeomExampleComponent.h
+    Source/AuxGeomSharedDrawFunctions.cpp
+    Source/AuxGeomSharedDrawFunctions.h
+    Source/BakedShaderVariantExampleComponent.h
+    Source/BakedShaderVariantExampleComponent.cpp
+    Source/SponzaBenchmarkComponent.cpp
+    Source/SponzaBenchmarkComponent.h
+    Source/BloomExampleComponent.cpp
+    Source/BloomExampleComponent.h
+    Source/CheckerboardExampleComponent.h
+    Source/CheckerboardExampleComponent.cpp
+    Source/CommonSampleComponentBase.cpp
+    Source/CommonSampleComponentBase.h
+    Source/CullingAndLodExampleComponent.cpp
+    Source/CullingAndLodExampleComponent.h
+    Source/DecalExampleComponent.cpp
+    Source/DecalExampleComponent.h
+    Source/DecalContainer.cpp
+    Source/DecalContainer.h
+    Source/DepthOfFieldExampleComponent.h
+    Source/DepthOfFieldExampleComponent.cpp
+    Source/DiffuseGIExampleComponent.cpp
+    Source/DiffuseGIExampleComponent.h
+    Source/DynamicDrawExampleComponent.h
+    Source/DynamicDrawExampleComponent.cpp
+    Source/DynamicMaterialTestComponent.cpp
+    Source/DynamicMaterialTestComponent.h
+    Source/EntityLatticeTestComponent.cpp
+    Source/EntityLatticeTestComponent.h
+    Source/EntityUtilityFunctions.cpp
+    Source/EntityUtilityFunctions.h
+    Source/ExposureExampleComponent.cpp
+    Source/ExposureExampleComponent.h
+    Source/LightCullingExampleComponent.cpp
+    Source/LightCullingExampleComponent.h
+    Source/MaterialHotReloadTestComponent.cpp
+    Source/MaterialHotReloadTestComponent.h
+    Source/MeshExampleComponent.cpp
+    Source/MeshExampleComponent.h
+    Source/MSAA_RPI_ExampleComponent.cpp
+    Source/MSAA_RPI_ExampleComponent.h
+    Source/MultiRenderPipelineExampleComponent.cpp
+    Source/MultiRenderPipelineExampleComponent.h
+    Source/MultiSceneExampleComponent.cpp
+    Source/MultiSceneExampleComponent.h
+    Source/MultiViewSingleSceneAuxGeomExampleComponent.cpp
+    Source/MultiViewSingleSceneAuxGeomExampleComponent.h
+    Source/ParallaxMappingExampleComponent.cpp
+    Source/ParallaxMappingExampleComponent.h
+    Source/Passes/RayTracingAmbientOcclusionPass.cpp
+    Source/Passes/RayTracingAmbientOcclusionPass.h
+    Source/ParallaxMappingExampleComponent.h
+    Source/ProceduralSkinnedMesh.cpp
+    Source/ProceduralSkinnedMesh.h
+    Source/ProceduralSkinnedMeshUtils.cpp
+    Source/ProceduralSkinnedMeshUtils.h
+    Source/RenderTargetTextureExampleComponent.cpp
+    Source/RenderTargetTextureExampleComponent.h
+    Source/RootConstantsExampleComponent.h
+    Source/RootConstantsExampleComponent.cpp
+    Source/SceneReloadSoakTestComponent.cpp
+    Source/SceneReloadSoakTestComponent.h
+    Source/ShadowExampleComponent.cpp
+    Source/ShadowExampleComponent.h
+    Source/ShadowedSponzaExampleComponent.cpp
+    Source/ShadowedSponzaExampleComponent.h
+    Source/SkinnedMeshContainer.cpp
+    Source/SkinnedMeshContainer.h
+    Source/SkinnedMeshExampleComponent.cpp
+    Source/SkinnedMeshExampleComponent.h
+    Source/SsaoExampleComponent.cpp
+    Source/SsaoExampleComponent.h
+    Source/SSRExampleComponent.cpp
+    Source/SSRExampleComponent.h
+    Source/StreamingImageExampleComponent.cpp
+    Source/StreamingImageExampleComponent.h
+    Source/TonemappingExampleComponent.cpp
+    Source/TonemappingExampleComponent.h
+    Source/TransparencyExampleComponent.cpp
+    Source/TransparencyExampleComponent.h
+    Source/ShaderReloadTestComponent.cpp
+    Source/ShaderReloadTestComponent.h
+    Source/Utils/ImGuiAssetBrowser.cpp
+    Source/Utils/ImGuiAssetBrowser.h
+    Source/Utils/ImGuiHistogramQueue.cpp
+    Source/Utils/ImGuiHistogramQueue.h
+    Source/Utils/ImGuiMaterialDetails.cpp
+    Source/Utils/ImGuiMaterialDetails.h
+    Source/Utils/ImGuiMessageBox.cpp
+    Source/Utils/ImGuiMessageBox.h
+    Source/Utils/ImGuiSaveFilePath.cpp
+    Source/Utils/ImGuiSaveFilePath.h
+    Source/Utils/ImGuiShaderUtils.cpp
+    Source/Utils/ImGuiShaderUtils.h
+    Source/Utils/ImGuiSidebar.cpp
+    Source/Utils/ImGuiSidebar.h
+    Source/Utils/Utils.cpp
+    Source/Utils/Utils.h
+    Source/Utils/ImGuiProgressList.cpp
+    Source/Utils/ImGuiProgressList.h
+)

+ 23 - 0
Gems/Meshlets/ASV_GPU_and_CPU_Demo/enabled_gems.cmake

@@ -0,0 +1,23 @@
+#
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+#
+#
+
+set(ENABLED_GEMS
+    Maestro
+    TextureAtlas
+    LmbrCentral
+    LyShine
+    Camera
+    EMotionFX
+    Atom_AtomBridge
+    AtomSampleViewer
+    SceneProcessing
+    EditorPythonBindings
+    ImGui
+    Profiler
+    Meshlets
+)

+ 31 - 0
Gems/Meshlets/Assets/Passes/MeshletsCompute.pass

@@ -0,0 +1,31 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "MeshletsComputePassTemplate",
+            "PassClass": "MultiDispatchComputePass",
+            "Slots": [
+                {
+                    "Name": "MeshletsSharedBuffer",
+                    "ShaderInputName": "m_meshletsSharedBuffer",
+                    "SlotType": "Output",
+                    "ScopeAttachmentUsage": "Shader",
+                    "LoadStoreAction": {
+                        "LoadAction": "Load",
+                        "StoreAction": "Store"
+                    }
+                }
+            ],
+            "PassData": {
+                "$type": "ComputePassData",
+                "ShaderAsset": {
+                    "FilePath": "Shaders/MeshletsCompute.shader"
+                }
+            },
+            "Connections": [
+            ]
+        }
+    }
+}

+ 50 - 0
Gems/Meshlets/Assets/Passes/MeshletsParent.pass

@@ -0,0 +1,50 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "MeshletsParentPassTemplate",
+            "PassClass": "ParentPass",
+            "Slots": [
+            ],
+            "PassRequests": [
+                {
+                    "Name": "MeshletsComputePass",
+                    "TemplateName": "MeshletsComputePassTemplate",
+                    "Connections": [
+                    ],
+                    "PassData": {
+                        "$type": "ComputePassData",
+                        "ShaderAsset": {
+                            "FilePath": "Shaders/MeshletsCompute.shader"
+                        }
+                    }
+                },
+                {
+                    "Name": "MeshletsRenderPass",
+                    "TemplateName": "MeshletsRenderPassTemplate",
+                    "Connections": [
+                        // Input/Outputs...
+                        {
+                            "LocalSlot": "DepthStencilInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthMSAA"
+                            }
+                        }
+                    ],
+                    "PassData": {
+                        "$type": "RasterPassData",
+                        "DrawListTag": "meshlets",
+                        "PipelineViewTag": "MainCamera",
+                        "PassSrgShaderAsset": {
+                            "FilePath": "Shaders/MeshletsDebugRenderShader.shader"
+                        }
+                    }
+                }
+            ]
+        }
+    }
+}
+ 

+ 16 - 0
Gems/Meshlets/Assets/Passes/MeshletsPassRequest.azasset

@@ -0,0 +1,16 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassRequest",
+    "ClassData": {
+        // This is not set yet - please fill with active passes.
+        // This should be replaced with a parent pass that will contain all meshlets passes
+        "Name": "MeshletsParentPass",
+        "TemplateName": "MeshletsParentPassTemplate",
+        "Enabled": true,
+        // Currently no connections from the pipeline are required - this will change shortly
+        // to have depth and lighting brought in.
+        "Connections": [
+        ]
+    }
+}

+ 21 - 0
Gems/Meshlets/Assets/Passes/MeshletsPassTemplates.azasset

@@ -0,0 +1,21 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "AssetAliasesSourceData",
+    "ClassData": {
+        "AssetPaths": [
+            {
+                "Name": "MeshletsParentPassTemplate",
+                "Path": "Passes/MeshletsParent.pass"
+            },
+            {
+                "Name": "MeshletsComputePassTemplate",
+                "Path": "Passes/MeshletsCompute.pass"
+            },
+            {
+                "Name": "MeshletsRenderPassTemplate",
+                "Path": "Passes/MeshletsRender.pass"
+            }
+        ]
+    }
+}

+ 41 - 0
Gems/Meshlets/Assets/Passes/MeshletsRender.pass

@@ -0,0 +1,41 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "MeshletsRenderPassTemplate",
+            "PassClass": "MeshletsRenderPass",
+            "Slots": [
+                // OUtput shared buffer for dynamic meshlets data
+                {
+                    "Name": "MeshletsSharedBuffer",
+                    "ShaderInputName": "m_meshletsSharedBuffer",
+                    "SlotType": "Input",
+                    "ScopeAttachmentUsage": "Shader"
+                },
+
+                // Input/Outputs...
+                {
+                    "Name": "DepthStencilInputOutput",
+                    "SlotType": "InputOutput",
+                    "ScopeAttachmentUsage": "DepthStencil"
+                },
+                {
+                    "Name": "RenderTargetInputOutput",
+                    "SlotType": "InputOutput",
+                    "ScopeAttachmentUsage": "RenderTarget"
+                }
+            ],
+            "Connections": [
+                {
+                    "LocalSlot": "MeshletsSharedBuffer",
+                    "AttachmentRef": {
+                        "Pass": "MeshletsComputePass",
+                        "Attachment": "MeshletsSharedBuffer"
+                    }
+                }
+            ]
+        }
+    }
+}

+ 246 - 0
Gems/Meshlets/Assets/Shaders/MeshletsCompute.azsl

@@ -0,0 +1,246 @@
+#pragma once
+
+#include <Atom/Features/SrgSemantics.azsli>
+
+//------------------------------------------------------------------------------
+ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
+{   // [To Do] No needto use StructuredBuffer - change to buffer and test
+    RWStructuredBuffer<int>     m_meshletsSharedBuffer;
+}
+
+//------------------------------------------------------------------------------
+//! The following meshlet descriptor structure must reflect the structure as 
+//! appeared in MeshletsData.h 
+struct MeshletDescriptor
+{
+    //! Offset into the indirect indices array representing the global index of
+    //! all the meshlet vertices.
+    //! The Indirect vertices array is built as follows:
+    //!     std::vector<uint32_t> indirectIndices;
+    //!     indirectIndices = { { meshlet 1 vertex indices }, { meshlet 2 }, .. { meshlet n} }
+    uint vertexOffset;      // In uint32_t steps
+
+    //! Offset into the global meshlets triangleIndices array represented as:
+    //!     std::vector<uint8_t> triangleIndices;
+    //!     triangleIndices = { {meshlet 1 local indices group}, ... { meshlet n} }
+    //! The local indices are an 8 bits index that can represent up to 256 entries.
+    uint triangleOffset;    // In bytes from the start of the array
+
+    //! Finding a vertex within the meshlet is done like that:
+    //!     triangleOffset = currentMeshlet.triangleOffset + meshletTrIndex * 3;
+    //!     localIndex_i = meshletTriangles[triangleOffset + i];    // i = triangle vertex index 0..2
+    //!     vertexIndex_i =  indirectIndices[currentMeshlet.vertexOffset + localIndex_i];
+
+    //! Amount of vertices and triangle for the meshlet - based on this the arrays
+    //! indirectIndices and triangleIndices are created per meshlet.
+    uint vertexCount;
+    uint triangleCount;
+};
+
+//------------------------------------------------------------------------------
+// This structure is used per mesh (object) and represents the meshlets data 
+// for this mesh.
+// This is not the instance data but rather the object data that can be used 
+// across instances of the same mesh.
+ShaderResourceGroup MeshletsDataSrg : SRG_PerObject
+{
+    // Shared buffer offset in uint to be used when using the shared buffer when 
+    // addressing the properties 
+    uint m_indicesOffset;   
+    uint m_texCoordsOffset;
+    uint2 padding;
+
+    // For the next array review the structuee 'MeshletDescriptor'.
+    // The array holds the offsets and amount of vertices and triangles per 
+    // meshlet.
+    StructuredBuffer<MeshletDescriptor> m_meshletsDescriptors;
+
+    // The following array consistes of sub arrays of triangles - one per meshlet.
+    // Each uint (32 bits) represents 3 x 8 bits indices into the meshlet indices 
+    // lookup array (the top byte is unused).
+    Buffer<uint>    m_meshletsTriangles;    
+
+    // Array of sub-tables, each one represents lookup table for a meshlet that 
+    // maps between a meshlet local vertex index and its global index in the mesh.
+    Buffer<uint>    m_meshletsIndicesLookup;
+
+    // ---------------------------------------------
+    // The following two buffers are in fact buffer views into the shared buffer 
+    // so that the GPU memory can be synchronized via the pass system using only 
+    // a single buffer and barier between passes.
+    // ---------------------------------------------
+    // Index buffer of the mesh comprised of meshlets
+    // Meshlets Compute threads will write the calculated indices per meshlet and 
+    // store it in the array if the meshlets are not culled.
+    RWBuffer<uint>  m_indices;
+
+    // Mesh texture coordinates - will be used for debug purposes to color meshlets
+    RWBuffer<float2> m_uvs;
+
+    //--------------------------------------------------------------------------
+    uint GetVertexIndex(uint index)
+    {
+        return PassSrg::m_meshletsSharedBuffer[m_indicesOffset + index];
+    }
+
+    void SetVertexIndex(uint index, uint vertexIndex )
+    {
+        PassSrg::m_meshletsSharedBuffer[m_indicesOffset + index] = vertexIndex;
+    }
+
+    void SetTriangleIndices(uint index, uint3 triIndices)
+    {
+        uint indexOffset = m_indicesOffset + index;
+        PassSrg::m_meshletsSharedBuffer[indexOffset] = triIndices.x;
+        PassSrg::m_meshletsSharedBuffer[indexOffset+1] = triIndices.y;
+        PassSrg::m_meshletsSharedBuffer[indexOffset+2] = triIndices.z;
+    }
+
+    void SetUVs(uint index, float2 texCoords)
+    {
+        // index multiplied by 2 since we have two coordinates 
+        uint texCoordIndex = m_texCoordsOffset + (index << 1);
+        PassSrg::m_meshletsSharedBuffer[texCoordIndex] = asint(texCoords.x);    
+        PassSrg::m_meshletsSharedBuffer[texCoordIndex+1] = asint(texCoords.y);    
+    }
+};
+
+//------------------------------------------------------------------------------
+// Given the local meshlet's triangle index and the meshlet, the function calculates
+// the global triangle's vertex indices and returns them along with the global 
+// triangle offset.
+//------------------------------------------------------------------------------
+uint4 GetGlobalVertexIndicesAndTriOffset(MeshletDescriptor meshlet, uint localTriangleIdx)
+{
+    uint globalTriangleIndex = meshlet.triangleOffset + localTriangleIdx;
+    uint encodedTri = MeshletsDataSrg::m_meshletsTriangles[globalTriangleIndex];
+    uint3 localIndices = uint3(
+        (encodedTri) & 0xff,
+        (encodedTri >> 8) & 0xff,
+        (encodedTri >> 16) & 0xff
+    );
+
+    uint3 globalIndirection = meshlet.vertexOffset + localIndices;
+    uint4 globalVertexIndicesAndTriOffset = uint4(
+        MeshletsDataSrg::m_meshletsIndicesLookup[globalIndirection.x],
+        MeshletsDataSrg::m_meshletsIndicesLookup[globalIndirection.y],
+        MeshletsDataSrg::m_meshletsIndicesLookup[globalIndirection.z],
+        globalTriangleIndex * 3
+    );
+
+    return globalVertexIndicesAndTriOffset;
+}
+
+/*
+* Modifications Copyright (c) Contributors to the Open 3D Engine Project. 
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+* 
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+//------------------------------------------------------------------------------
+//! The group's threads count should match either Meshlets::maxVerticesPerMeshlet
+//! or Meshlets::maxTrianglesPerMeshlet depending on our algorithm for minimizing 
+//! amount of work per thread and achieving max parallelizm.
+//------------------------------------------------------------------------------
+#define THREADS_COUNT 64
+
+//------------------------------------------------------------------------------
+// The following method demonstartes the usage of the meshlets data buffers to 
+// create the index buffer content on the fly and as debug display it also 
+// generates the meshlets color in the UV channel.
+// A future step should be to run a culling compute before this and generate 
+// a visibility list of the meshlets. This visibility list will be used to 
+// generate the dispatch groups per the visible meshlets and populate the index
+// buffer accordingly hence saving most of the rasterization of culled meshlets.
+// Care should be taken for synchronizing between threads for the location of 
+// the indices to be written - possibly single thread per mesh can be used to 
+// prepare the look up table for writing the indices by each thread in a unique 
+// location.
+//------------------------------------------------------------------------------
+[numthreads(THREADS_COUNT, 1, 1)]
+void ComputeMeshletsIndexBuffer(
+    uint groupIndex : SV_GroupIndex,
+    uint3 groupId : SV_GroupID,
+    uint3 dispatchThreadId : SV_DispatchThreadID)
+{
+    uint meshletId = groupId.x;
+    MeshletDescriptor meshlet = MeshletsDataSrg::m_meshletsDescriptors[meshletId];
+
+    uint uvDebugIndex = meshletId + 7;  // to start off with some interesting color
+    float2 debugUVs = float2( (uvDebugIndex % 3), ((uvDebugIndex / 3) % 3) ) * 0.5;
+
+//#define _DEBUG_TEST_CONTENT_
+#ifdef _DEBUG_TEST_CONTENT_
+    ///////////////////////// Start - Test Debug Only ////////////////////////////
+    // Simple test of the first entries of each meshlet - this will test writing 
+    // Tests writing data and data validity using both direct and indirect approach
+    if ((groupIndex < meshlet.triangleCount) && (groupIndex < 4))
+    {
+        uint4 vtxGlobalVerticesAndTriOffset = GetGlobalVertexIndicesAndTriOffset(meshlet, groupIndex);
+
+        // Both method work!!!
+        if (groupIndex < 2)
+        {   // Sub-Buffers address
+            MeshletsDataSrg::m_indices[vtxGlobalVerticesAndTriOffset.w] = 555;
+            MeshletsDataSrg::m_indices[vtxGlobalVerticesAndTriOffset.w + 1] = meshletId;
+            MeshletsDataSrg::m_indices[vtxGlobalVerticesAndTriOffset.w + 2] = groupIndex;
+
+            // Notice that writing to address is writing float2! (not a single element)
+            // This is ok for testing, but wrong in general since there is no 1:1 correlation 
+            // between vertices and indices.
+            MeshletsDataSrg::m_uvs[vtxGlobalVerticesAndTriOffset.w].x = 555;
+            MeshletsDataSrg::m_uvs[vtxGlobalVerticesAndTriOffset.w].y = groupIndex;
+
+            // This is the indirection in into the vertex pointed by the Index Buffer but since it
+            // can be located anywhere, it is harder to verify
+//            MeshletsDataSrg::m_uvs[MeshletsDataSrg::m_indices[vtxGlobalVerticesAndTriOffset.w]].x = 555;
+//            MeshletsDataSrg::m_uvs[MeshletsDataSrg::m_indices[vtxGlobalVerticesAndTriOffset.w]].y = meshletId;
+        }
+        else
+        {   // Shared Buffer using offsets
+            uint3 testIndex = uint3(22,meshletId,groupIndex);
+
+            MeshletsDataSrg::SetTriangleIndices(vtxGlobalVerticesAndTriOffset.w, testIndex);
+
+            MeshletsDataSrg::SetUVs(vtxGlobalVerticesAndTriOffset.w, groupIndex);
+            MeshletsDataSrg::SetUVs(vtxGlobalVerticesAndTriOffset.w + 1, groupIndex);
+            MeshletsDataSrg::SetUVs(vtxGlobalVerticesAndTriOffset.w + 2, groupIndex);
+        }
+        return;
+    }
+    ///////////////////////// End - Test Debug Only ////////////////////////////
+#endif
+
+    if (groupIndex < meshlet.triangleCount)
+    {   // groupIndex is used here as the index of the triangle we process
+        uint4 vtxGlobalVerticesAndTriOffset = GetGlobalVertexIndicesAndTriOffset(meshlet, groupIndex);
+
+//#define _USE_SHARED_BUFFER_OFFSET_
+#ifdef _USE_SHARED_BUFFER_OFFSET_
+        //// Setting the various properties via usage of the shared buffer with offsets
+        // Construct the triangles using the meshlets data
+        MeshletsDataSrg::SetTriangleIndices(vtxGlobalVerticesAndTriOffset.w, vtxGlobalVerticesAndTriOffset.xyz);
+
+        // Mark the triangles based on the meshlets Id.
+        MeshletsDataSrg::SetUVs(vtxGlobalVerticesAndTriOffset.x, debugUVs);
+        MeshletsDataSrg::SetUVs(vtxGlobalVerticesAndTriOffset.y, debugUVs);
+        MeshletsDataSrg::SetUVs(vtxGlobalVerticesAndTriOffset.z, debugUVs);
+#else
+        // Setting the properties directly through the buffer views that represent areas 
+        // in the memory of the shared buffer. 
+        // Set the global mesh index buffer for this meshlet triangle
+        // Future progress will require meshlets as visible or not and accordingly write this data
+        MeshletsDataSrg::m_indices[vtxGlobalVerticesAndTriOffset.w] = vtxGlobalVerticesAndTriOffset.x;
+        MeshletsDataSrg::m_indices[vtxGlobalVerticesAndTriOffset.w + 1] = vtxGlobalVerticesAndTriOffset.y;
+        MeshletsDataSrg::m_indices[vtxGlobalVerticesAndTriOffset.w + 2] = vtxGlobalVerticesAndTriOffset.z;
+
+        // Set the vertices UV according to the meshlet Id as a debug indication
+        // This is for testing only and should not be touched in the actual PBR render
+        MeshletsDataSrg::m_uvs[vtxGlobalVerticesAndTriOffset.x] = debugUVs;
+        MeshletsDataSrg::m_uvs[vtxGlobalVerticesAndTriOffset.y] = debugUVs;        
+        MeshletsDataSrg::m_uvs[vtxGlobalVerticesAndTriOffset.z] = debugUVs;
+#endif
+    }
+}

+ 14 - 0
Gems/Meshlets/Assets/Shaders/MeshletsCompute.shader

@@ -0,0 +1,14 @@
+{ 
+    "Source" : "MeshletsCompute.azsl",
+
+    "ProgramSettings":
+    {
+	  "EntryPoints":
+      [
+        {
+          "name": "ComputeMeshletsIndexBuffer",
+          "type": "Compute"
+        }
+      ]
+    }   
+}

+ 129 - 0
Gems/Meshlets/Assets/Shaders/MeshletsDebugRenderShader.azsl

@@ -0,0 +1,129 @@
+/*
+* Modifications Copyright (c) Contributors to the Open 3D Engine Project. 
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+* 
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+// To use the object Id and VertexUtility the ObjectSrg must be created 
+// and passed with the Object Id here.
+// This will require more than several changes also on the CPU side, utilizing the 
+// transform (m_transformService->ReserveObjectId()) and setting the Id in the 
+// PerObjectSrg (objectSrg->SetConstant(objectIdIndex, m_objectId.GetIndex())).
+// Look at ModelDataInstance::Init(Data::Instance<RPI::Model> model) to see how
+// to set the model Id - this will lead to MeshDrawPacket::DoUpdate that will demonstrate 
+// how to set the Srgs.
+// There is no need to set all the Srgs in the DrawPacket - only the ones that the pass
+// does not add manually in the command list (check the inheritance of the commit method).
+//#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
+#include <Atom/Features/SrgSemantics.azsli>
+
+//------------------------------------------------------------------------------
+//ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
+ShaderResourceGroup PassSrg : SRG_PerPass
+{   // No need for StructuredBuffer - change to buffer across all
+    StructuredBuffer<int>     m_meshletsSharedBuffer;
+}
+
+// Vertex Assembly streams will NOT be used - it is shown below as reference
+// Instead we use the buffers directly with index offset into the data.
+struct VSInput
+{
+    uint m_vertexIndex : SV_VertexID;
+
+/*  Use the following if ever passed as vertex assembly streams
+    float3 m_position : POSITION;
+    float3 m_normal : NORMAL;
+    float4 m_tangent : TANGENT; 
+    float3 m_bitangent : BITANGENT; 
+    float2 m_uv : UV0;
+*/
+};
+
+struct VSOutput
+{
+    float4 m_position : SV_Position;
+    float3 m_normal: NORMAL;
+    float3 m_tangent : TANGENT; 
+    float3 m_bitangent : BITANGENT; 
+    float3 m_worldPosition : UV0;
+    float2 m_uv : UV1;
+};
+
+struct MeshletsPassOutput
+{
+    float4 m_color  : SV_Target0;
+};
+
+struct MeshletsPassOutputWithDepth
+{
+    float4 m_color  : SV_Target0;
+    float m_depth : SV_Depth;
+};
+
+// Shader Resource Groups
+#include <viewsrg.srgi>
+#include <MeshletsPerObjectRenderSrg.azsli>
+
+#include <Atom/RPI/Math.azsli>
+
+//! Utility function for vertex shaders to transform vertex tangent, bitangent, and normal vectors into world space.
+void ConstructTBN(float3 vertexNormal, float4 vertexTangent, float3 vertexBitangent, 
+    float4x4 localToWorld, float3x3 localToWorldInverseTranspose, 
+    out float3 normalWS, out float3 tangentWS, out float3 bitangentWS)
+{
+    normalWS = normalize(mul(localToWorldInverseTranspose, vertexNormal));
+    tangentWS = normalize(mul(localToWorld, float4(vertexTangent.xyz, 0)).xyz);
+    bitangentWS = normalize(mul(localToWorld, float4(vertexBitangent, 0)).xyz);
+}
+
+//! @param skipShadowCoords can be useful for example when PixelDepthOffset is enable, because the pixel shader will have to run before the final world position is known
+void VertexHelper(uint vertexIndex, inout VSOutput OUT, float3 worldPosition)
+{
+    OUT.m_worldPosition = worldPosition;
+
+    // This paragraph is temporary to force the shader to include the Srg.
+    float dummyVariableToForceSrg = 0.000001 * (float)PassSrg::m_meshletsSharedBuffer[vertexIndex];
+    OUT.m_worldPosition.z += (abs(dummyVariableToForceSrg) < 0.001) ? dummyVariableToForceSrg : 0;
+
+    OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(OUT.m_worldPosition, 1.0));
+
+    float4x4 objectToWorld = MeshletsObjectRenderSrg::GetWorldMatrix();
+    float3x3 objectToWorldIT = MeshletsObjectRenderSrg::GetWorldMatrixInverseTranspose();
+
+    ConstructTBN( 
+        MeshletsObjectRenderSrg::GetNormal(vertexIndex),  
+        MeshletsObjectRenderSrg::GetTangent(vertexIndex), 
+        MeshletsObjectRenderSrg::GetBiTangent(vertexIndex), 
+        objectToWorld, objectToWorldIT, 
+        OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
+}
+
+VSOutput MeshletsDebugRender_MainPassVS(VSInput IN)
+{
+    VSOutput OUT;
+ 
+    // Idea - test vertexIndex by sending to render as a position.
+    float3 localPosition = MeshletsObjectRenderSrg::GetPosition(IN.m_vertexIndex);
+    float3 worldPosition = mul(MeshletsObjectRenderSrg::GetWorldMatrix(), float4(localPosition, 1.0)).xyz;
+ 
+    VertexHelper(IN.m_vertexIndex, OUT, worldPosition);
+
+    OUT.m_uv = MeshletsObjectRenderSrg::GetUV(IN.m_vertexIndex);
+
+    return OUT;
+}
+
+MeshletsPassOutput MeshletsDebugRender_MainPassPS(VSOutput IN)
+{
+    // ------- Output -------
+    MeshletsPassOutput OUT;
+
+    // Debug purposes - uv will be colored by the Compute that 
+    // will generate the meshlets indices.
+    OUT.m_color = float4(IN.m_uv.rg, 0, 1);
+
+    return OUT;
+}
+

+ 42 - 0
Gems/Meshlets/Assets/Shaders/MeshletsDebugRenderShader.shader

@@ -0,0 +1,42 @@
+{
+    "Source" : "./MeshletsDebugRenderShader.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": "MeshletsDebugRender_MainPassVS",
+          "type": "Vertex"
+        },
+        {
+          "name": "MeshletsDebugRender_MainPassPS",
+          "type": "Fragment"
+        }
+      ]
+    },
+
+    "DrawList" : "MeshletsDrawList"
+}

+ 102 - 0
Gems/Meshlets/Assets/Shaders/MeshletsPerObjectRenderSrg.azsli

@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <scenesrg.srgi>
+
+/*
+// Utilize the following PerDraw srg for instancing - currently not used yet.
+ShaderResourceGroup MeshletsInstanceRenderSrg : SRG_PerDraw
+{
+    //! used object Id for instancing and matrix retrieval
+    uint m_objectId;
+
+    //! Returns the matrix for transforming points from Object Space to World Space.
+    float4x4 GetWorldMatrix()
+    {
+        return SceneSrg::GetObjectToWorldMatrix(m_objectId);
+    }
+
+    //! Returns the inverse-transpose of the world matrix.
+    //! Commonly used to transform normals while supporting non-uniform scale.
+    float3x3 GetWorldMatrixInverseTranspose()
+    {
+        return SceneSrg::GetObjectToWorldInverseTransposeMatrix(m_objectId);
+    }
+}
+*/
+
+ShaderResourceGroup MeshletsObjectRenderSrg : SRG_PerObject
+{
+    //--------------------------------------------------
+    // This part should be moved outside the PerObject srg and used in the PerDraw 
+    // srg that represents the instance frequency.
+    // object Id is used as an index for instancing and matrix retrieval in the 
+    // SceneSrg matrices arrays.
+    uint m_objectId;
+
+    //! Returns the matrix for transforming points from Object Space to World Space.
+    float4x4 GetWorldMatrix()
+    {
+        return SceneSrg::GetObjectToWorldMatrix(m_objectId);
+    }
+
+    //! Returns the inverse-transpose of the world matrix.
+    //! Commonly used to transform normals while supporting non-uniform scale.
+    float3x3 GetWorldMatrixInverseTranspose()
+    {
+        return SceneSrg::GetObjectToWorldInverseTransposeMatrix(m_objectId);
+    }
+
+    //---------------------------------------------------
+    // Vertex streams - currently we use Buffer<float> instead of the exact required
+    // data type (float3 for example) because of alignment problem in Atom buffer 
+    // creation from a common pool.
+    // [To Do] - change when supported to prevent offset calculation overhead.
+//    Buffer<float3> m_positions;
+//    Buffer<float3> m_normals;
+//    Buffer<float4> m_tangents; 
+//    Buffer<float3> m_bitangents; 
+
+    Buffer<float> m_positions;
+    Buffer<float> m_normals;
+    Buffer<float4> m_tangents; 
+    Buffer<float> m_bitangents; 
+
+    Buffer<float2> m_uvs;
+
+    float3 GetPosition(uint vertexIndex)
+    {
+        uint index = vertexIndex * 3;
+        return float3(m_positions[index], m_positions[index+1], m_positions[index+2]);
+    }
+
+    float3 GetNormal(uint vertexIndex)
+    {
+        uint index = vertexIndex * 3;
+        return float3(m_normals[index], m_normals[index+1], m_normals[index+2]);
+    }
+
+    float3 GetBiTangent(uint vertexIndex)
+    {
+        uint index = vertexIndex * 3;
+        return float3(m_bitangents[index], m_bitangents[index+1], m_bitangents[index+2]);
+    }
+
+    // The last element of the tangents indicates face direction / winding
+    float4 GetTangent(uint vertexIndex)
+    {
+        return m_tangents[vertexIndex];
+    }
+
+    float2 GetUV(uint vertexIndex)
+    {
+        return m_uvs[vertexIndex];
+    }
+}

+ 21 - 0
Gems/Meshlets/CMakeLists.txt

@@ -0,0 +1,21 @@
+#
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+#
+#
+
+set(gem_path ${CMAKE_CURRENT_LIST_DIR})
+set(gem_json ${gem_path}/gem.json)
+# o3de_restricted_path(${gem_json} gem_restricted_path gem_parent_relative_path)
+
+# o3de_pal_dir(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} "${gem_restricted_path}" "${gem_path}" "${gem_parent_relative_path}")
+
+# Now that we have the platform abstraction layer (PAL) folder for this folder, thats where we will find the
+# project cmake for this platform.
+# include(${pal_dir}/${PAL_PLATFORM_NAME_LOWERCASE}_gem.cmake)
+
+ly_add_external_target_path(${CMAKE_CURRENT_LIST_DIR}/3rdParty)
+
+add_subdirectory(Code)

+ 179 - 0
Gems/Meshlets/Code/CMakeLists.txt

@@ -0,0 +1,179 @@
+#
+# 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
+#
+#
+
+# Currently we are in the Code folder: ${CMAKE_CURRENT_LIST_DIR}
+# Get the platform specific folder ${pal_dir} for the current folder: ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}
+# Note: o3de_pal_dir will take care of the details for us, as this may be a restricted platform
+#       in which case it will see if that platform is present here or in the restricted folder.
+#       i.e. It could here in our gem : Gems/Meshlets/Code/Platform/<platorm_name>  or
+#            <restricted_folder>/<platform_name>/Gems/Meshlets/Code
+#o3de_pal_dir(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} "${gem_restricted_path}" "${gem_path}" "${gem_name}")
+
+# Now that we have the platform abstraction layer (PAL) folder for this folder, thats where we will find the
+# traits for this platform. Traits for a platform are defines for things like whether or not something in this gem
+# is supported by this platform.
+#include(${pal_dir}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
+
+# Add the Meshlets.Static target
+# Note: We include the common files and the platform specific files which are set in meshlets_common_files.cmake
+# and in ${pal_dir}/meshlets_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
+ly_add_target(
+    NAME Meshlets.Static STATIC
+    NAMESPACE Gem
+    FILES_CMAKE
+        meshlets_files.cmake
+#        ${pal_dir}/meshlets_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
+    INCLUDE_DIRECTORIES
+        PUBLIC
+            Include
+            External/Include
+            Source
+            Source/Meshlets
+        PRIVATE
+            External/Include
+            Source
+            Source/Meshlets
+    BUILD_DEPENDENCIES
+        PUBLIC
+            AZ::AzCore
+            AZ::AzFramework
+            Gem::AtomLyIntegration_CommonFeatures.Static
+            Gem::Atom_RPI.Public
+)
+
+# Here add Meshlets target, it depends on the Meshlets.Static
+ly_add_target(
+    NAME Meshlets ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
+    NAMESPACE Gem
+    FILES_CMAKE
+        meshlets_shared_files.cmake
+#        ${pal_dir}/meshlets_shared_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
+    INCLUDE_DIRECTORIES
+        PUBLIC
+            Include
+            Source
+            Source/Meshlets
+        PRIVATE
+            Source
+            Source/Meshlets
+    BUILD_DEPENDENCIES
+        PRIVATE
+            Gem::Meshlets.Static
+)
+
+# By default, we will specify that the above target Meshlets would be used by
+# Client and Server type targets when this gem is enabled.  If you don't want it
+# active in Clients or Servers by default, delete one of both of the following lines:
+ly_create_alias(NAME Meshlets.Clients NAMESPACE Gem TARGETS Gem::Meshlets)
+ly_create_alias(NAME Meshlets.Servers NAMESPACE Gem TARGETS Gem::Meshlets)
+
+# If we are on a host platform, we want to add the host tools targets like the Meshlets.Editor target which
+# will also depend on Meshlets.Static
+if(PAL_TRAIT_BUILD_HOST_TOOLS)
+    ly_add_target(
+        NAME Meshlets.Editor.Static STATIC
+        NAMESPACE Gem
+        FILES_CMAKE
+            meshlets_editor_files.cmake
+        INCLUDE_DIRECTORIES
+            PRIVATE
+                External/Include
+                Source
+                Source/Meshlets
+            PUBLIC
+                Include
+        BUILD_DEPENDENCIES
+            PUBLIC
+                AZ::AzToolsFramework
+                Gem::Meshlets.Static
+    )
+
+    ly_add_target(
+        NAME Meshlets.Editor GEM_MODULE
+        NAMESPACE Gem
+        AUTOMOC
+        FILES_CMAKE
+            meshlets_editor_shared_files.cmake
+        INCLUDE_DIRECTORIES
+            PRIVATE
+                Source
+                Source/Meshlets
+            PUBLIC
+                Include
+        BUILD_DEPENDENCIES
+            PUBLIC
+                Gem::Meshlets.Editor.Static
+    )
+
+    # By default, we will specify that the above target Meshlets would be used by
+    # Tool and Builder type targets when this gem is enabled.  If you don't want it
+    # active in Tools or Builders by default, delete one of both of the following lines:
+    ly_create_alias(NAME Meshlets.Tools    NAMESPACE Gem TARGETS Gem::Meshlets.Editor)
+    ly_create_alias(NAME Meshlets.Builders NAMESPACE Gem TARGETS Gem::Meshlets.Editor)
+
+
+endif()
+
+################################################################################
+# Tests
+################################################################################
+# See if globally, tests are supported
+if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
+    # We globally support tests, see if we support tests on this platform for Meshlets.Static
+    if(PAL_TRAIT_MESHLETS_TEST_SUPPORTED)
+        # We support Meshlets.Tests on this platform, add Meshlets.Tests target which depends on Meshlets.Static
+        ly_add_target(
+            NAME Meshlets.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
+            NAMESPACE Gem
+            FILES_CMAKE
+                meshlets_files.cmake
+                meshlets_tests_files.cmake
+            INCLUDE_DIRECTORIES
+                PRIVATE
+                    Tests
+                    Source
+            BUILD_DEPENDENCIES
+                PRIVATE
+                    AZ::AzTest
+                    AZ::AzFramework
+                    Gem::Meshlets.Static
+        )
+
+        # Add Meshlets.Tests to googletest
+        ly_add_googletest(
+            NAME Gem::Meshlets.Tests
+        )
+    endif()
+
+    # If we are a host platform we want to add tools test like editor tests here
+    if(PAL_TRAIT_BUILD_HOST_TOOLS)
+        # We are a host platform, see if Editor tests are supported on this platform
+        if(PAL_TRAIT_MESHLETS_EDITOR_TEST_SUPPORTED)
+            # We support Meshlets.Editor.Tests on this platform, add Meshlets.Editor.Tests target which depends on Meshlets.Editor
+            ly_add_target(
+                NAME Meshlets.Editor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
+                NAMESPACE Gem
+                FILES_CMAKE
+                    meshlets_editor_tests_files.cmake
+                INCLUDE_DIRECTORIES
+                    PRIVATE
+                        Tests
+                        Source
+                BUILD_DEPENDENCIES
+                    PRIVATE
+                        AZ::AzTest
+                        Gem::Meshlets.Editor
+            )
+
+            # Add Meshlets.Editor.Tests to googletest
+            ly_add_googletest(
+                NAME Gem::Meshlets.Editor.Tests
+            )
+        endif()
+    endif()
+endif()

+ 42 - 0
Gems/Meshlets/Code/Include/Meshlets/MeshletsBus.h

@@ -0,0 +1,42 @@
+
+/*
+* Modifications Copyright (c) Contributors to the Open 3D Engine Project. 
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+* 
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+#pragma once
+
+#include <AzCore/EBus/EBus.h>
+#include <AzCore/Interface/Interface.h>
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        class MeshletsRequests
+        {
+        public:
+            AZ_RTTI(MeshletsRequests, "{c518217d-aa8a-47dc-8f2c-b63b4720d481}");
+            virtual ~MeshletsRequests() = default;
+            // Put your public methods here
+        };
+
+        class MeshletsBusTraits
+            : public AZ::EBusTraits
+        {
+        public:
+            //////////////////////////////////////////////////////////////////////////
+            // EBusTraits overrides
+            static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
+            static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
+            //////////////////////////////////////////////////////////////////////////
+        };
+
+        using MeshletsRequestBus = AZ::EBus<MeshletsRequests, MeshletsBusTraits>;
+        using MeshletsInterface = AZ::Interface<MeshletsRequests>;
+
+    } // namespace Meshlets
+} // namespace AZ

+ 525 - 0
Gems/Meshlets/Code/Source/Meshlets/MeshletsAssets.cpp

@@ -0,0 +1,525 @@
+/*
+* Copyright (c) Contributors to the Open 3D Engine Project.
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+*
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+#pragma once
+
+#include <AzCore/Math/Aabb.h>
+
+#include <Atom/RPI.Public/Image/StreamingImage.h>
+#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
+
+#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
+#include <Atom/RPI.Public/MeshDrawPacket.h>
+
+#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
+#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
+#include <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
+#include <Atom/RPI.Reflect/ResourcePoolAssetCreator.h>
+#include <Atom/RPI.Reflect/Model/ModelAsset.h>
+#include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
+#include <Atom/RPI.Reflect/Model/ModelLodAssetCreator.h>
+
+#include <vector>
+
+#include "MeshletsAssets.h"
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        //======================================================================
+        //                            MeshletsModel
+        //======================================================================
+        uint32_t MeshletsModel::s_modelNumber = 0;
+
+        //----------------------------------------------------------------------
+        // Meshlets Generation.
+        // Resulted operation will alter the mesh 
+        //----------------------------------------------------------------------
+        void MeshletsModel::debugMarkMeshletsUVs(GeneratorMesh& mesh)   
+        {
+            for (uint32_t meshletId = 0; meshletId < m_meshletsData.Descriptors.size(); ++meshletId)
+            {
+                meshopt_Meshlet& meshlet = m_meshletsData.Descriptors[meshletId];
+                float textureCoordU = (meshletId % 3) * 0.5f;
+                float textureCoordV = ((meshletId / 3) % 3) * 0.5f;
+                for (uint32_t triIdx = 0 ; triIdx < meshlet.triangle_count; ++triIdx)
+                {
+                    uint32_t encodedTri = m_meshletsData.EncodedTriangles[meshlet.triangle_offset + triIdx];
+                    // Next bring decode the uint32_t and separate into three elements.
+                    uint8_t vtxIndirectIndex[3] = {
+                        uint8_t((encodedTri >> 0) & 0xff),
+                        uint8_t((encodedTri >> 8) & 0xff),
+                        uint8_t((encodedTri >> 16) & 0xff)
+                    }; 
+
+                    for (uint32_t vtx = 0; vtx < 3; ++vtx)
+                    {
+                        uint32_t vtxIndex = m_meshletsData.IndicesIndirection[meshlet.vertex_offset + vtxIndirectIndex[vtx]];
+
+                        mesh.vertices[vtxIndex].tx = textureCoordU;
+                        mesh.vertices[vtxIndex].ty = textureCoordV;
+                    }
+                } 
+            }
+        }
+
+        // The results of using this function will alter the mesh UVs
+        void debugMarkVertexUVs(GeneratorMesh& mesh)
+        {
+            for (uint32_t vtxIndex = 0; vtxIndex < mesh.vertices.size(); ++vtxIndex)
+            {
+                float tx = (vtxIndex % 5) * 0.25f;
+                float ty = ((vtxIndex / 5) % 5) * 0.25f;
+                mesh.vertices[vtxIndex].tx = tx;
+                mesh.vertices[vtxIndex].ty = ty;
+            }
+        }
+
+        Data::Instance<RPI::ShaderResourceGroup> MeshletsModel::CreateShaderResourceGroup(
+            Data::Instance<RPI::Shader> shader,
+            const char* shaderResourceGroupId,
+            [[maybe_unused]] const char* moduleName)
+        {
+            Data::Instance<RPI::ShaderResourceGroup> srg = RPI::ShaderResourceGroup::Create(shader->GetAsset(), AZ::Name{ shaderResourceGroupId });
+            if (!srg)
+            {
+                AZ_Error(moduleName, false, "Failed to create shader resource group");
+                return nullptr;
+            }
+            return srg;
+        }
+
+        uint32_t MeshletsModel::CreateMeshlets(GeneratorMesh& mesh)
+        {
+            const size_t max_vertices = 64;
+            const size_t max_triangles = 64;  // NVidia-recommended 126, rounded down to a multiple of 4 - set to 64 based on GPU and data generated
+            const float cone_weight = 0.5f;   // note: should be set to 0 unless cone culling is used at runtime!
+
+            //----------------------------------------------------------------
+            size_t max_meshlets = meshopt_buildMeshletsBound(mesh.indices.size(), max_vertices, max_triangles);
+
+/*
+            // NO scan seems to return more localized meshlets
+            m_meshletsData.meshlets.resize(meshopt_buildMeshlets(
+            &m_meshletsData.meshlets[0], &m_meshletsData.meshlet_vertices[0], &m_meshletsData.meshlet_triangles[0],
+            &mesh.indices[0], mesh.indices.size(),
+            &mesh.vertices[0].px, mesh.vertices.size(), sizeof(GeneratorVertex),
+            max_vertices, max_triangles, cone_weight));
+            
+            if (!m_meshletsData.meshlets.size())
+            {
+                printf("Error generating meshlets - no meshlets were built\n");
+                return 0;
+            }
+
+            // this is an example of how to trim the vertex/triangle arrays when copying data out to GPU storage
+            const meshopt_Meshlet& last = m_meshletsData.meshlets.back();
+            m_meshletsData.meshlet_vertices.resize(last.vertex_offset + last.vertex_count);
+            m_meshletsData.meshlet_triangles.resize(last.triangle_offset + ((last.triangle_count * 3 + 3) & ~3));
+            uint32_t meshletsAmount = (uint32_t) m_meshletsData.meshlets.size();
+            //----------------------------------------------------------------
+            */
+
+            ////////////////////////////
+            std::vector<meshopt_Meshlet> meshlets(max_meshlets);
+            std::vector<unsigned int> meshlet_vertices(max_meshlets * max_vertices);        // Vertex Index indirection map
+            std::vector<unsigned char> meshlet_triangles(max_meshlets * max_triangles * 3); // Meshlet triangles into the vertex index indirection - local to meshlet.
+
+            // NO scan seems to return more localized meshlets
+            meshlets.resize(meshopt_buildMeshlets(
+                &meshlets[0], &meshlet_vertices[0], &meshlet_triangles[0], &mesh.indices[0], mesh.indices.size(),
+                &mesh.vertices[0].px, mesh.vertices.size(), sizeof(GeneratorVertex), max_vertices, max_triangles, cone_weight));
+
+            // this is an example of how to trim the vertex/triangle arrays when copying data out to GPU storage
+            const meshopt_Meshlet& last = meshlets.back();
+            meshlet_vertices.resize(last.vertex_offset + last.vertex_count);
+            meshlet_triangles.resize(last.triangle_offset + ((last.triangle_count * 3 + 3) & ~3));
+            uint32_t meshletsAmount = (uint32_t) meshlets.size();
+
+            m_meshletsData.Descriptors = meshlets;
+            m_meshletsData.IndicesIndirection = meshlet_vertices;
+            m_meshletsData.EncodeTrianglesData(meshlet_triangles);
+            m_meshletsData.ValidateData((uint32_t)meshlet_vertices.size());
+            ////////////////////////////
+
+            // Add this in order to display meshlet separation - debug purpose only
+            static bool markTextureCoordinates = true;
+            if (markTextureCoordinates)
+            {
+                debugMarkMeshletsUVs(mesh);
+            }
+
+            AZ_Warning("Meshlets", false, "Successfully generated [%d] meshlets\n", meshletsAmount);
+            return meshletsAmount;
+        }
+
+        //----------------------------------------------------------------------
+        // Enhanced (Meshlet) Model Creation 
+        //----------------------------------------------------------------------
+        Data::Asset<RPI::BufferAsset> MeshletsModel::CreateBufferAsset(
+            const AZStd::string& bufferName,
+            const RHI::BufferViewDescriptor& bufferViewDescriptor,
+            RHI::BufferBindFlags bufferBindFlags,
+            const void* data
+        )
+        {
+            RPI::BufferAssetCreator creator;
+            creator.Begin(Uuid::CreateRandom());
+
+            RHI::BufferDescriptor bufferDescriptor;
+            bufferDescriptor.m_bindFlags = bufferBindFlags;
+            bufferDescriptor.m_byteCount = static_cast<uint64_t>(bufferViewDescriptor.m_elementSize) * static_cast<uint64_t>(bufferViewDescriptor.m_elementCount);
+
+            if (data)
+            {
+                creator.SetBuffer(data, bufferDescriptor.m_byteCount, bufferDescriptor);
+            }
+
+            creator.SetBufferViewDescriptor(bufferViewDescriptor);
+            creator.SetUseCommonPool(RPI::CommonBufferPoolType::StaticInputAssembly);
+
+            Data::Asset<RPI::BufferAsset> bufferAsset;
+
+            // The next line is the actual buffer asset creation
+            [[maybe_unused]] bool creationSuccessful = creator.End(bufferAsset);
+            AZ_Error("Meshlets", creationSuccessful, "Error -- creating buffer [%s]", bufferName.c_str());
+
+            return bufferAsset;
+        }
+
+        bool MeshletsModel::ProcessBuffersData(float* position, uint32_t vtxNum)
+        {
+            const float maxVertexSizeSqr = 99.9f * 99.9f;  // under 100 meters
+            for (uint32_t vtx = 0; vtx < vtxNum; ++vtx, position += 3)
+            {
+                Vector3 positionV3 = Vector3(position[0], position[1], position[2]);
+
+                float lengthSq = positionV3.GetLengthSq();
+                if (lengthSq < maxVertexSizeSqr)
+                {
+                    m_aabb.AddPoint(positionV3);
+                }
+                else
+                {   
+                    AZ_Warning("Meshlets", false, "Warning -- vertex [%d:%d] out of bound (%.2f, %.2f, %.2f) in model [%s]",
+                        vtx, vtxNum, positionV3.GetX(), positionV3.GetY(), positionV3.GetZ(), m_name.c_str());
+                }
+            }
+
+            AZ_Error("Meshlets", m_aabb.IsValid(), "Error --- Model [%s] AABB is invalid - all [%d] vertices are corrupted",
+                m_name.c_str(), vtxNum);
+            return m_aabb.IsValid() ? true : false;
+        }
+
+        // The following method creates a new model out of a given meshLodAsset.
+        // Moving the creator outside calling method and have the new model contain several
+        // Lods did not work as it needs to be local to the block!
+        uint32_t MeshletsModel::CreateMeshletsModel(
+            const RPI::ModelLodAsset::Mesh& meshAsset )
+        {
+            //-------------------------------------------
+            // Start model creation
+            RPI::ModelAssetCreator modelAssetCreator;
+            Uuid modelId = Uuid::CreateRandom();
+            modelAssetCreator.Begin(Uuid::CreateRandom());
+            modelAssetCreator.SetName(m_name);
+            //-------------------------------------------
+            
+            // setup a stream layout and shader input contract for the vertex streams
+            RHI::Format IndexStreamFormat;
+            RHI::Format streamFromat;
+            RHI::BufferViewDescriptor bufferDescriptors[10];
+
+            uint32_t meshletsAmount = 0;
+            RHI::BufferBindFlags defaultBindFlags = RHI::BufferBindFlags::InputAssembly | RHI::BufferBindFlags::ShaderRead;
+//            RHI::BufferBindFlags readWriteBindFlags = RHI::BufferBindFlags::InputAssembly | RHI::BufferBindFlags::ShaderReadWrite;
+
+            // Index buffer
+            uint32_t indexCount = 0;
+            const RPI::BufferAssetView* bufferAssetView = &meshAsset.GetIndexBufferAssetView();
+            uint8_t* indices = RetrieveBufferData(bufferAssetView, IndexStreamFormat, 0, indexCount, bufferDescriptors[0]);
+
+            // With this flag specified we get an error at buffer pool level in ValidateInitRequest
+//            const auto indicesAsset = CreateBufferAsset(IndicesSemanticName.GetStringView(), bufferDescriptors[0], readWriteBindFlags, indices);
+            const auto indicesAsset = CreateBufferAsset(IndicesSemanticName.GetStringView(), bufferDescriptors[0], defaultBindFlags, indices);
+
+            // Vertex streams
+            uint32_t vertexCount = 0;
+            bufferAssetView = meshAsset.GetSemanticBufferAssetView(PositionSemanticName);
+            float* positions =
+                (float*)RetrieveBufferData(bufferAssetView, streamFromat, 0, vertexCount, bufferDescriptors[1]);
+            const auto positionsAsset = CreateBufferAsset(PositionSemanticName.GetStringView(), bufferDescriptors[1], defaultBindFlags, positions);
+
+            bufferAssetView = meshAsset.GetSemanticBufferAssetView(NormalSemanticName);
+            float* normals = positions ?
+                (float*)RetrieveBufferData(bufferAssetView, streamFromat, vertexCount, vertexCount, bufferDescriptors[2]) :
+                nullptr;
+            const auto normalsAsset = CreateBufferAsset(NormalSemanticName.GetStringView(), bufferDescriptors[2], defaultBindFlags, normals);
+
+            bufferAssetView = meshAsset.GetSemanticBufferAssetView(UVSemanticName);
+            float* texCoords = normals && bufferAssetView ?
+                (float*)RetrieveBufferData(bufferAssetView, streamFromat, vertexCount, vertexCount, bufferDescriptors[3]) :
+                nullptr;
+
+            bufferAssetView = meshAsset.GetSemanticBufferAssetView(TangentSemanticName);
+            [[maybe_unused]] float* tangents = normals && bufferAssetView ?
+                (float*)RetrieveBufferData(bufferAssetView, streamFromat, vertexCount, vertexCount, bufferDescriptors[4]) :
+                nullptr;
+            const auto tangentsAsset = CreateBufferAsset(TangentSemanticName.GetStringView(), bufferDescriptors[4], defaultBindFlags, tangents);
+
+            bufferAssetView = meshAsset.GetSemanticBufferAssetView(BiTangentSemanticName);
+            [[maybe_unused]] float* bitangents = normals && bufferAssetView ?
+                (float*)RetrieveBufferData(bufferAssetView, streamFromat, vertexCount, vertexCount, bufferDescriptors[5]) :
+                nullptr;
+            const auto bitangentsAsset = CreateBufferAsset(BiTangentSemanticName.GetStringView(), bufferDescriptors[5], defaultBindFlags, bitangents);
+
+            if (!positionsAsset || !normalsAsset || !tangentsAsset || !indicesAsset || !texCoords)
+            {
+                AZ_Error("Meshlets", false, "Failed to create meshlet model [%s] - buffer assets were not created successfully", m_name.c_str());
+                return 0;
+            }
+
+            // The following is crucial for the AABB generation - it can be used
+            // for scaling the actual vertices or create transform based on it.
+            ProcessBuffersData(positions, vertexCount);
+
+            meshletsAmount = CreateMeshlets(positions, normals, texCoords, vertexCount,
+                (uint16_t*)indices, indexCount, IndexStreamFormat);
+
+            if (!meshletsAmount)
+            {
+                AZ_Error("Meshlets", false, "Failed to create meshlet model [%s] - the meshlet creation process failed", m_name.c_str());
+//                return 0;
+            }
+
+            // Done only here since we update the UV data to represent meshlets coloring
+            const auto texCoordsAsset = CreateBufferAsset(UVSemanticName.GetStringView(), bufferDescriptors[3], defaultBindFlags, texCoords);
+//            const auto texCoordsAsset = CreateBufferAsset(UVSemanticName.GetStringView(), bufferDescriptors[3], readWriteBindFlags, texCoords);
+
+            // Model LOD Creation
+            {
+                //--------------------------------------------
+                RPI::ModelLodAssetCreator modelLodAssetCreator;
+                modelLodAssetCreator.Begin(Uuid::CreateRandom());
+
+                modelLodAssetCreator.BeginMesh();
+                {
+                    // Original model replication
+                    {
+                        modelLodAssetCreator.SetMeshAabb(AZStd::move(m_aabb));
+                        modelLodAssetCreator.SetMeshName(Name{ m_name.c_str() });
+
+                        modelLodAssetCreator.SetMeshIndexBuffer({ indicesAsset, bufferDescriptors[0] });
+
+                        modelLodAssetCreator.AddMeshStreamBuffer(RHI::ShaderSemantic{ "POSITION" }, PositionSemanticName,
+                            RPI::BufferAssetView{ positionsAsset, bufferDescriptors[1] });
+                        modelLodAssetCreator.AddMeshStreamBuffer(RHI::ShaderSemantic{ "NORMAL" }, NormalSemanticName,
+                            RPI::BufferAssetView{ normalsAsset, bufferDescriptors[2] });
+                        modelLodAssetCreator.AddMeshStreamBuffer(RHI::ShaderSemantic{ "UV" }, UVSemanticName,
+                            RPI::BufferAssetView{ texCoordsAsset, bufferDescriptors[3] });
+                        modelLodAssetCreator.AddMeshStreamBuffer(RHI::ShaderSemantic{ "TANGENT" }, TangentSemanticName,
+                            RPI::BufferAssetView{ tangentsAsset, bufferDescriptors[4] });
+                        modelLodAssetCreator.AddMeshStreamBuffer(RHI::ShaderSemantic{ "BITANGENT" }, BiTangentSemanticName,
+                            RPI::BufferAssetView{ bitangentsAsset, bufferDescriptors[5] });
+                    }
+
+                    // Meshlets data creation
+                    if (meshletsAmount > 0)
+                    {
+                        // Meshlets descriptors buffer
+                        const auto meshletsDesc = RHI::BufferViewDescriptor::CreateTyped(
+                            0, meshletsAmount, RHI::Format::R32G32B32A32_UINT);
+                        const auto meshletsAsset = CreateBufferAsset(MeshletsDescriptorsName.GetCStr(), meshletsDesc,
+                            defaultBindFlags, (void*)m_meshletsData.Descriptors.data());
+                        modelLodAssetCreator.AddMeshStreamBuffer(RHI::ShaderSemantic{ MeshletsDescriptorsName }, MeshletsDescriptorsName,
+                            RPI::BufferAssetView{ meshletsAsset, meshletsDesc });
+
+                        // Meshlets triangles - sending it as uint32 to simplify calculations
+                        // The triangles data is encoded - each uint32_t is 3 indices of 8 bits.
+                        const auto meshletsTrisDesc = RHI::BufferViewDescriptor::CreateTyped(
+                            0, (uint32_t)m_meshletsData.EncodedTriangles.size(), RHI::Format::R32_UINT);
+                        const auto meshletsTrisAsset = CreateBufferAsset(MeshletsTrianglesName.GetCStr(), meshletsTrisDesc,
+                            defaultBindFlags, (void*)m_meshletsData.EncodedTriangles.data());
+                        modelLodAssetCreator.AddMeshStreamBuffer(RHI::ShaderSemantic{ MeshletsTrianglesName }, MeshletsTrianglesName,
+                            RPI::BufferAssetView{ meshletsTrisAsset, meshletsTrisDesc });
+
+                        // Meshlets indirect indices buffer
+                        const auto meshletsIndicesDesc = RHI::BufferViewDescriptor::CreateTyped(
+                            0, (uint32_t)m_meshletsData.IndicesIndirection.size(), RHI::Format::R32_UINT);
+                        const auto meshletsIndicesAsset = CreateBufferAsset(MeshletsIndicesLookupName.GetCStr(), meshletsIndicesDesc,
+                            defaultBindFlags, (void*)m_meshletsData.IndicesIndirection.data());
+                        modelLodAssetCreator.AddMeshStreamBuffer(RHI::ShaderSemantic{ MeshletsIndicesLookupName }, MeshletsIndicesLookupName,
+                            RPI::BufferAssetView{ meshletsIndicesAsset, meshletsIndicesDesc });
+                    }
+                }
+                modelLodAssetCreator.EndMesh();
+                
+                // Create the model LOD based on the model LOD asset we created
+                Data::Asset<RPI::ModelLodAsset> modelLodAsset;
+                if (!modelLodAssetCreator.End(modelLodAsset))
+                {
+                    AZ_Error("Meshlets", false, "Error -- creating model [%s] - ModelLoadAssetCreator.End() failed",
+                        m_name.c_str());
+                    return 0;
+                }
+
+                // Add the LOD model asset created to the model asset.
+                modelAssetCreator.AddLodAsset(AZStd::move(modelLodAsset));
+
+                //-------------------------------------------
+                // Final stage - create the model based on the created assets
+                Data::Asset<RPI::ModelAsset> modelAsset;
+
+                if (!modelAssetCreator.End(modelAsset))
+                {
+                    AZ_Error("Meshlets", false, "Error -- creating model [%s] - model asset was not created", m_name.c_str());
+                    return 0;
+                }
+
+                m_meshletsModel = RPI::Model::FindOrCreate(modelAsset);
+                if (!m_meshletsModel)
+                {
+                    AZ_Error("Meshlets", false, "Error -- creating model [%s] - model could not be found or created", m_name.c_str());
+                    return 0;
+                }
+                //-------------------------------------------
+            }
+
+            return meshletsAmount;
+        }
+
+
+        //----------------------------------------------------------------------
+        // Model Traversal and Data Copy for Creation 
+        //----------------------------------------------------------------------
+        uint8_t* MeshletsModel::RetrieveBufferData(
+            const RPI::BufferAssetView* bufferView,
+            RHI::Format& format,
+            uint32_t expectedAmount, uint32_t &existingAmount,
+            RHI::BufferViewDescriptor& bufferDesc
+        )  
+        {
+            const Data::Asset<RPI::BufferAsset>& bufferAsset = bufferView->GetBufferAsset();
+//            const RHI::BufferViewDescriptor& bufferDesc = bufferView->GetBufferViewDescriptor();
+            bufferDesc = bufferView->GetBufferViewDescriptor();
+            existingAmount = bufferDesc.m_elementCount;
+
+            if ((bufferDesc.m_elementOffset != 0) ||
+                ((existingAmount != expectedAmount) && (expectedAmount != 0)))
+            {
+                AZ_Error("Meshlets", false, "More than a single mesh, or non-matching elements count");
+                return nullptr;
+            }
+            format = bufferDesc.m_elementFormat;
+            AZStd::span<const uint8_t> bufferData = bufferAsset->GetBuffer();
+            return (uint8_t *) bufferData.data();
+        }
+
+        uint32_t MeshletsModel::CreateMeshlets(
+            float* positions, float* normals, float* texCoords, uint32_t vtxNum,
+            uint16_t* indices, uint32_t idxNum, RHI::Format IndexStreamFormat
+        )
+        {
+            GeneratorMesh mesh;
+
+            // Filling the mesh data for the meshlet library
+            mesh.vertices.resize(vtxNum);
+            for (uint32_t vtx = 0, posIdx=0, uvIdx=0; vtx < vtxNum; ++vtx, posIdx+=3, uvIdx+=2)
+            {
+                GeneratorVertex& genVtx = mesh.vertices[vtx];
+                genVtx.px = positions[posIdx];
+                genVtx.py = positions[posIdx+1];
+                genVtx.pz = positions[posIdx+2];
+                genVtx.nx = normals[posIdx];
+                genVtx.ny = normals[posIdx + 1];
+                genVtx.nz = normals[posIdx + 2];
+                genVtx.tx = texCoords[uvIdx];
+                genVtx.ty = texCoords[uvIdx + 1];
+            }
+
+            mesh.indices.resize(idxNum);
+            if (IndexStreamFormat == RHI::Format::R16_UINT)
+            {   // 16 bits index format 0..64K vertices
+                for (uint32_t idx = 0; idx < idxNum; ++idx)
+                {
+                    mesh.indices[idx] = (unsigned int)indices[idx];
+                }
+            }
+            else
+            {   // simple memcpy since elements are of 4 bytes size
+                memcpy(mesh.indices.data(), indices, idxNum * 4);
+            }
+
+            static bool createMeshlets = true;
+            uint32_t meshletsAmount = 0;
+            if (createMeshlets)
+            {
+                meshletsAmount = CreateMeshlets(mesh);
+            }
+            else
+            {
+                debugMarkVertexUVs(mesh);
+            }
+
+            // Copy back the altered UVs for visual verification
+            for (uint32_t vtx = 0, uvIdx = 0; vtx < vtxNum; ++vtx, uvIdx += 2)
+            {
+                GeneratorVertex& genVtx = mesh.vertices[vtx];
+
+                texCoords[uvIdx] = genVtx.tx;
+                texCoords[uvIdx + 1] = genVtx.ty;
+            }
+
+            return meshletsAmount;
+        }
+
+        uint32_t MeshletsModel::CreateMeshletsFromModelAsset(Data::Asset<RPI::ModelAsset> sourceModelAsset)
+        {
+            uint32_t meshletsAmount = 0;
+            m_sourceModelAsset = sourceModelAsset;
+
+            for (const Data::Asset<RPI::ModelLodAsset>& lodAsset : sourceModelAsset->GetLodAssets())
+            {
+                for (const RPI::ModelLodAsset::Mesh& meshAsset : lodAsset->GetMeshes())
+                {
+                    meshletsAmount += CreateMeshletsModel(meshAsset);
+                }
+            }
+
+            AZ_Warning("Meshlets", false, "Meshlet model [%s] was created", m_name.c_str());
+
+            return meshletsAmount;
+        }
+
+        MeshletsModel::MeshletsModel(Data::Asset<RPI::ModelAsset> sourceModelAsset)
+        {
+            IndicesSemanticName = Name{ "INDICES" };
+            PositionSemanticName = Name{ "POSITION" };
+            NormalSemanticName = Name{ "NORMAL" };
+            TangentSemanticName = Name{ "TANGENT" };
+            BiTangentSemanticName = Name{ "BITANGENT" };
+            UVSemanticName = Name{ "UV" };
+
+
+            MeshletsDescriptorsName = Name{ "MESHLETS" };
+            MeshletsTrianglesName = Name{ "MESHLETS_TRIANGLES" };     
+            MeshletsIndicesLookupName = Name{ "MESHLETS_LOOKUP" };     
+
+            m_name = "Model_" + AZStd::to_string(s_modelNumber++);
+            m_aabb = Aabb::CreateNull();
+
+            m_meshletsAmount = CreateMeshletsFromModelAsset(sourceModelAsset);
+        }
+
+        MeshletsModel::~MeshletsModel()
+        {
+        }
+
+    } // namespace Meshlets
+} // namespace AZ

+ 137 - 0
Gems/Meshlets/Code/Source/Meshlets/MeshletsAssets.h

@@ -0,0 +1,137 @@
+/*
+* Modifications Copyright (c) Contributors to the Open 3D Engine Project. 
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+* 
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+#pragma once
+
+#include <AzCore/base.h>
+#include <AzCore/Name/Name.h>
+#include <AzCore/Asset/AssetCommon.h>
+#include <AzCore/std/containers/map.h>
+#include <AzCore/std/containers/list.h>
+
+#include <AtomCore/Instance/Instance.h>
+#include <AtomCore/Instance/InstanceData.h>
+
+#include <Atom/RHI/ImagePool.h>
+
+#include <Atom/RPI.Public/Image/StreamingImage.h>
+#include <Atom/RPI.Public/Material/Material.h>
+#include <Atom/RPI.Public/Model/Model.h>
+#include <Atom/RPI.Reflect/Model/ModelAsset.h>
+
+#include "../../External/meshoptimizer.h"
+
+#include <MeshletsDispatchItem.h>
+#include <MeshletsData.h>
+
+namespace AZ
+{
+    namespace RPI
+    {
+        class MeshDrawPacket;
+    }
+
+    namespace Meshlets
+    {
+        //======================================================================
+        //!       Reference Class to Demonstrate Meshlets Prep in CPU
+        //! The following class is taking in ModelAsset and based on it, generates
+        //! a new Atom Model that now contains enhanced meshlet data.
+        //! This class is built to demonstrate and used as reference for using the mesh data
+        //! to generate the meshlets on the fly and send them to the regular render.
+        //! This is NOT the class that will be used to create indirect Compute and Draw calls.
+        //! For this we will be using the MeshletRenderObject class.
+        //! 
+        //! Currently assuming single model without Lods so that the handling of the
+        //! meshlet creation and handling of the array is easier. If several meshes or Lods
+        //! exist, they will be created as separate models and the last model's instance
+        //! will be kept in this class.
+        //! Each one of the ModelLods contains a vector of meshes, representing possible
+        //! multiple element within the mesh - to fully represent a mesh, the replication
+        //! method will need to run and gather all data, unify it within a single stream
+        //! and address from each of the Lods.
+        //======================================================================
+        class MeshletsModel
+        {
+        public:
+            static uint32_t s_modelNumber;
+
+            Name IndicesSemanticName;
+
+            Name PositionSemanticName;
+            Name NormalSemanticName;
+            Name TangentSemanticName;
+            Name BiTangentSemanticName;
+            Name UVSemanticName;
+
+            Name MeshletsDescriptorsName;
+            Name MeshletsTrianglesName;
+            Name MeshletsIndicesLookupName;
+
+            MeshletsModel(Data::Asset<RPI::ModelAsset> sourceModelAsset);
+            ~MeshletsModel();
+
+            static  Data::Instance<RPI::ShaderResourceGroup> CreateShaderResourceGroup(
+                Data::Instance<RPI::Shader> shader,
+                const char* shaderResourceGroupId,
+                [[maybe_unused]] const char* moduleName
+            );
+
+            Data::Instance<RPI::Model> GetMeshletsModel() { return m_meshletsModel; }
+
+            AZStd::string& GetName() { return m_name;  }
+
+        protected:
+            bool ProcessBuffersData(float* position, uint32_t vtxNum);
+            void debugMarkMeshletsUVs(GeneratorMesh& mesh);
+
+            uint32_t CreateMeshlets(GeneratorMesh& mesh);
+
+            uint8_t* RetrieveBufferData(
+                const RPI::BufferAssetView* bufferView,
+                RHI::Format& format,
+                uint32_t expectedAmount, uint32_t& existingAmount,
+                RHI::BufferViewDescriptor& bufferDesc
+            );
+
+            Data::Asset<RPI::BufferAsset> CreateBufferAsset(
+                const AZStd::string& bufferName,
+                const RHI::BufferViewDescriptor& bufferViewDescriptor,
+                RHI::BufferBindFlags bufferBindFlags,
+                const void* data);
+
+            uint32_t CreateMeshlets(
+                float* positions, float* normals, float* texCoords, uint32_t vtxNum,
+                uint16_t* indices, uint32_t idxNum, RHI::Format IndexStreamFormat
+            );
+            uint32_t CreateMeshletsFromModelAsset(Data::Asset<RPI::ModelAsset> sourceModelAsset);
+
+            uint32_t CreateMeshletsModel(const RPI::ModelLod& modelLod);
+            uint32_t CreateMeshletsModel(const RPI::ModelLodAsset::Mesh& meshAsset);
+
+            uint32_t GetMehsletsAmount() { return m_meshletsAmount;  }
+
+        private:
+            AZStd::string m_name;
+
+            Data::Instance<RPI::Shader> m_meshletsDataPrepComputeShader;
+
+            Aabb m_aabb;    // Should be per Lod per mesh and not global
+
+            Data::Asset<RPI::ModelAsset> m_sourceModelAsset;
+
+            Data::Instance<RPI::Model> m_meshletsModel;
+
+            // Meshlets data should be a vector of meshlets data per lod per mesh
+            MeshletsData m_meshletsData;    // the actual mesh meshlets' data
+
+            uint32_t m_meshletsAmount = 0;
+        };
+
+    } // namespace Meshlets
+} // namespace AZ

+ 181 - 0
Gems/Meshlets/Code/Source/Meshlets/MeshletsData.h

@@ -0,0 +1,181 @@
+/*
+* Modifications Copyright (c) Contributors to the Open 3D Engine Project. 
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+* 
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+#pragma once
+
+#include <AzCore/base.h>
+#include <AzCore/std/containers/map.h>
+#include <AzCore/std/containers/list.h>
+
+#include "../../External/meshoptimizer.h"
+
+namespace AZ
+{
+    namespace RPI
+    {
+        class MeshDrawPacket;
+    }
+
+    namespace Meshlets
+    {
+        enum class ComputeStreamsSemantics : uint8_t
+        {
+            MeshletsData = 0,
+            MehsletsTriangles,
+            MeshletsIndicesIndirection,
+
+            // for debug coloring purposes
+            UVs,
+            Indices,
+
+            NumBufferStreams
+        };
+
+        enum class RenderStreamsSemantics : uint8_t
+        {
+            Positions = 0,
+            Normals,
+            Tangents,
+            BiTangents,
+
+            UVs,
+            Indices,
+
+            NumBufferStreams
+        };
+
+        struct MeshletDescriptor
+        {
+            //! Offset into the indirect indices array representing the global index of
+            //! all the meshlet vertices.
+            //! The Indirect vertices array is built as follows:
+            //!     std::vector<uint32_t> indirectIndices;
+            //!     indirectIndices = { { meshlet 1 vertex indices }, { meshlet 2 }, .. { meshlet n} }
+            uint32_t vertexOffset;      // In uint32_t steps
+
+            //! Offset into the global meshlets triangleIndices array represented as:
+            //!     std::vector<uint8_t> triangleIndices;
+            //!     triangleIndices = { {meshlet 1 local indices group}, ... { meshlet n} }
+            //! The local indices are an 8 bits index that can represent up to 256 entries.
+            uint32_t triangleOffset;    // In bytes from the start of the array
+
+            //! Finding a vertex within the meshlet is done like that:
+            //!     triangleOffset = currentMeshlet.triangleOffset + meshletTrIndex * 3;
+            //!     localIndex_i = meshletTriangles[triangleOffset + i];    // i = triangle vertex index 0..2
+            //!     vertexIndex_i =  indirectIndices[currentMeshlet.vertexOffset + localIndex_i];
+
+            //! Amount of vertices and triangle for the meshlet - based on this the arrays
+            //! indirectIndices and triangleIndices are created per meshlet.
+            uint32_t vertexCount;
+            uint32_t triangleCount;
+        };
+
+        struct GeneratorVertex
+        {
+            float px, py, pz;
+            float nx, ny, nz;
+            float tx, ty;
+        };
+
+        struct GeneratorMesh
+        {
+            std::vector<GeneratorVertex> vertices;
+            std::vector<unsigned int> indices;
+        };
+
+        //======================================================================
+        struct MeshletsData
+        {
+            std::vector<meshopt_Meshlet> Descriptors;
+            std::vector<uint32_t> EncodedTriangles;     // Meshlet triangles local indices [0..256]
+            std::vector<uint32_t> IndicesIndirection;   // Vertex Index indirection map - local to global
+
+            bool ValidateData(uint32_t vtxCount)
+            {
+                bool validData = true;
+                for (uint32_t meshletId = 0; meshletId < Descriptors.size(); ++meshletId)
+                {
+                    meshopt_Meshlet& meshlet = Descriptors[meshletId];
+
+                    for (uint32_t triIdx = 0; triIdx < meshlet.triangle_count; ++triIdx)
+                    {
+                        uint32_t encodedTri = EncodedTriangles[meshlet.triangle_offset + triIdx];
+                        // Next bring decode the uint32_t and separate into three elements.
+                        uint8_t vtxIndirectIndex[3] = {
+                            uint8_t((encodedTri >> 0) & 0xff),
+                            uint8_t((encodedTri >> 8) & 0xff),
+                            uint8_t((encodedTri >> 16) & 0xff)
+                        };
+
+                        for (uint32_t vtx = 0; vtx < 3; ++vtx)
+                        {
+                            uint32_t vtxIndex = IndicesIndirection[meshlet.vertex_offset + vtxIndirectIndex[vtx]];
+                            if (vtxIndex >= vtxCount)
+                            {
+                                AZ_Warning("Meshlets", false, "Invalid triangle vertex index [%d] - maximum allowed [%d]",
+                                    vtxIndex, vtxCount);
+                                EncodedTriangles[meshlet.triangle_offset + triIdx] = 0;  // Decimate the triangle to the first vertex
+                                validData = false;
+                                break;
+                            }
+                        }
+                    }
+                }
+                return validData;
+            }
+            // Given triangle local index vector, convert the indices to an encoded
+            // triangle vector where every entry of uint32_t represents three indices.
+            // Although it is not as optimal (25% more space), it fits the packing
+            // of data for the GPU using a uint32_t buffer.
+            uint32_t EncodeTrianglesData(std::vector<unsigned char> triangles)
+            {
+                // Start by converting the per byte indices into encoded triangles indices
+                uint32_t triNum = (uint32_t) triangles.size() / 3;
+                EncodedTriangles.resize(triNum);
+                for (uint32_t tri = 0, orgTri = 0; tri < triNum; ++tri, orgTri += 3)
+                {
+                    EncodedTriangles[tri] = 0x00ffffff & 
+                        (triangles[orgTri + 0] << 0) |
+                        (triangles[orgTri + 1] << 8) |
+                        (triangles[orgTri + 2] << 16);
+                }
+
+                // Convert the per index byte offsets into per triangle uint32 offsets
+                for (uint32_t meshletId = 0; meshletId < Descriptors.size(); ++meshletId)
+                {
+                    Descriptors[meshletId].triangle_offset /= 3;
+                }
+
+                return triNum;
+            }
+
+            //! Using the meshlets data, generates a regular u32 vector indices
+            //! This can be used as a debug validation data and send to render.
+            void GenerateDecodedIndices(std::vector<uint32_t>& decodedIndexVector)
+            {
+                uint32_t currentIdx = 0;
+                for (uint32_t meshletId = 0; meshletId < Descriptors.size(); ++meshletId)
+                {
+                    meshopt_Meshlet& meshlet = Descriptors[meshletId];
+                    for (uint32_t tri = 0; tri < meshlet.triangle_count; ++tri)
+                    {
+                        uint32_t encodedTriangle = EncodedTriangles[meshlet.triangle_offset + tri];
+                        for (uint32_t i = 0; i < 3; i++)
+                        {
+                            uint32_t localIndex = (encodedTriangle >> (i * 8)) & 0xff;
+                            uint32_t indirectIndex = meshlet.vertex_offset + localIndex;
+                            decodedIndexVector[currentIdx++] = IndicesIndirection[indirectIndex];
+                        }
+                    }
+                }
+            }
+        };
+
+    } // namespace Meshlets
+} // namespace AZ
+

+ 68 - 0
Gems/Meshlets/Code/Source/Meshlets/MeshletsDispatchItem.cpp

@@ -0,0 +1,68 @@
+/*
+ * 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 <MeshletsDispatchItem.h>
+
+#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
+#include <Atom/RPI.Public/Shader/Shader.h>
+
+#include <limits>
+
+#include "MeshletsAssets.h"
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        #define MESHLETS_THREAD_GROUP_SIZE 64
+
+        MeshletsDispatchItem::~MeshletsDispatchItem()
+        {
+        }
+
+        void MeshletsDispatchItem::InitDispatch(
+            RPI::Shader* shader,
+            Data::Instance<RPI::ShaderResourceGroup> meshletsDataSrg,
+            uint32_t meshletsAmount )
+        {
+            RHI::DispatchDirect dispatchArgs(
+                meshletsAmount, 1, 1,
+                MESHLETS_THREAD_GROUP_SIZE, 1, 1
+            );
+
+            m_dispatchItem.m_arguments = dispatchArgs;
+            m_dispatchItem.m_shaderResourceGroupCount = 1;      // Per pass srg can be added by the individual passes
+            m_dispatchItem.m_shaderResourceGroups =
+            {
+                meshletsDataSrg->GetRHIShaderResourceGroup()
+            };
+            m_meshletsDataSrg = meshletsDataSrg;    // can also be retrieve directly from the m_dispatchItem
+
+            m_shader = shader;
+            if (shader)
+            {
+                RHI::PipelineStateDescriptorForDispatch pipelineDesc;
+                m_shader->GetVariant(RPI::ShaderAsset::RootShaderVariantStableId).ConfigurePipelineState(pipelineDesc);
+                m_dispatchItem.m_pipelineState = m_shader->AcquirePipelineState(pipelineDesc);
+            }
+        }
+
+        void MeshletsDispatchItem::SetPipelineState(RPI::Shader* shader)
+        {
+            m_shader = shader;
+            if (shader)
+            {
+                RHI::PipelineStateDescriptorForDispatch pipelineDesc;
+                m_shader->GetVariant(RPI::ShaderAsset::RootShaderVariantStableId).ConfigurePipelineState(pipelineDesc);
+                m_dispatchItem.m_pipelineState = m_shader->AcquirePipelineState(pipelineDesc);
+            }
+        }
+
+
+    } // namespace Meshlets
+} // namespace AZ

+ 57 - 0
Gems/Meshlets/Code/Source/Meshlets/MeshletsDispatchItem.h

@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AtomCore/Instance/InstanceData.h>
+
+#include <Atom/RHI/DispatchItem.h>
+
+namespace AZ
+{
+    namespace RHI
+    {
+        class PipelineState;
+    }
+
+    namespace RPI
+    {
+        class Shader;
+        class ShaderResourceGroup;
+    }
+
+    namespace Meshlets
+    {
+        class MeshletsDispatchItem
+            : public Data::InstanceData
+        {
+        public:
+            AZ_CLASS_ALLOCATOR(MeshletsDispatchItem, AZ::SystemAllocator, 0);
+
+            MeshletsDispatchItem() = default;
+
+            ~MeshletsDispatchItem();
+
+            void InitDispatch(
+                RPI::Shader* shader,
+                Data::Instance<RPI::ShaderResourceGroup> meshletsDataSrg,
+                uint32_t meshletsAmount // Amount of meshlets
+            );
+
+            void SetPipelineState(RPI::Shader* shader);
+
+            RHI::DispatchItem* GetDispatchItem() { return m_shader ? &m_dispatchItem : nullptr; }
+            Data::Instance<RPI::ShaderResourceGroup> GetMeshletDataSrg() { return m_meshletsDataSrg;  }
+
+        private:
+            RHI::DispatchItem m_dispatchItem;
+            Data::Instance<RPI::ShaderResourceGroup> m_meshletsDataSrg;
+            RPI::Shader* m_shader;
+        };
+    } // namespace Meshlets
+} // namespace AZ

+ 439 - 0
Gems/Meshlets/Code/Source/Meshlets/MeshletsFeatureProcessor.cpp

@@ -0,0 +1,439 @@
+/*
+* Modifications Copyright (c) Contributors to the Open 3D Engine Project. 
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+* 
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+#include <AzCore/std/parallel/thread.h>
+#include <AzCore/Serialization/SerializeContext.h>
+
+#include <AzCore/Math/MatrixUtils.h>
+#include <AzCore/Math/Matrix3x4.h>
+#include <AzCore/Math/Matrix4x4.h>
+#include <AzCore/Math/Transform.h>
+
+#include <Atom/RPI.Public/Scene.h>
+#include <Atom/RPI.Public/RenderPipeline.h>
+#include <Atom/RPI.Public/Pass/PassFilter.h>
+#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
+#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
+
+#include <Atom/Feature/RenderCommon.h>
+#include <Atom/Feature/Mesh/MeshFeatureProcessor.h>
+
+#include <MeshletsRenderObject.h>
+#include <MeshletsFeatureProcessor.h>
+
+#ifndef SAFE_DELETE
+    #define SAFE_DELETE(p){if(p){delete p;p=nullptr;}}
+#endif
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        MeshletsFeatureProcessor::MeshletsFeatureProcessor()
+        {
+            MeshletsComputePassName = Name("MeshletsComputePass");
+            MeshletsRenderPassName = Name("MeshletsRenderPass");
+            CreateResources();
+        }
+
+        MeshletsFeatureProcessor::~MeshletsFeatureProcessor()
+        {
+            CleanResources();
+        }
+
+        void MeshletsFeatureProcessor::CreateResources()
+        {
+            if (!Meshlets::SharedBufferInterface::Get())
+            {   // Since there can be several pipelines, allocate the shared buffer only for the
+                // first one and from that moment on it will be used through its interface
+                AZStd::string sharedBufferName = "MeshletsSharedBuffer";
+                uint32_t bufferSize = 256 * 1024 * 1024;
+
+                // Prepare Render Srg descriptors for calculating the required alignment for the shared buffer
+                MeshRenderData tempRenderData;
+                MeshletsRenderObject::PrepareRenderSrgDescriptors(tempRenderData, 1, 1);
+
+                m_sharedBuffer = AZStd::make_unique<Meshlets::SharedBuffer>(sharedBufferName, bufferSize, tempRenderData.RenderBuffersDescriptors);
+            }
+
+            m_renderObjectsMarkedForDeletion = m_meshletsRenderObjects;
+            m_renderObjectsMarkedForDeletion.clear();
+            DeletePendingMeshletsRenderObjects();
+        }
+
+        void MeshletsFeatureProcessor::CleanResources()
+        {
+            m_sharedBuffer.reset();
+        }
+
+        void MeshletsFeatureProcessor::CleanPasses()
+        {
+            m_computePass = nullptr;
+            m_renderPass = nullptr;
+        }
+
+        void MeshletsFeatureProcessor::Init([[maybe_unused]]RPI::RenderPipeline* pipeline)
+        {
+            InitComputePass(MeshletsComputePassName);
+            InitRenderPass(MeshletsRenderPassName);
+        }
+
+        void MeshletsFeatureProcessor::Reflect(ReflectContext* context)
+        {
+            if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
+            {
+                serializeContext
+                    ->Class<MeshletsFeatureProcessor, RPI::FeatureProcessor>()
+                    ->Version(0);
+            }
+        }
+
+        void MeshletsFeatureProcessor::Activate()
+        {
+            m_transformServiceFeatureProcessor = GetParentScene()->GetFeatureProcessor<Render::TransformServiceFeatureProcessor>();
+            AZ_Assert(m_transformServiceFeatureProcessor, "MeshFeatureProcessor requires a TransformServiceFeatureProcessor on its parent scene.");
+
+            EnableSceneNotification();
+            TickBus::Handler::BusConnect();
+        }
+
+        void MeshletsFeatureProcessor::Deactivate()
+        {
+            DisableSceneNotification();
+            TickBus::Handler::BusDisconnect();
+        }
+
+        int MeshletsFeatureProcessor::GetTickOrder()
+        {
+            return AZ::TICK_PRE_RENDER;
+        }
+
+        bool MeshletsFeatureProcessor::HasMeshletPasses(RPI::RenderPipeline* renderPipeline)
+        {
+            RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassName(MeshletsComputePassName, renderPipeline);
+            RPI::Ptr<RPI::Pass> desiredPass = RPI::PassSystemInterface::Get()->FindFirstPass(passFilter);
+            return desiredPass ? true : false;
+        }
+
+        bool MeshletsFeatureProcessor::InitComputePass(const Name& passName)
+        {
+            m_computePass = Data::Instance<MultiDispatchComputePass>();
+            RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassName(passName, m_renderPipeline);
+            RPI::Ptr<RPI::Pass> desiredPass = RPI::PassSystemInterface::Get()->FindFirstPass(passFilter);
+
+            if (desiredPass)
+            {
+                m_computePass = static_cast<MultiDispatchComputePass*>(desiredPass.get());
+                m_computeShader = m_computePass->GetShader();
+            }
+            else
+            {
+                AZ_Error("Meshlets", false,
+                    "%s does not exist in this pipeline. Check your game project's .pass assets.",
+                    passName.GetCStr());
+                return false;
+            }
+            return true;
+        }
+
+        bool MeshletsFeatureProcessor::InitRenderPass(const Name& passName)
+        {
+            m_renderPass = Data::Instance<MeshletsRenderPass>();
+            RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassName(passName, m_renderPipeline);
+            RPI::Ptr<RPI::Pass> desiredPass = RPI::PassSystemInterface::Get()->FindFirstPass(passFilter);
+
+            if (desiredPass)
+            {
+                m_renderPass = static_cast<MeshletsRenderPass*>(desiredPass.get());
+                m_renderShader = m_renderPass->GetShader();
+            }
+            else
+            {
+                AZ_Error("Meshlets", false,
+                    "%s does not exist in this pipeline. Check your game project's .pass assets.",
+                    passName.GetCStr());
+                return false;
+            }
+            return true;
+        }
+
+        // This hook to the pipeline is still not connected and requires testing.
+        // Current connection is by altering the two pipeline manually. Since the hook is
+        // not the same for both pipelines, special care should be taken (on MainPipeline
+        // it comes after OpaquePass while on the LowEndPipeline after MSAAResolvePass). It
+        // is possible to simplify the logic and only hook to the active pipeline.
+        bool MeshletsFeatureProcessor::AddMeshletsPassesToPipeline(RPI::RenderPipeline* renderPipeline)
+        {
+            const char* passRequestAssetFilePath = "Passes/MeshletsPassRequest.azasset";
+            m_passRequestAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath<AZ::RPI::AnyAsset>(
+                passRequestAssetFilePath, AZ::RPI::AssetUtils::TraceLevel::Warning);
+            const AZ::RPI::PassRequest* passRequest = nullptr;
+            if (m_passRequestAsset->IsReady())
+            {
+                passRequest = m_passRequestAsset->GetDataAs<AZ::RPI::PassRequest>();
+            }
+            if (!passRequest)
+            {
+                AZ_Error("Meshlets", false, "Failed to add meshlets pass. Can't load PassRequest from [%s]", passRequestAssetFilePath);
+                return false;
+            }
+
+            // Create the pass
+            RPI::Ptr<RPI::Pass> pass = RPI::PassSystemInterface::Get()->CreatePassFromRequest(passRequest);
+            if (!pass)
+            {
+                AZ_Error("Meshlets", false, "Failed creating meshlets pass from pass request for pipeline [%s]",
+                    renderPipeline->GetId().GetCStr());
+                return false;
+            }
+
+            // Add the pass to render pipeline
+            bool success = renderPipeline->AddPassAfter(pass, Name("OpaquePass"));
+
+            AZ_Error("Meshlets", success, "Meshlets pass injection to render pipeline [%s] failed",
+                renderPipeline->GetId().GetCStr());
+
+            return success;
+        }
+
+        void MeshletsFeatureProcessor::SetTransform(
+            const Render::TransformServiceFeatureProcessorInterface::ObjectId objectId, 
+            const AZ::Transform& transform)
+        {
+            m_transformServiceFeatureProcessor->SetTransformForId(objectId, transform);
+        }
+
+        //==============================================================================
+        // This method is called the first time that a render object is constructed and
+        // does not need to be called again.
+        // At each frame the MeshletsFeatureProcessor will call the AddDrawPackets per each
+        // visible (multi meshlet) mesh and add its draw packets to the view.
+        // The buffers for the render are passed and set via Srg and not as assembly buffers
+        // which means that the instance Id must be set (for matrices) and vertex Id must
+        // be used in the shader to address the buffers
+        // Notice that because the object Id is mapped 1:1 with the DrawPacket, it currently
+        // does not support instancing. For instancing support, a separate per Instance Srg
+        // per call is required and the DrawPacket as well as the dispatch should be move to
+        // become part of an object instance structure and not the render object (that is
+        // shared between instances)
+        bool MeshletsFeatureProcessor::BuildDrawPacket(
+            ModelLodDataArray& lodRenderDataArray,
+            Render::TransformServiceFeatureProcessorInterface::ObjectId objectId)
+        {
+            if (!m_renderPass)
+            {
+                return false;
+            }
+
+            for (uint32_t lod = 0; lod < lodRenderDataArray.size(); ++lod)
+            {
+                MeshRenderData* lodRenderData = lodRenderDataArray[lod];
+                if (!lodRenderData || !lodRenderData->RenderObjectSrg)
+                {
+                    AZ_Error("Meshlets", false,
+                        "Failed to build draw packet - missing LOD[%d] render data Render Srg.", lod);
+                    return false;
+                }
+
+                // ObjectId belongs to the instance and not the object - to be moved
+                lodRenderData->ObjectId = objectId;
+
+                RHI::DrawPacketBuilder::DrawRequest drawRequest;
+                m_renderPass->FillDrawRequestData(drawRequest);
+                drawRequest.m_stencilRef = 0;
+                drawRequest.m_sortKey = 0;
+// Leave the following empty if using buffers rather than vertex streams.
+//                drawRequest.m_streamBufferViews = lodRenderData->m_renderStreamBuffersViews; 
+
+                RHI::DrawPacketBuilder drawPacketBuilder;
+                RHI::DrawIndexed drawIndexed;
+
+                drawIndexed.m_indexCount = lodRenderData->IndexCount;
+                drawIndexed.m_indexOffset = 0;
+                drawIndexed.m_vertexOffset = 0;
+
+                drawPacketBuilder.Begin(nullptr);
+                drawPacketBuilder.SetDrawArguments(drawIndexed);
+                drawPacketBuilder.SetIndexBufferView(lodRenderData->IndexBufferView);
+
+                // Add the object Id to the Srg - once instancing is supported, the ObjectId and the
+                // render Srg should be per instance / draw and not per object.
+                RHI::ShaderInputConstantIndex indexConstHandle = lodRenderData->RenderObjectSrg->FindShaderInputConstantIndex(Name("m_objectId"));
+                if (!lodRenderData->RenderObjectSrg->SetConstant(indexConstHandle, objectId.GetIndex()))
+                {
+                    AZ_Error("Meshlets", false, "Failed to bind Render Constant [m_ObjectId]");
+                }
+                lodRenderData->RenderObjectSrg->Compile();
+
+                // Add the per object render Srg - buffers required for the geometry render
+                drawPacketBuilder.AddShaderResourceGroup(lodRenderData->RenderObjectSrg->GetRHIShaderResourceGroup());
+
+                drawPacketBuilder.AddDrawItem(drawRequest);
+
+                // Change the following line in order to support instancing.
+                // For instancing the data cannot be associated 1:1 with the object
+                lodRenderData->MeshDrawPacket = drawPacketBuilder.End();
+
+                if (!lodRenderData->MeshDrawPacket)
+                {
+                    AZ_Error("Meshlets", false, "Failed to build the Meshlet DrawPacket.");
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        // Notice that currently this does not support object instancing.  Each object is assumed to
+        // have a single ObjectId and PerObject srg.
+        // To enhance this, create an ObjectInstanceData and per instance srg rather than per object
+        // and create a new instance every time this method is invoked.
+        // This will also require to split the srg from the srg with the meshlets buffers.
+        Render::TransformServiceFeatureProcessorInterface::ObjectId
+            MeshletsFeatureProcessor::AddMeshletsRenderObject(MeshletsRenderObject* meshletsRenderObject)
+        {
+            Render::TransformServiceFeatureProcessorInterface::ObjectId objectId = m_transformServiceFeatureProcessor->ReserveObjectId();
+
+            if (m_renderPass)
+            {
+                BuildDrawPacket(meshletsRenderObject->GetMeshletsRenderData(), objectId);
+            }
+
+            m_meshletsRenderObjects.push_back(meshletsRenderObject);
+
+            AZ_Error("Meshlets", m_renderPass, "Meshlets object did not build DrawItem due to missing render pass");
+
+            return objectId;
+        }
+
+        void MeshletsFeatureProcessor::DeletePendingMeshletsRenderObjects()
+        {
+            if (m_renderObjectsMarkedForDeletion.empty())
+            {
+                return;
+            }
+
+            for (auto renderObject : m_renderObjectsMarkedForDeletion)
+            {
+                ModelLodDataArray& modelLodArray = renderObject->GetMeshletsRenderData(0);
+                for (auto& renderData : modelLodArray)
+                {
+                    if (m_transformServiceFeatureProcessor && renderData)
+                    {
+                        m_transformServiceFeatureProcessor->ReleaseObjectId(renderData->ObjectId);
+                        break;  //same instance / object Id
+                    }
+                }
+                m_meshletsRenderObjects.remove(renderObject);
+                SAFE_DELETE(renderObject);
+            }
+            m_renderObjectsMarkedForDeletion.clear();
+        }
+
+        void MeshletsFeatureProcessor::RemoveMeshletsRenderObject(MeshletsRenderObject* meshletsRenderObject)
+        {
+            m_renderObjectsMarkedForDeletion.emplace_back(meshletsRenderObject);
+        }
+
+        void MeshletsFeatureProcessor::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
+        {
+            // OnTick can be used instead of the ::Simulate since it is set to be before the render
+        }
+
+        void MeshletsFeatureProcessor::Simulate(const RPI::FeatureProcessor::SimulatePacket& packet)
+        {
+            AZ_PROFILE_FUNCTION(AzRender);
+
+            AZ_UNUSED(packet);
+        }
+
+        void MeshletsFeatureProcessor::Render([[maybe_unused]] const RPI::FeatureProcessor::RenderPacket& packet)
+        {
+            AZ_PROFILE_FUNCTION(AzRender);
+
+            // Skip adding draw or dispatch items if there it no hair render objects
+            if (m_meshletsRenderObjects.size() == 0)
+            {
+                return;
+            }
+
+            // Remove any dangling leftovers 
+            DeletePendingMeshletsRenderObjects();
+
+            AZStd::list<RHI::DispatchItem*> dispatchItems;
+            AZStd::list<const RHI::DrawPacket*> drawPackets;
+            for (auto renderObject : m_meshletsRenderObjects)
+            {
+                // For demo purposed the model lod index is set for 0.
+                // This entire control scheme will be removed to be replaced with GPU
+                // driven pipeline control.
+                ModelLodDataArray& modelLodArray = renderObject->GetMeshletsRenderData(0);
+
+                for (auto& renderData : modelLodArray)
+                {
+                    if (renderData)
+                    {
+                        // the following is for testing only
+                        Render::TransformServiceFeatureProcessorInterface::ObjectId objectId = renderData->ObjectId;
+                        [[maybe_unused]] Transform transform = m_transformServiceFeatureProcessor->GetTransformForId(objectId);
+
+                        if (MeshletsRenderObject::CreateAndBindComputeSrgAndDispatch(m_computeShader, *renderData))
+                        {
+                            dispatchItems.push_back(renderData->MeshDispatchItem.GetDispatchItem());
+                        }
+                        drawPackets.push_back(renderData->MeshDrawPacket);
+                    }
+                    AZ_Error("Meshlets", renderData, "Render data is NULL");
+                }
+            }
+            m_computePass->AddDispatchItems(dispatchItems);
+            m_renderPass->AddDrawPackets(drawPackets);
+        }
+
+        void MeshletsFeatureProcessor::OnRenderPipelinePassesChanged([[maybe_unused]] RPI::RenderPipeline* renderPipeline)
+        {
+            if (!HasMeshletPasses(renderPipeline))
+            {   // This pipeline is not relevant - exist without changing anything.
+                return;
+            }
+
+            m_renderPipeline = renderPipeline;
+            CreateResources();
+
+            // This should be used in the future instead of changing the actual pipeline file
+//            AddMeshletsPassesToPipeline(m_renderPipeline);
+            Init(m_renderPipeline);
+        }
+
+        void MeshletsFeatureProcessor::OnRenderPipelineAdded([[maybe_unused]] RPI::RenderPipelinePtr renderPipeline)
+        {
+            if (!HasMeshletPasses(renderPipeline.get()))
+            {   // This pipeline is not relevant - exist without changing anything.
+                return;
+            }
+
+            m_renderPipeline = renderPipeline.get();
+            CreateResources();
+
+            // This should be used in the future instead of changing the actual pipeline file
+//            AddMeshletsPassesToPipeline(m_renderPipeline);
+            Init(m_renderPipeline);
+        }
+
+        void MeshletsFeatureProcessor::OnRenderPipelineRemoved([[maybe_unused]] RPI::RenderPipeline* renderPipeline)
+        {
+            if (!HasMeshletPasses(renderPipeline))
+            {   // This pipeline is not relevant - exist without changing anything.
+                return;
+            }
+
+            m_renderPipeline = nullptr;
+        }
+
+    } // namespace AtomSceneStream
+} // namespace AZ

+ 135 - 0
Gems/Meshlets/Code/Source/Meshlets/MeshletsFeatureProcessor.h

@@ -0,0 +1,135 @@
+/*
+* Modifications Copyright (c) Contributors to the Open 3D Engine Project. 
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+* 
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+#pragma once
+
+#include <AzCore/base.h>
+#include <AzCore/std/containers/unordered_map.h>
+#include <AzCore/std/containers/list.h>
+#include <AzCore/std/parallel/thread.h>
+
+#include <AzCore/Component/TickBus.h>
+
+#include <AtomCore/Instance/Instance.h>
+#include <Atom/RPI.Public/FeatureProcessor.h>
+
+#include <Atom/Feature/TransformService/TransformServiceFeatureProcessor.h>
+
+#include <SharedBuffer.h>
+#include <MultiDispatchComputePass.h>
+#include <MeshletsRenderPass.h>
+
+namespace RPI
+{
+    class RenderPipeline;
+}
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        class MeshletsRenderObject;
+
+
+        // Not used yet - should be developed to support instancing
+        struct RenderObjectInstanceData
+        {
+            Render::TransformServiceFeatureProcessorInterface::ObjectId m_objectId;
+            MeshletsRenderObject* meshletsRenderObject;
+        };
+
+        class MeshletsFeatureProcessor final
+            : public RPI::FeatureProcessor
+            , private AZ::TickBus::Handler
+        {
+            Name MeshletsComputePassName;
+            Name MeshletsRenderPassName;
+
+        public:
+            AZ_RTTI(MeshletsFeatureProcessor, "{1D93DE27-2DC4-4E9B-90B3-DCDCB941C920}", RPI::FeatureProcessor);
+
+            static void Reflect(AZ::ReflectContext* context);
+
+            MeshletsFeatureProcessor();
+            virtual ~MeshletsFeatureProcessor();
+
+            void Init(RPI::RenderPipeline* pipeline);
+
+            // FeatureProcessor overrides ...
+            void Activate() override;
+            void Deactivate() override;
+            void Simulate(const FeatureProcessor::SimulatePacket& packet) override;
+            void Render(const FeatureProcessor::RenderPacket& packet) override;
+
+            bool InitComputePass(const Name& passName);
+            bool InitRenderPass(const Name& passName);
+
+            // AZ::TickBus::Handler overrides
+            void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
+            int GetTickOrder() override;
+
+            // RPI::SceneNotificationBus overrides ...
+            void OnRenderPipelineAdded(RPI::RenderPipelinePtr renderPipeline) override;
+            void OnRenderPipelineRemoved(RPI::RenderPipeline* renderPipeline) override;
+            void OnRenderPipelinePassesChanged(RPI::RenderPipeline* renderPipeline) override;
+
+            void SetTransform(
+                const Render::TransformServiceFeatureProcessorInterface::ObjectId objectId,
+                const AZ::Transform& transform);
+            Render::TransformServiceFeatureProcessorInterface::ObjectId AddMeshletsRenderObject(
+                MeshletsRenderObject* meshletsRenderObject);
+            void RemoveMeshletsRenderObject(MeshletsRenderObject* meshletsRenderObject);
+
+            Data::Instance<RPI::Shader> GetComputeShader() { return m_computeShader; }
+            Data::Instance<RPI::Shader> GetRenderShader() { return m_renderShader; }
+
+        protected:
+            bool BuildDrawPacket(
+                ModelLodDataArray& lodRenderDataArray,
+                Render::TransformServiceFeatureProcessorInterface::ObjectId objectId);
+
+            bool HasMeshletPasses(RPI::RenderPipeline* renderPipeline);
+            bool AddMeshletsPassesToPipeline(RPI::RenderPipeline* renderPipeline);
+
+            void CreateResources();
+            void CleanResources();
+            void CleanPasses();
+
+            void DeletePendingMeshletsRenderObjects();
+
+        private:
+            AZ_DISABLE_COPY_MOVE(MeshletsFeatureProcessor);
+
+            AZStd::unique_ptr<Meshlets::SharedBuffer> m_sharedBuffer;  // used for all meshlets geometry buffers.
+
+            // Cache the pass request data for creating a parent pass and associating it in the pipeline
+            AZ::Data::Asset<AZ::RPI::AnyAsset> m_passRequestAsset;
+
+            Data::Instance<MultiDispatchComputePass> m_computePass;
+            Data::Instance<MeshletsRenderPass> m_renderPass;
+
+            AZStd::list<MeshletsRenderObject*> m_meshletsRenderObjects;
+
+            //! The render pipeline is acquired and set when a pipeline is created or changed
+            //! and accordingly the passes and the feature processor are associated.
+            //! Notice that scene can contain several pipelines all using the same feature
+            //! processor.  On the pass side, it will acquire the scene and request the FP, 
+            //! but on the FP side, it will only associate to the latest pass hence such a case
+            //! might still be a problem.  If needed, it can be resolved using a map for each
+            //! pass name per pipeline.
+            RPI::RenderPipeline* m_renderPipeline = nullptr;
+
+            Render::TransformServiceFeatureProcessor* m_transformServiceFeatureProcessor = nullptr;
+
+            AZStd::list<MeshletsRenderObject*> m_renderObjectsMarkedForDeletion;
+
+            Data::Instance<RPI::Shader> m_renderShader;
+            Data::Instance<RPI::Shader> m_computeShader;
+        };
+    } // namespace Meshlets
+} // namespace AZ

+ 800 - 0
Gems/Meshlets/Code/Source/Meshlets/MeshletsRenderObject.cpp

@@ -0,0 +1,800 @@
+/*
+* Modifications Copyright (c) Contributors to the Open 3D Engine Project. 
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+* 
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+#include <AzCore/Math/Aabb.h>
+
+#include <Atom/RHI/Factory.h>
+
+#include <Atom/RPI.Public/Image/StreamingImage.h>
+#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
+
+#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
+#include <Atom/RPI.Public/MeshDrawPacket.h>
+
+#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
+#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
+#include <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
+#include <Atom/RPI.Reflect/ResourcePoolAssetCreator.h>
+#include <Atom/RPI.Reflect/Model/ModelAsset.h>
+#include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
+#include <Atom/RPI.Reflect/Model/ModelLodAssetCreator.h>
+
+#include <vector>
+
+#include <MeshletsFeatureProcessor.h>
+#include <MeshletsUtilities.h>
+#include <MeshletsRenderObject.h>
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        //======================================================================
+        //                        MeshletsRenderObject
+        //======================================================================
+        uint32_t MeshletsRenderObject::s_modelNumber = 0;
+
+        bool MeshletsRenderObject::SetShaders()
+        {
+            {
+                m_computeShader = m_featureProcessor->GetComputeShader();
+                if (!m_computeShader)
+                {
+                    AZ_Error("Meshlets", false, "Failed to get Compute shader");
+                    return false;
+                }
+
+                m_renderShader = m_featureProcessor->GetRenderShader();
+                if (!m_renderShader)
+                {
+                    AZ_Error("Meshlets", false, "Failed to get Render shader");
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        //! This method will generate the meshlets and store their data in 'm_meshletsData' 
+        uint32_t MeshletsRenderObject::CreateMeshlets(GeneratorMesh& mesh)
+        {
+            const size_t max_vertices = Meshlets::maxVerticesPerMeshlet;    // matching wave/warp groups size multiplier
+            const size_t max_triangles = Meshlets::maxTrianglesPerMeshlet;  // NVidia-recommended 126, rounded down to a multiple of 4
+            const float cone_weight = 0.5f;   // note: should be set to 0 unless cone culling is used at runtime!
+
+            //----------------------------------------------------------------
+            size_t max_meshlets = meshopt_buildMeshletsBound(mesh.indices.size(), max_vertices, max_triangles);
+
+/*
+            // NO scan seems to return more localized meshlets
+            m_meshletsData.meshlets.resize(meshopt_buildMeshlets(
+            &m_meshletsData.meshlets[0], &m_meshletsData.meshlet_vertices[0], &m_meshletsData.meshlet_triangles[0],
+            &mesh.indices[0], mesh.indices.size(),
+            &mesh.vertices[0].px, mesh.vertices.size(), sizeof(GeneratorVertex),
+            max_vertices, max_triangles, cone_weight));
+            
+            if (!m_meshletsData.meshlets.size())
+            {
+                printf("Error generating meshlets - no meshlets were built\n");
+                return 0;
+            }
+
+            // this is an example of how to trim the vertex/triangle arrays when copying data out to GPU storage
+            const meshopt_Meshlet& last = m_meshletsData.meshlets.back();
+            m_meshletsData.meshlet_vertices.resize(last.vertex_offset + last.vertex_count);
+            m_meshletsData.meshlet_triangles.resize(last.triangle_offset + ((last.triangle_count * 3 + 3) & ~3));
+            uint32_t meshletsCount = (uint32_t) m_meshletsData.meshlets.size();
+            //----------------------------------------------------------------
+*/
+
+            ////////////////////////////
+            std::vector<meshopt_Meshlet> meshlets(max_meshlets);
+            std::vector<unsigned int> meshlet_vertices(max_meshlets * max_vertices);        // Vertex Index indirection map
+            std::vector<unsigned char> meshlet_triangles(max_meshlets * max_triangles * 3); // Meshlet triangles into the vertex index indirection - local to meshlet.
+
+            // NO scan seems to return more localized meshlets
+            meshlets.resize(meshopt_buildMeshlets(
+                &meshlets[0], &meshlet_vertices[0], &meshlet_triangles[0], &mesh.indices[0], mesh.indices.size(),
+                &mesh.vertices[0].px, mesh.vertices.size(), sizeof(GeneratorVertex), max_vertices, max_triangles, cone_weight));
+
+            // this is an example of how to trim the vertex/triangle arrays when copying data out to GPU storage
+            const meshopt_Meshlet& last = meshlets.back();
+            meshlet_vertices.resize(last.vertex_offset + last.vertex_count);
+            meshlet_triangles.resize(last.triangle_offset + ((last.triangle_count * 3 + 3) & ~3));
+            uint32_t meshletsCount = (uint32_t) meshlets.size();
+
+            m_meshletsData.Descriptors = meshlets;
+            m_meshletsData.IndicesIndirection = meshlet_vertices;
+            m_meshletsData.EncodeTrianglesData(meshlet_triangles);
+
+            // Some validation
+            m_meshletsData.ValidateData((uint32_t)meshlet_vertices.size());
+            std::vector<uint32_t> decodedIndices(meshlet_triangles.size());
+            m_meshletsData.GenerateDecodedIndices(decodedIndices);
+            ////////////////////////////
+
+            AZ_Warning("Meshlets", false, "Successfully generated [%d] meshlets\n", meshletsCount);
+            return meshletsCount;
+        }
+
+        uint32_t MeshletsRenderObject::CreateMeshlets(
+            float* positions, float* normals, float* texCoords, uint32_t vtxNum,
+            uint16_t* indices, uint32_t idxNum, RHI::Format IndexStreamFormat
+        )
+        {
+            GeneratorMesh mesh;
+
+            // Filling the mesh data for the meshlet library
+            mesh.vertices.resize(vtxNum);
+            for (uint32_t vtx = 0, posIdx = 0, uvIdx = 0; vtx < vtxNum; ++vtx, posIdx += 3, uvIdx += 2)
+            {
+                GeneratorVertex& genVtx = mesh.vertices[vtx];
+                genVtx.px = positions[posIdx];
+                genVtx.py = positions[posIdx + 1];
+                genVtx.pz = positions[posIdx + 2];
+                genVtx.nx = normals[posIdx];
+                genVtx.ny = normals[posIdx + 1];
+                genVtx.nz = normals[posIdx + 2];
+                genVtx.tx = texCoords[uvIdx];
+                genVtx.ty = texCoords[uvIdx + 1];
+            }
+
+            mesh.indices.resize(idxNum);
+            if (IndexStreamFormat == RHI::Format::R16_UINT)
+            {   // 16 bits index format 0..64K vertices
+                for (uint32_t idx = 0; idx < idxNum; ++idx)
+                {
+                    mesh.indices[idx] = (unsigned int)indices[idx];
+                }
+            }
+            else
+            {   // simple memcpy since elements are of 4 bytes size
+                memcpy(mesh.indices.data(), indices, idxNum * 4);
+            }
+
+            static bool createMeshlets = true;
+            uint32_t meshletsCount = 0;
+            if (createMeshlets)
+            {
+                meshletsCount = CreateMeshlets(mesh);
+            }
+
+            return meshletsCount;
+        }
+
+        bool MeshletsRenderObject::ProcessBuffersData(float* position, uint32_t vtxNum)
+        {
+            uint32_t badVertices = 0;
+            const float maxVertexSizeSqr = 99.9f * 99.9f;  // under 100 meters
+            for (uint32_t vtx = 0; vtx < vtxNum; ++vtx, position += 3)
+            {
+                Vector3 positionV3 = Vector3(position[0], position[1], position[2]);
+
+                float lengthSq = positionV3.GetLengthSq();
+                if (lengthSq < maxVertexSizeSqr)
+                {
+                    m_aabb.AddPoint(positionV3);
+                }
+                else
+                {
+                    ++badVertices;
+                    AZ_Warning("Meshlets", false, "Warning -- vertex [%d:%d] out of bound (%.2f, %.2f, %.2f) in model [%s]",
+                        vtx, vtxNum, positionV3.GetX(), positionV3.GetY(), positionV3.GetZ(), m_name.c_str());
+                }
+            }
+
+            AZ_Error("Meshlets", (badVertices == 0),
+                "[%d] Bad Vertices in Model [%s]", badVertices, m_name.c_str());
+            AZ_Error("Meshlets", m_aabb.IsValid(), "Error --- Model [%s] AABB is invalid - all [%d] vertices are corrupted",
+                m_name.c_str(), vtxNum);
+            return m_aabb.IsValid() ? true : false;
+        }
+
+        void MeshletsRenderObject::PrepareComputeSrgDescriptors(
+            MeshRenderData &meshRenderData, 
+            uint32_t vertexCount, uint32_t indexCount)
+        {
+            if (meshRenderData.ComputeBuffersDescriptors.size())
+            {
+                return;
+            }
+
+            meshRenderData.ComputeBuffersDescriptors.resize(uint8_t(ComputeStreamsSemantics::NumBufferStreams));
+
+            // Allocated using regular buffers
+            meshRenderData.ComputeBuffersDescriptors[uint8_t(ComputeStreamsSemantics::MeshletsData)] =
+                SrgBufferDescriptor(
+                    RPI::CommonBufferPoolType::ReadOnly,
+                    RHI::Format::Unknown,   // Mark is as Unknown since it represents StructuredBuffer
+//                    RHI::BufferBindFlags::Indirect |  [To Do] - add this when moving to GPU driven render pipeline
+                    RHI::BufferBindFlags::ShaderRead,
+                    sizeof(MeshletDescriptor), (uint32_t)m_meshletsData.Descriptors.size(), 
+                    Name{ "MESHLETS" }, Name{ "m_meshletsDescriptors" }, 0, 0,
+                    (uint8_t*)m_meshletsData.Descriptors.data()
+                );
+
+            meshRenderData.ComputeBuffersDescriptors[uint8_t(ComputeStreamsSemantics::MehsletsTriangles)] =
+                SrgBufferDescriptor(
+                    RPI::CommonBufferPoolType::ReadOnly,
+                    RHI::Format::R32_UINT,
+//                    RHI::BufferBindFlags::Indirect |  [To Do] - add this when moving to GPU driven render pipeline
+                    RHI::BufferBindFlags::ShaderRead,
+                    sizeof(uint32_t), (uint32_t)m_meshletsData.EncodedTriangles.size(),
+                    Name{ "MESHLETS_TRIANGLES" }, Name{ "m_meshletsTriangles" }, 1, 0,
+                    (uint8_t*)m_meshletsData.EncodedTriangles.data()
+                );
+
+            meshRenderData.ComputeBuffersDescriptors[uint8_t(ComputeStreamsSemantics::MeshletsIndicesIndirection)] =
+                SrgBufferDescriptor(
+                    RPI::CommonBufferPoolType::ReadOnly,
+                    RHI::Format::R32_UINT,
+//                    RHI::BufferBindFlags::Indirect |  [To Do] - add this when moving to GPU driven render pipeline
+                    RHI::BufferBindFlags::ShaderRead,
+                    sizeof(uint32_t), (uint32_t)m_meshletsData.IndicesIndirection.size(),
+                    Name{ "MESHLETS_LOOKUP" }, Name{ "m_meshletsIndicesLookup" }, 2, 0,
+                    (uint8_t*)m_meshletsData.IndicesIndirection.data()
+                );
+
+            // Allocated using view into shared buffer to allow for a barrier before the render pass
+            // [To Do] - including the InputAssembly flag will fail the validation.
+            // This requires change in Atom since the pool flags and the buffer flags are not
+            // properly correlated!
+            meshRenderData.ComputeBuffersDescriptors[uint8_t(ComputeStreamsSemantics::UVs)] =
+                SrgBufferDescriptor(
+                    RPI::CommonBufferPoolType::ReadWrite,
+                    RHI::Format::R32G32_FLOAT,
+//                    RHI::BufferBindFlags::Indirect |  [To Do] - add this when moving to GPU driven render pipeline
+                    RHI::BufferBindFlags::ShaderReadWrite, // | RHI::BufferBindFlags::InputAssembly,
+                    sizeof(float) * 2, vertexCount,
+                    Name{ "UV" }, Name{ "m_uvs" }, 3, 0
+                );
+
+            meshRenderData.ComputeBuffersDescriptors[uint8_t(ComputeStreamsSemantics::Indices)] =
+                SrgBufferDescriptor(
+                    RPI::CommonBufferPoolType::ReadWrite,
+                    RHI::Format::R32_UINT,
+//                    RHI::BufferBindFlags::Indirect |  [To Do] - add this when moving to GPU driven render pipeline
+                    RHI::BufferBindFlags::ShaderReadWrite, // | RHI::BufferBindFlags::InputAssembly,
+                    sizeof(uint32_t), indexCount,
+                    Name{ "INDICES" }, Name{ "m_indices" }, 4, 0
+                );
+        }
+
+        void MeshletsRenderObject::PrepareRenderSrgDescriptors(
+            MeshRenderData &meshRenderData, uint32_t vertexCount, uint32_t indicesCount)
+        {
+            if (meshRenderData.RenderBuffersDescriptors.size())
+            {
+                return;
+            }
+
+            meshRenderData.RenderBuffersDescriptors.resize(uint8_t(RenderStreamsSemantics::NumBufferStreams));
+
+            meshRenderData.RenderBuffersDescriptors[uint8_t(RenderStreamsSemantics::Positions)] =
+                SrgBufferDescriptor(
+//                    RPI::CommonBufferPoolType::StaticInputAssembly,
+                    RPI::CommonBufferPoolType::ReadOnly,
+                    RHI::Format::R32_FLOAT,
+//                    RHI::Format::R32G32B32_FLOAT,
+//                    RHI::BufferBindFlags::Indirect |  [To Do] - add this when moving to GPU driven render pipeline
+//                    RHI::BufferBindFlags::InputAssembly, 
+                    RHI::BufferBindFlags::ShaderRead,
+                    sizeof(float),
+                    3 * vertexCount,    // The amount of elements   
+                    Name{ "POSITION" }, Name{ "m_positions" }, 0, 0
+                );
+
+            // The following should be unknown structure type to represent StructuredBuffer.
+            // This is done in order to avoid misalignment due to elements that are not
+            // 16 bytes aligned.
+            meshRenderData.RenderBuffersDescriptors[uint8_t(RenderStreamsSemantics::Normals)] =
+                SrgBufferDescriptor(
+//                    RPI::CommonBufferPoolType::StaticInputAssembly,
+                    RPI::CommonBufferPoolType::ReadOnly,
+                    RHI::Format::R32_FLOAT,
+//                    RHI::Format::R32G32B32_FLOAT,
+//                    RHI::BufferBindFlags::Indirect |  [To Do] - add this when moving to GPU driven render pipeline
+//                    RHI::BufferBindFlags::InputAssembly, 
+                    RHI::BufferBindFlags::ShaderRead,
+                    sizeof(float),
+                    3 * vertexCount,    // The amount of elements   
+                    Name{ "NORMAL" }, Name{ "m_normals" }, 1, 0
+                );
+
+            meshRenderData.RenderBuffersDescriptors[uint8_t(RenderStreamsSemantics::Tangents)] =
+                SrgBufferDescriptor(
+//                    RPI::CommonBufferPoolType::StaticInputAssembly,
+                    RPI::CommonBufferPoolType::ReadOnly,
+                    RHI::Format::R32G32B32A32_FLOAT,
+//                    RHI::BufferBindFlags::Indirect |  [To Do] - add this when moving to GPU driven render pipeline
+//                    RHI::BufferBindFlags::InputAssembly, 
+                    RHI::BufferBindFlags::ShaderRead,
+                    sizeof(float) * 4, vertexCount,
+                    Name{ "TANGENT" }, Name{ "m_tangents" }, 2, 0
+                );
+
+            meshRenderData.RenderBuffersDescriptors[uint8_t(RenderStreamsSemantics::BiTangents)] =
+                SrgBufferDescriptor(
+                    RPI::CommonBufferPoolType::ReadOnly,
+                    RHI::Format::R32_FLOAT,
+//                    RHI::Format::R32G32B32_FLOAT,
+//                    RHI::BufferBindFlags::Indirect |  [To Do] - add this when moving to GPU driven render pipeline
+//                    RHI::BufferBindFlags::InputAssembly, 
+                    RHI::BufferBindFlags::ShaderRead,
+                    sizeof(float),
+                    3 * vertexCount,    // The amount of elements   
+                    Name{ "BITANGENT" }, Name{ "m_bitangents" }, 3, 0
+                );
+
+            // For now created as ReadWrite shared buffer - should be ReadOnly in the final product
+            meshRenderData.RenderBuffersDescriptors[uint8_t(RenderStreamsSemantics::UVs)] =
+                SrgBufferDescriptor(
+                    RPI::CommonBufferPoolType::ReadOnly,
+                    RHI::Format::R32G32_FLOAT,
+//                    RHI::BufferBindFlags::Indirect |  [To Do] - add this when moving to GPU driven render pipeline
+                    RHI::BufferBindFlags::ShaderReadWrite | RHI::BufferBindFlags::InputAssembly,
+                    sizeof(float) * 2, vertexCount,
+                    Name{ "UV" }, Name{ "m_uvs" }, 4, 0
+                );
+
+            // Notice that several of these buffers were already created and so the pool type
+            // doesn't really matter since it won't be used.
+            meshRenderData.RenderBuffersDescriptors[uint8_t(RenderStreamsSemantics::Indices)] =
+                SrgBufferDescriptor(
+                    RPI::CommonBufferPoolType::StaticInputAssembly,  // Not used (by the pool), created using shared buffer
+                    RHI::Format::R32_UINT,
+//                    RHI::BufferBindFlags::Indirect |  [To Do] - add this when moving to GPU driven render pipeline
+                    RHI::BufferBindFlags::ShaderReadWrite | RHI::BufferBindFlags::InputAssembly,
+                    sizeof(uint32_t), indicesCount,
+                    Name{ "INDICES" }, Name{ "m_indices" }, 5, 0
+                );
+        }
+
+        // Notice that unlike the Compute buffers, for the render all the buffers are
+        // read only and because of this we can create and bind all in one stage.
+        bool MeshletsRenderObject::CreateAndBindRenderBuffers(MeshRenderData &meshRenderData)
+        {
+            // Create the Srg first - required for the buffers generation
+            if (m_renderShader)
+            {
+                meshRenderData.RenderObjectSrg = RPI::ShaderResourceGroup::Create(
+                        m_renderShader->GetAsset(), AZ::Name{ "MeshletsObjectRenderSrg" });
+            }
+            if (!meshRenderData.RenderObjectSrg)
+            {
+                AZ_Error("Meshlets", false, "Failed to create the Render Srg - Meshlets Mesh load will fail");
+                return false;
+            }
+
+            bool success = true;
+            uint32_t streamsNum = (uint32_t) meshRenderData.RenderBuffersDescriptors.size();
+            meshRenderData.RenderBuffersViews.resize(streamsNum);   // resize for a vector, no need for an array
+            meshRenderData.RenderBuffers.resize(streamsNum);
+
+            // Unlike the compute method - the render method actually creates all buffers including
+            // the shared indices and UVs buffers that are used by the Compute prior to rendering.
+            for (uint32_t stream = 0; stream < streamsNum ; ++stream)
+            {
+                SrgBufferDescriptor& bufferDesc = meshRenderData.RenderBuffersDescriptors[stream];
+
+                if ((stream == uint8_t(RenderStreamsSemantics::UVs)) ||
+                    (stream == uint8_t(RenderStreamsSemantics::Indices))) 
+                {   
+                    // Shared Buffer Views: allocate views from the shared buffer since all buffers will
+                    // share the same state and be shader read/write - in this case the buffers were created
+                    // by CreateComputeBuffers so we need to copy the data from there including the
+                    // descriptors that contains the proper offsets to the shared buffer.
+
+                    if (stream == uint8_t(RenderStreamsSemantics::UVs))
+                    {
+                        uint8_t mappedIdx = uint8_t(ComputeStreamsSemantics::UVs);
+                        bufferDesc.m_viewOffsetInBytes = meshRenderData.ComputeBuffersDescriptors[mappedIdx].m_viewOffsetInBytes;
+                        meshRenderData.RenderBuffersViews[stream] = meshRenderData.ComputeBuffersViews[mappedIdx];
+
+                        RHI::ShaderInputBufferIndex indexHandle = meshRenderData.RenderObjectSrg->FindShaderInputBufferIndex(bufferDesc.m_paramNameInSrg);
+                        bufferDesc.m_resourceShaderIndex = indexHandle.GetIndex();
+                        if (!meshRenderData.RenderObjectSrg->SetBufferView(indexHandle, meshRenderData.RenderBuffersViews[stream].get()))
+                        {
+                            AZ_Error("Meshlets", false, "Failed to bind render buffer view for %s", bufferDesc.m_bufferName.GetCStr());
+                            return false;
+                        }
+                    }
+                    if (stream == uint8_t(RenderStreamsSemantics::Indices))
+                    {
+                        uint8_t mappedIdx = uint8_t(ComputeStreamsSemantics::Indices);
+                        bufferDesc.m_viewOffsetInBytes = meshRenderData.ComputeBuffersDescriptors[mappedIdx].m_viewOffsetInBytes;
+
+                        meshRenderData.IndexBufferView = RHI::IndexBufferView(
+                            meshRenderData.ComputeBuffersViews[mappedIdx]->GetBuffer(),
+                            bufferDesc.m_viewOffsetInBytes,
+                            (uint64_t)bufferDesc.m_elementCount * bufferDesc.m_elementSize,
+                            (bufferDesc.m_elementFormat == RHI::Format::R32_UINT) ? RHI::IndexFormat::Uint32 : RHI::IndexFormat::Uint16);
+                    }
+                }
+                else
+                {   // Regular buffers creation - since they are read only, no need to use the shared buffer
+                    meshRenderData.RenderBuffersViews[stream] = Data::Instance<RHI::BufferView>();
+                    meshRenderData.RenderBuffers[stream] = UtilityClass::CreateBufferAndBindToSrg(
+                        "Meshlets", bufferDesc, meshRenderData.RenderObjectSrg);
+
+                    success &= (meshRenderData.RenderBuffers[stream] ? true : false);
+                }
+            }
+
+            // Copy the original mesh data into the buffers or delete the allocators if failed
+            if (success)
+            {
+                for (uint32_t stream = 0; stream < streamsNum && success ; ++stream)
+                {   // upload the original streams data - skip indices and UVs copy : Compute will set them
+                    if ((stream == uint8_t(RenderStreamsSemantics::UVs)) ||
+                        (stream == uint8_t(RenderStreamsSemantics::Indices)))
+                    {   // Update the data so that we can compare with the Compute output 
+//                        continue;
+                    }
+
+                    Data::Instance<RPI::Buffer> buffer = meshRenderData.RenderBuffers[stream] ?
+                        meshRenderData.RenderBuffers[stream] : 
+                        Meshlets::SharedBufferInterface::Get()->GetBuffer();
+
+                    SrgBufferDescriptor& bufferDesc = meshRenderData.RenderBuffersDescriptors[stream];
+                    size_t requiredSize = (uint64_t)bufferDesc.m_elementCount * bufferDesc.m_elementSize;
+                    bool upload = buffer->UpdateData( bufferDesc.m_bufferData, requiredSize, bufferDesc.m_viewOffsetInBytes);
+                    AZ_Error("Meshlets", upload, "Data could not be uploaded to Render buffer [%s]", bufferDesc.m_bufferName.GetCStr());
+                    success &= upload;
+                }
+            }
+
+            // Final stage - compile the Srg
+            // Make sure to compile the Srg - when setting all parameter including ObjectId.
+//            meshRenderData.RenderObjectSrg->Compile();
+
+            return success;
+        }
+
+        // For the Compute, since some of the buffers are RW the RHI will verify that
+        // they are attached to the frame scheduler (ValidateSetBufferView) and this
+        // might fail if the creation is not times correctly, hence they is a split
+        // between the creation and the binding to the Srg.
+        bool MeshletsRenderObject::CreateAndBindComputeSrgAndDispatch(
+            Data::Instance<RPI::Shader> computeShader, MeshRenderData& meshRenderData)
+        {
+            // Start with the Srg creation - it will be required for the buffers generation
+            if (meshRenderData.ComputeSrg)
+            {
+                return true;
+            }
+
+            meshRenderData.ComputeSrg =
+                RPI::ShaderResourceGroup::Create(computeShader->GetAsset(), AZ::Name{ "MeshletsDataSrg" });
+            if (!meshRenderData.ComputeSrg)
+            {
+                AZ_Error("Meshlets", false, "Failed to create the Compute Srg");
+                return false;
+            }
+
+            uint32_t streamsNum = (uint32_t)meshRenderData.ComputeBuffersDescriptors.size();
+            bool success = true;
+            for (uint32_t stream = 0; stream < streamsNum ; ++stream)
+            {
+                SrgBufferDescriptor& bufferDesc = meshRenderData.ComputeBuffersDescriptors[stream];
+
+                if ((stream == uint8_t(ComputeStreamsSemantics::UVs)) ||
+                    (stream == uint8_t(ComputeStreamsSemantics::Indices)))
+                {   // Shared Buffer Views: allocate views from the shared buffer since Index and UV buffers will
+                    // share the same state and be shader read/write.
+                    if (!meshRenderData.ComputeBuffersViews[stream])
+                    {
+                        AZ_Error("Meshlets", false, "Buffer view doesn't exist");
+                        success = false;
+                        continue;
+                    }
+
+                    success &= UtilityClass::BindBufferViewToSrg(
+                        "Meshlets", meshRenderData.ComputeBuffersViews[stream], bufferDesc,
+                        meshRenderData.ComputeSrg);
+
+                    // And now for the second method - using offsets within the shared buffer
+                    AZ::Name constantName = (stream == uint8_t(ComputeStreamsSemantics::UVs)) ?
+                        Name("m_texCoordsOffset") : Name("m_indicesOffset");
+                    RHI::ShaderInputConstantIndex constantHandle = meshRenderData.ComputeSrg->FindShaderInputConstantIndex(constantName);
+                    uint32_t offsetInUint = bufferDesc.m_viewOffsetInBytes / sizeof(uint32_t);
+                    if (!meshRenderData.ComputeSrg->SetConstant(constantHandle, offsetInUint))
+                    {
+                        AZ_Error("Meshlets", false, "Failed to bind Constant [%s]", constantName.GetCStr());
+                        return false;
+                    }
+                }
+                else
+                {   // Regular buffers: since these buffers are read only and will not be altered there is no need to
+                    // use the shared buffer. This also means that we bind using buffers instead of buffers views.
+                    if (!meshRenderData.ComputeBuffers[stream])
+                    {
+                        AZ_Error("Meshlets", false, "Buffer doesn't exist");
+                        success = false;
+                        continue;
+                    }
+
+                    success &= UtilityClass::BindBufferToSrg(
+                        "Meshlets", meshRenderData.ComputeBuffers[stream], bufferDesc,
+                        meshRenderData.ComputeSrg);
+                }
+            }
+
+            if (success)
+            {
+                // Compile the Srg and create the dispatch 
+                meshRenderData.ComputeSrg->Compile();
+
+                meshRenderData.MeshDispatchItem.InitDispatch(
+                    computeShader.get(), meshRenderData.ComputeSrg, meshRenderData.MeshletsCount);
+            }
+
+            return success;
+        }
+
+        bool MeshletsRenderObject::CreateComputeBuffers(MeshRenderData &meshRenderData)
+        {
+            bool success = true;
+            uint32_t streamsNum = (uint32_t)meshRenderData.ComputeBuffersDescriptors.size();
+            meshRenderData.ComputeBuffersAllocators.resize(streamsNum);
+            meshRenderData.ComputeBuffersViews.resize(streamsNum);
+            meshRenderData.ComputeBuffers.resize(streamsNum);
+
+            for (uint32_t stream = 0; stream < streamsNum ; ++stream)
+            {
+                SrgBufferDescriptor& bufferDesc = meshRenderData.ComputeBuffersDescriptors[stream];
+
+                if ((stream == uint8_t(ComputeStreamsSemantics::UVs)) ||
+                    (stream == uint8_t(ComputeStreamsSemantics::Indices)))
+                {   // Shared Buffer Views: allocate views from the shared buffer since Index and UV buffers will
+                    // share the same state and be shader read/write.
+                    meshRenderData.ComputeBuffersViews[stream] = UtilityClass::CreateSharedBufferView(
+                        "Meshlets", bufferDesc, meshRenderData.ComputeBuffersAllocators[stream]);
+                }
+                else
+                {   // Regular buffers: since these buffers are read only and will not be altered there is no need to
+                    // use the shared buffer. This also means that we bind using buffers instead of buffers views.
+                    meshRenderData.ComputeBuffersViews[stream] = Data::Instance<RHI::BufferView>();
+                    meshRenderData.ComputeBuffers[stream] = UtilityClass::CreateBuffer("Meshlets", bufferDesc);
+
+                    success &= (meshRenderData.ComputeBuffers[stream] ? true : false);
+                }
+            }
+
+            // Copy the original mesh data into the buffers or delete the allocators if failed
+            if (success)
+            {
+                for (uint32_t stream = 0; stream < streamsNum ; ++stream)
+                {   // upload the original streams data.
+                    // Avoid this for indices and UVs in order to test the compute stage output
+                    if ((stream == uint8_t(ComputeStreamsSemantics::UVs)) ||
+                        (stream == uint8_t(ComputeStreamsSemantics::Indices)))
+                    {
+                        continue;
+                    }
+
+                    SrgBufferDescriptor& bufferDesc = meshRenderData.ComputeBuffersDescriptors[stream];
+                    size_t requiredSize = (uint64_t)bufferDesc.m_elementCount * bufferDesc.m_elementSize;
+                    bool upload = meshRenderData.ComputeBuffers[stream]->UpdateData(bufferDesc.m_bufferData, requiredSize, bufferDesc.m_viewOffsetInBytes);
+                    AZ_Error("Meshlets", success == upload, "Data could not be uploaded to Compute buffer [%s]", bufferDesc.m_bufferName.GetCStr());
+                    success &= upload;
+                }
+            }
+
+            return success;
+        }
+
+        bool MeshletsRenderObject::RetrieveSourceMeshData(
+            const RPI::ModelLodAsset::Mesh& meshAsset,
+            MeshRenderData& meshRenderData, 
+            uint32_t vertexCount, uint32_t indexCount)
+        {
+            RHI::BufferViewDescriptor bufferDescriptor;
+            RHI::Format streamFormat;
+
+            // Take care of the indices since it's stored differently than the rest of the streams
+            SrgBufferDescriptor* descriptor = &meshRenderData.RenderBuffersDescriptors[uint8_t(RenderStreamsSemantics::Indices)];
+            const RPI::BufferAssetView* bufferAssetView = &meshAsset.GetIndexBufferAssetView();
+            descriptor->m_bufferData = RetrieveBufferData(bufferAssetView, streamFormat, 0, indexCount, bufferDescriptor);
+
+            bool success = (streamFormat == descriptor->m_elementFormat);
+            AZ_Error("Meshlets", success, "Error - index buffer with different format [%d]", streamFormat);
+
+            // Now take care of the rest of the streams
+            for (uint8_t stream = 0; stream < uint8_t(RenderStreamsSemantics::NumBufferStreams); ++stream)
+            {
+                if (stream == uint8_t(RenderStreamsSemantics::Indices))
+                {
+                    continue;
+                }
+
+                descriptor = &meshRenderData.RenderBuffersDescriptors[stream];
+                bufferAssetView = meshAsset.GetSemanticBufferAssetView(descriptor->m_bufferName);
+                if (!bufferAssetView)
+                {
+                    AZ_Error("Meshlets", false,
+                        "Error - missing buffer stream [%s]", descriptor->m_bufferName.GetCStr());
+                    return false;
+                }
+                descriptor->m_bufferData = RetrieveBufferData(bufferAssetView, streamFormat, vertexCount, vertexCount, bufferDescriptor);
+
+                if (streamFormat != descriptor->m_elementFormat)
+                {
+                    AZ_Warning("Meshlets", false,
+                        "Error - buffer %s with different format [%d]", descriptor->m_bufferName.GetCStr(), streamFormat);
+//                    success = false;
+                }
+
+                if (!descriptor->m_bufferData)
+                {
+                    AZ_Error("Meshlets", false, "Failed to create meshlet model [%s] - buffer [%s] data could not be retrieved",
+                        descriptor->m_bufferName.GetCStr());
+                    return false;
+                }
+            }
+
+            // AABB generation - can also be used for vertices scaling / creating transform.
+            success &= ProcessBuffersData((float*)meshRenderData.RenderBuffersDescriptors[uint8_t(RenderStreamsSemantics::Positions)].m_bufferData, vertexCount);
+
+            return success;
+        }
+
+        //----------------------------------------------------------------------
+        // The following method populates the new meshlets model from the given meshLodAsset.
+        // Moving the creator outside calling method and have the new model contain several
+        // Lods did not work as it needs to be local to the block!
+        uint32_t MeshletsRenderObject::CreateMeshletsRenderObject(
+            const RPI::ModelLodAsset::Mesh& meshAsset,
+            MeshRenderData &meshRenderData)
+        {
+            uint32_t indexCount = meshAsset.GetIndexCount();
+            uint32_t vertexCount = meshAsset.GetVertexCount();
+
+            // Prepare the rendering descriptor required next
+            PrepareRenderSrgDescriptors(meshRenderData, vertexCount, indexCount);
+
+            if (!RetrieveSourceMeshData(meshAsset, meshRenderData, vertexCount, indexCount))
+            {
+                return 0;
+            }
+
+            // Now we start generating the meshlets data.
+            uint32_t meshletsCount = CreateMeshlets(
+                (float*)meshRenderData.RenderBuffersDescriptors[uint8_t(RenderStreamsSemantics::Positions)].m_bufferData,
+                (float*)meshRenderData.RenderBuffersDescriptors[uint8_t(RenderStreamsSemantics::Normals)].m_bufferData,
+                (float*)meshRenderData.RenderBuffersDescriptors[uint8_t(RenderStreamsSemantics::UVs)].m_bufferData,
+                vertexCount,
+                (uint16_t*) meshRenderData.RenderBuffersDescriptors[uint8_t(RenderStreamsSemantics::Indices)].m_bufferData,
+                indexCount,
+                meshRenderData.RenderBuffersDescriptors[uint8_t(RenderStreamsSemantics::Indices)].m_elementFormat
+                );
+
+            if (!meshletsCount)
+            {
+                AZ_Error("Meshlets", false, "Failed to create meshlet model [%s] - the meshlet creation process failed", m_name.c_str());
+                return 0;
+            }
+
+            meshRenderData.MeshletsCount = meshletsCount;
+            meshRenderData.IndexCount = indexCount;
+
+            //----------------------------------------------------------------
+            // Prepare the Compute buffers, views and Srg for the Compute pass.
+            PrepareComputeSrgDescriptors(meshRenderData, vertexCount, indexCount);
+            if (!CreateComputeBuffers(meshRenderData))
+            {
+                 return 0;
+            }
+
+            //----------------------------------------------------------------
+            // Create the render streams and bind the the render Srg for the Render pass.
+            if (!CreateAndBindRenderBuffers(meshRenderData))
+            {
+                return 0;
+            }
+
+            return meshletsCount;
+        }
+
+
+        //----------------------------------------------------------------------
+        // Model Traversal and Data Copy for Creation 
+        //----------------------------------------------------------------------
+        uint8_t* MeshletsRenderObject::RetrieveBufferData(
+            const RPI::BufferAssetView* bufferView,
+            RHI::Format& format,
+            uint32_t expectedAmount, uint32_t &existingAmount,
+            RHI::BufferViewDescriptor& bufferDesc
+        )  
+        {
+            const Data::Asset<RPI::BufferAsset>& bufferAsset = bufferView->GetBufferAsset();
+//            const RHI::BufferViewDescriptor& bufferDesc = bufferView->GetBufferViewDescriptor();
+            bufferDesc = bufferView->GetBufferViewDescriptor();
+            existingAmount = bufferDesc.m_elementCount;
+
+            if ((bufferDesc.m_elementOffset != 0) ||
+                ((existingAmount != expectedAmount) && (expectedAmount != 0)))
+            {
+                AZ_Error("Meshlets", false, "More than a single mesh, or non-matching elements count");
+                return nullptr;
+            }
+
+            format = bufferDesc.m_elementFormat;
+            AZStd::span<const uint8_t> bufferData = bufferAsset->GetBuffer();
+            return (uint8_t *) bufferData.data();
+        }
+
+        // [To Do] - currently we create only the first mesh of the first Lod to be able to
+        // get to a fully working POC.
+        // Enhancing this by doing a double pass, gathering all data and creating meshlets groups
+        // by Lod level should not be a problem but a design of meshlet model structure should be
+        // put in place before doing that.
+        uint32_t MeshletsRenderObject::CreateMeshletsFromModelAsset(Data::Asset<RPI::ModelAsset> sourceModelAsset)
+        {
+            uint32_t meshletsCount = 0;
+
+            m_modelRenderData.resize(sourceModelAsset->GetLodAssets().size());
+            uint32_t curLod = 0;
+            uint32_t lodAssetsAmount = uint32_t(sourceModelAsset->GetLodAssets().size());
+
+//            for (const Data::Asset<RPI::ModelLodAsset>& lodAsset : sourceModelAsset->GetLodAssets())
+            for (const Data::Asset<RPI::ModelLodAsset>& lodAsset = sourceModelAsset->GetLodAssets()[curLod] ;
+                curLod < lodAssetsAmount ; ++curLod )
+            {
+                ModelLodDataArray* lodRenderData = &m_modelRenderData[curLod];
+                uint32_t meshCount = uint32_t(lodAsset->GetMeshes().size());
+                lodRenderData->resize(meshCount);
+
+//                for (const RPI::ModelLodAsset::Mesh& meshAsset : lodAsset->GetMeshes())
+                for (uint32_t meshIdx=0 ; meshIdx<meshCount ; ++meshIdx)
+                {
+                    MeshRenderData* meshRenderData = new MeshRenderData;
+                    meshletsCount += CreateMeshletsRenderObject(lodAsset->GetMeshes()[meshIdx], *meshRenderData);
+
+                    (*lodRenderData)[meshIdx] = meshRenderData;
+                    // check for validity! better option is to allocate and pass by ptr.
+                    if (meshIdx == 0)
+                        break;   
+                }
+            }
+
+            AZ_Warning("Meshlets", false, "Meshlet model [%s] was created", m_name.c_str());
+            return meshletsCount;
+        }
+
+        MeshletsRenderObject::MeshletsRenderObject(Data::Asset<RPI::ModelAsset> sourceModelAsset,
+            MeshletsFeatureProcessor* meshletsFeatureProcessor)
+        {
+            MeshletsRenderObject::s_textureCoordinatesName = Name("m_texCoordsOffset");
+            MeshletsRenderObject::s_indicesName = Name("m_indicesOffset");
+
+            m_featureProcessor = meshletsFeatureProcessor;
+            SetShaders();
+            m_name = "Model_" + AZStd::to_string(s_modelNumber++);
+            m_aabb = Aabb::CreateNull();
+
+            if (!Meshlets::SharedBufferInterface::Get())
+            {
+                AZ_Error("Meshlets", false, "Shared buffer was NOT created - meshlets model will not be created.");
+                return;
+            }
+            m_meshletsCount = CreateMeshletsFromModelAsset(sourceModelAsset);
+        }
+
+        MeshletsRenderObject::~MeshletsRenderObject()
+        {
+            for (auto modelLodDataArray : m_modelRenderData)
+            {
+                for (auto lodData : modelLodDataArray)
+                {
+                    delete lodData;
+                }
+            }
+        }
+
+    } // namespace Meshlets
+} // namespace AZ

+ 192 - 0
Gems/Meshlets/Code/Source/Meshlets/MeshletsRenderObject.h

@@ -0,0 +1,192 @@
+/*
+* Modifications Copyright (c) Contributors to the Open 3D Engine Project. 
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+* 
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+#pragma once
+
+#include <AzCore/base.h>
+#include <AzCore/Name/Name.h>
+#include <AzCore/Asset/AssetCommon.h>
+#include <AzCore/std/containers/map.h>
+#include <AzCore/std/containers/array.h>
+
+#include <AtomCore/Instance/Instance.h>
+#include <AtomCore/Instance/InstanceData.h>
+
+#include <Atom/RHI/StreamBufferView.h>
+
+#include <Atom/RPI.Public/MeshDrawPacket.h>
+#include <Atom/RPI.Public/Model/Model.h>
+#include <Atom/RPI.Reflect/Model/ModelAsset.h>
+
+#include <Atom/Feature/TransformService/TransformServiceFeatureProcessor.h>
+
+#include "../../External/meshoptimizer.h"
+
+#include <SharedBuffer.h>
+#include <MeshletsDispatchItem.h>
+#include <MeshletsData.h>
+
+namespace AZ
+{
+    namespace RPI
+    {
+        class MeshDrawPacket;
+    }
+
+    namespace Meshlets
+    {
+        const uint32_t maxVerticesPerMeshlet = 64;     // matching wave/warp groups size multiplier
+//        const uint32_t maxTrianglesPerMeshlet = 124; // NVidia-recommended 126, rounded down to a multiple of 4
+        const uint32_t maxTrianglesPerMeshlet = 64;    // Set it to 64 per inspection of both GPU threads / generated data
+
+        class MeshletsFeatureProcessor;
+
+        // The following structure holds the per object data - currently with no support
+        // for instancing.
+        // To support instancing move the dispatch item, draw packet and object Id to
+        // a separate instance data structure.
+        // The data here should only represent the object render/compute data without
+        // having any instance data (matrices, Id, etc..)
+        struct MeshRenderData
+        {
+            Render::TransformServiceFeatureProcessorInterface::ObjectId ObjectId;   // should be per instance
+
+            uint32_t MeshletsCount = 0;
+
+            // Used by the direct Draw stage only - should be changed for indirect culled render.
+            uint32_t IndexCount = 0;  
+
+             //! Compute render data
+            Data::Instance<RPI::ShaderResourceGroup> ComputeSrg;          // Per object Compute data - can be shared across instances
+            AZStd::vector<SrgBufferDescriptor> ComputeBuffersDescriptors;
+            AZStd::vector<Data::Instance<RHI::BufferView>> ComputeBuffersViews;
+            AZStd::vector<Data::Instance<Meshlets::SharedBufferAllocation>> ComputeBuffersAllocators;
+            AZStd::vector <Data::Instance<RPI::Buffer>> ComputeBuffers;   // stand alone non shared buffers
+            MeshletsDispatchItem MeshDispatchItem;
+
+            //! Render pass data
+            Data::Instance<RPI::ShaderResourceGroup> RenderObjectSrg;     // Per object render data - includes instanceId and vertex buffers
+            AZStd::vector<SrgBufferDescriptor> RenderBuffersDescriptors;
+            RHI::IndexBufferView IndexBufferView;
+            AZStd::vector<Data::Instance<RHI::BufferView>> RenderBuffersViews; 
+            AZStd::vector <Data::Instance<RPI::Buffer>> RenderBuffers;    // stand alone non shared buffers
+
+            const RHI::DrawPacket* MeshDrawPacket = nullptr;    // Should be moved to the instance data structure
+        };
+        using ModelLodDataArray = AZStd::vector<MeshRenderData*>;    // MeshRenderData per mesh in the Lod
+
+        //! Currently assuming single model without Lods so that the handling of the
+        //! meshlet creation and handling of the array is easier. If several meshes or Lods
+        //! exist, they will be created as separate models and the last model's instance
+        //! will be kept in this class.
+        //! To enhance this, add inheritance to lower levels of the model / mesh.
+        //! MeshletsModel represents a combined model that can contain an array
+        //! of ModelLods.
+        //! Each one of the ModelLods contains a vector of meshes, representing possible multiple
+        //! element within the mesh.
+        class MeshletsRenderObject
+        {
+        public:
+            static uint32_t s_modelNumber;
+
+            Name s_textureCoordinatesName;
+            Name s_indicesName;
+
+            MeshletsRenderObject(Data::Asset<RPI::ModelAsset> sourceModelAsset, MeshletsFeatureProcessor* meshletsFeatureProcessor);
+            ~MeshletsRenderObject();
+
+            static  Data::Instance<RPI::ShaderResourceGroup> CreateShaderResourceGroup(
+                Data::Instance<RPI::Shader> shader,
+                const char* shaderResourceGroupId,
+                [[maybe_unused]] const char* moduleName
+            );
+
+            AZStd::string& GetName() { return m_name;  }
+
+            ModelLodDataArray& GetMeshletsRenderData(uint32_t lodIdx = 0)
+            {
+                AZ_Assert(m_modelRenderData.size(), "Meshlets - model does not contain any render data");
+                return m_modelRenderData[AZStd::max(lodIdx, (uint32_t)m_modelRenderData.size() - 1)];
+            }
+
+
+            // This method is binding the buffers to the Srg and is separated from
+            // the creation method to allow frame sync when the data is compiled
+            static bool CreateAndBindComputeSrgAndDispatch(Data::Instance<RPI::Shader> computeShader, MeshRenderData& meshRenderData);
+
+            uint32_t GetMeshletsCount() { return m_meshletsCount; }
+
+            // The prep of this data should be used to create the shared buffer alignment 
+            static void PrepareRenderSrgDescriptors(MeshRenderData &meshRenderData, uint32_t vertexCount, uint32_t indicesCount);
+
+        protected:
+            bool ProcessBuffersData(float* position, uint32_t vtxNum);
+
+            uint8_t* RetrieveBufferData(
+                const RPI::BufferAssetView* bufferView,
+                RHI::Format& format,
+                uint32_t expectedAmount, uint32_t& existingAmount,
+                RHI::BufferViewDescriptor& bufferDesc
+            );
+
+            bool RetrieveSourceMeshData(
+                const RPI::ModelLodAsset::Mesh& meshAsset,
+                MeshRenderData& meshRenderData,
+                uint32_t vertexCount, uint32_t indexCount);
+
+            uint32_t CreateMeshlets(GeneratorMesh& mesh);
+
+            uint32_t CreateMeshlets(
+                float* positions, float* normals, float* texCoords, uint32_t vtxNum,
+                uint16_t* indices, uint32_t idxNum, RHI::Format IndexStreamFormat
+            );
+
+            uint32_t CreateMeshletsFromModelAsset(Data::Asset<RPI::ModelAsset> sourceModelAsset);
+
+            uint32_t CreateMeshletsRenderObject(const RPI::ModelLodAsset::Mesh& meshAsset, MeshRenderData &meshRenderData);
+
+
+            bool BuildDrawPacket( RHI::DrawPacketBuilder::DrawRequest& drawRequest, MeshRenderData& meshRenderData);
+
+            bool CreateAndBindRenderBuffers(MeshRenderData &meshRenderData);
+
+            void PrepareComputeSrgDescriptors(MeshRenderData &meshRenderData, uint32_t vertexCount, uint32_t indexCount);
+
+            bool CreateComputeBuffers(MeshRenderData &meshRenderData);
+
+            bool SetShaders();
+
+        private:
+            MeshletsFeatureProcessor* m_featureProcessor = nullptr;
+            AZStd::string m_name;
+
+            Aabb m_aabb;    // Should be per Lod per mesh and not global
+
+            static AZStd::vector<SrgBufferDescriptor> m_srgBufferDescriptors;
+
+            // [To Do] - meshlets data should be a vector of meshlets data per lod per mesh
+            // This should be fairly easy to do once LOD are properly supported - set it
+            // in the MeshRenderData.
+            MeshletsData m_meshletsData;    // the actual mesh meshlets' data
+
+            uint32_t m_meshletsCount = 0;
+
+            //------------------------------------------------------------------
+            // Remarks:
+            // 1. Moving to indirect compute, all the buffer views will need to either
+            // become offsets passed as part of each mesh dispatch, or bindless resources.
+            // Having the first approach does not require bindless mechanism in place.  
+            //------------------------------------------------------------------
+            Data::Instance<RPI::Shader> m_renderShader;
+            Data::Instance<RPI::Shader> m_computeShader;
+
+            AZStd::vector<ModelLodDataArray> m_modelRenderData;         // Render data array of Lods.
+        };
+
+    } // namespace Meshlets
+} // namespace AZ

+ 281 - 0
Gems/Meshlets/Code/Source/Meshlets/MeshletsRenderPass.cpp

@@ -0,0 +1,281 @@
+/*
+ * 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/RHI/RHISystemInterface.h>
+#include <Atom/RHI/DrawPacketBuilder.h>
+#include <Atom/RHI/PipelineState.h>
+
+#include <Atom/RPI.Public/View.h>
+#include <Atom/RPI.Public/RPIUtils.h>
+#include <Atom/RPI.Public/RenderPipeline.h>
+#include <Atom/RPI.Public/RPISystemInterface.h>
+#include <Atom/RPI.Public/Pass/PassUtils.h>
+#include <Atom/RPI.Public/Scene.h>
+
+#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
+#include <Atom/RPI.Reflect/Pass/RasterPassData.h>
+#include <Atom/RPI.Reflect/Pass/PassTemplate.h>
+#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
+
+#include <MeshletsRenderPass.h>
+#include <MeshletsUtilities.h>
+#include <MeshletsFeatureProcessor.h>
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        // --- Creation & Initialization ---
+        RPI::Ptr<MeshletsRenderPass> MeshletsRenderPass::Create(const RPI::PassDescriptor& descriptor)
+        {
+            RPI::Ptr<MeshletsRenderPass> pass = aznew MeshletsRenderPass(descriptor);
+            return pass;
+        }
+
+        MeshletsRenderPass::MeshletsRenderPass(const RPI::PassDescriptor& descriptor)
+            : RasterPass(descriptor),
+            m_passDescriptor(descriptor)
+        {
+            // For inherited classes, override this method and set the proper path.
+            // Example: "Shaders/MeshletsDebugRenderShader.azshader"
+            SetShaderPath("Shaders/meshletsdebugrendershader.azshader");
+            LoadShader();
+        }
+
+        bool MeshletsRenderPass::AcquireFeatureProcessor()
+        {
+            if (m_featureProcessor)
+            {
+                return true;
+            }
+
+            RPI::Scene* scene = GetScene();
+            if (!scene)
+            {
+                return false;
+            }
+
+            m_featureProcessor = scene->GetFeatureProcessor<MeshletsFeatureProcessor>();
+            if (!m_featureProcessor)
+            {
+                AZ_Warning("Meshlets", false,
+                    "MeshletsRenderPass [%s] - Failed to retrieve Hair feature processor from the scene",
+                    GetName().GetCStr());
+                return false;
+            }
+            return true;
+        }
+
+        void MeshletsRenderPass::InitializeInternal()
+        {
+            if (GetScene())
+            {
+                RasterPass::InitializeInternal();
+            }
+        }
+
+
+        bool MeshletsRenderPass::LoadShader()
+        {
+            RPI::ShaderReloadNotificationBus::Handler::BusDisconnect();
+
+            const RPI::RasterPassData* passData = RPI::PassUtils::GetPassData<RPI::RasterPassData>(m_passDescriptor);
+
+            // If we successfully retrieved our custom data, use it to set the DrawListTag
+            if (!passData)
+            {
+                AZ_Error("Meshlets", false, "Missing pass raster data");
+                return false;
+            }
+
+            // Load Shader
+            const char* shaderFilePath = m_shaderPath.c_str();
+            Data::Asset<RPI::ShaderAsset> shaderAsset =
+                RPI::AssetUtils::LoadAssetByProductPath<RPI::ShaderAsset>(shaderFilePath, RPI::AssetUtils::TraceLevel::Error);
+
+            if (!shaderAsset.GetId().IsValid())
+            {
+                AZ_Error("Meshlets", false, "Invalid shader asset for shader '%s'!", shaderFilePath);
+                return false;
+            }
+
+            m_shader = RPI::Shader::FindOrCreate(shaderAsset);
+            if (!m_shader)
+            {
+                AZ_Error("Meshlets", false, "Pass failed to load shader '%s'!", shaderFilePath);
+                return false;
+            }
+
+            // Per Pass Srg
+            {
+                // Using 'PerPass' naming since currently RasterPass assumes that the pass Srg is always named 'PassSrg'
+                // [To Do] - RasterPass should use srg slot index and not name - currently this will
+                //  result in a crash in one of the Atom existing MSAA passes that requires further dive. 
+                // m_shaderResourceGroup = UtilityClass::CreateShaderResourceGroup(m_shader, "HairPerPassSrg", "Meshlets");
+                m_shaderResourceGroup = UtilityClass::CreateShaderResourceGroup(m_shader, "PassSrg", "Meshlets");
+                if (!m_shaderResourceGroup)
+                {
+                    AZ_Error("Meshlets", false, "Failed to create the per pass srg");
+                    return false;
+                }
+            }
+            RPI::ShaderReloadNotificationBus::Handler::BusConnect(shaderAsset.GetId());
+
+            return true;
+        }
+
+        bool MeshletsRenderPass::InitializePipelineState()
+        {
+            if (!m_shader)
+            {
+                return false;
+            }
+
+            const RPI::ShaderVariant& shaderVariant = m_shader->GetVariant(RPI::ShaderAsset::RootShaderVariantStableId);
+            RHI::PipelineStateDescriptorForDraw pipelineStateDescriptor;
+            shaderVariant.ConfigurePipelineState(pipelineStateDescriptor);
+
+            RPI::Scene* scene = GetScene();
+            if (!scene)
+            {
+                AZ_Error("Meshlets", false, "Scene could not be acquired");
+                return false;
+            }
+            RHI::DrawListTag drawListTag = m_shader->GetDrawListTag();
+            scene->ConfigurePipelineState(drawListTag, pipelineStateDescriptor);
+
+            pipelineStateDescriptor.m_renderAttachmentConfiguration = GetRenderAttachmentConfiguration();
+            pipelineStateDescriptor.m_inputStreamLayout.SetTopology(AZ::RHI::PrimitiveTopology::TriangleList);
+            pipelineStateDescriptor.m_inputStreamLayout.Finalize();
+
+            m_pipelineState = m_shader->AcquirePipelineState(pipelineStateDescriptor);
+            if (!m_pipelineState)
+            {
+                AZ_Error("Meshlets", false, "Pipeline state could not be acquired");
+                return false;
+            }
+
+            return true;
+        }
+
+        Data::Instance<RPI::Shader> MeshletsRenderPass::GetShader()
+        {
+            if (!m_shader)
+            {
+                AZ_Error("Meshlets", LoadShader(), "MeshletsRenderPass could not initialize pipeline or shader");
+            }
+            return m_shader;
+        }
+
+        bool MeshletsRenderPass::FillDrawRequestData(RHI::DrawPacketBuilder::DrawRequest& drawRequest)
+        {
+            if (!m_pipelineState)
+            {
+                return false;
+            }
+
+            drawRequest.m_listTag = m_drawListTag;
+            drawRequest.m_pipelineState = m_pipelineState;
+
+            return true;
+        }
+
+        // Adding draw packets
+        bool MeshletsRenderPass::AddDrawPackets(AZStd::list<const RHI::DrawPacket*> drawPackets)
+        {
+            bool overallSuccess = true;
+
+            if (!m_currentView &&
+                (!(m_currentView = GetView()) || !m_currentView->HasDrawListTag(m_drawListTag)))
+            {
+                m_currentView = nullptr;    // set it to nullptr to prevent further attempts this frame
+                AZ_Warning("Meshlets", false, "AddDrawPackets: failed to acquire or match the DrawListTag - check that your pass and shader tag name match");
+                return false;
+            }
+            
+            for (const RHI::DrawPacket* drawPacket : drawPackets)
+            {
+                if (!drawPacket)
+                {   // might not be an error - the object might have just been added and the DrawPacket is
+                    // scheduled to be built when the render frame begins
+                    AZ_Warning("Meshlets", false, "MeshletsRenderPass - DrawPacket wasn't built");
+                    overallSuccess = false;
+                    continue;   // other draw packets might be ok - don't break
+                }
+                m_currentView->AddDrawPacket(drawPacket);
+            }
+            return overallSuccess;
+        }
+
+        void MeshletsRenderPass::FrameBeginInternal(FramePrepareParams params)
+        {
+            if (!m_shader && AcquireFeatureProcessor())
+            {
+                LoadShader();
+            }
+
+            if (m_shader && !m_pipelineState)
+            {
+                InitializePipelineState();
+            }
+
+            if (!m_shader || !m_pipelineState)
+            {
+                return;
+            }
+
+            // Refresh current view every frame
+            if (!(m_currentView = GetView()) || !m_currentView->HasDrawListTag(m_drawListTag))
+            {
+                m_currentView = nullptr;    // set it to null if view exists but no tag match
+                AZ_Warning("Meshlets", false, "FrameBeginInternal: failed to acquire or match the DrawListTag - check that your pass and shader tag name match");
+                return;
+            }
+
+            RPI::RasterPass::FrameBeginInternal(params);
+        }
+
+        void MeshletsRenderPass::CompileResources(const RHI::FrameGraphCompileContext& context)
+        {
+            AZ_PROFILE_FUNCTION(AzRender);
+
+            if (!m_featureProcessor)
+            {
+                return;
+            }
+
+            // Compilation of remaining srgs will be done by the parent class 
+            RPI::RasterPass::CompileResources(context);
+        }
+
+        void MeshletsRenderPass::BuildShaderAndRenderData()
+        {
+            m_shader = nullptr; 
+            m_pipelineState = nullptr;
+            if (!AcquireFeatureProcessor() || !LoadShader() || !InitializePipelineState())
+            {
+                AZ_Error( "Meshlets", false, "MeshletsRenderPass::BuildShaderAndRenderData failed")
+            }
+        }
+
+        void MeshletsRenderPass::OnShaderReinitialized([[maybe_unused]] const RPI::Shader & shader)
+        {
+            BuildShaderAndRenderData();
+        }
+
+        void MeshletsRenderPass::OnShaderAssetReinitialized([[maybe_unused]] const Data::Asset<RPI::ShaderAsset>& shaderAsset)
+        {
+            BuildShaderAndRenderData();
+        }
+
+        void MeshletsRenderPass::OnShaderVariantReinitialized([[maybe_unused]] const AZ::RPI::ShaderVariant& shaderVariant)
+        {
+            BuildShaderAndRenderData();
+        }
+    } // namespace Meshlets
+}   // namespace AZ

+ 98 - 0
Gems/Meshlets/Code/Source/Meshlets/MeshletsRenderPass.h

@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzCore/Memory/SystemAllocator.h>
+
+#include <Atom/RHI.Reflect/Size.h>
+
+#include <Atom/RPI.Public/Pass/RasterPass.h>
+#include <Atom/RPI.Public/Shader/Shader.h>
+#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
+#include <Atom/RPI.Public/Shader/ShaderReloadNotificationBus.h>
+
+#include <Atom/Feature/TransformService/TransformServiceFeatureProcessor.h>
+
+#include <MeshletsRenderObject.h>
+
+namespace AZ
+{
+    namespace RHI
+    {
+        struct DrawItem;
+        class DrawPacket;
+    }
+
+    namespace Meshlets
+    {
+        class MeshletsRenderObject;
+        class MeshletsFeatureProcessor;
+
+        class MeshletsRenderPass
+            : public RPI::RasterPass
+            , private RPI::ShaderReloadNotificationBus::Handler
+        {
+            AZ_RPI_PASS(MeshletsRenderPass);
+
+        public:
+            AZ_RTTI(MeshletsRenderPass, "{753E455B-8E36-4DC3-B315-789F0EF0483C}", RasterPass);
+            AZ_CLASS_ALLOCATOR(MeshletsRenderPass, SystemAllocator, 0);
+
+            static RPI::Ptr<MeshletsRenderPass> Create(const RPI::PassDescriptor& descriptor);
+
+            // Adds the lod array of render data
+            bool FillDrawRequestData(RHI::DrawPacketBuilder::DrawRequest& drawRequest);
+            bool AddDrawPackets(AZStd::list<const RHI::DrawPacket*> drawPackets);
+
+            Data::Instance<RPI::Shader> GetShader();
+
+            void SetFeatureProcessor(MeshletsFeatureProcessor* featureProcessor)
+            {
+                m_featureProcessor = featureProcessor;
+            }
+
+        protected:
+            explicit MeshletsRenderPass(const RPI::PassDescriptor& descriptor);
+
+            // ShaderReloadNotificationBus::Handler overrides...
+            void OnShaderReinitialized(const RPI::Shader& shader) override;
+            void OnShaderAssetReinitialized(const Data::Asset<RPI::ShaderAsset>& shaderAsset) override;
+            void OnShaderVariantReinitialized(const AZ::RPI::ShaderVariant& shaderVariant) override;
+
+            void SetShaderPath(const char* shaderPath) { m_shaderPath = shaderPath; }
+            bool LoadShader();
+            bool InitializePipelineState();
+            bool AcquireFeatureProcessor();
+            void BuildShaderAndRenderData();
+
+            // Pass behavior overrides
+            void InitializeInternal() override;
+            void FrameBeginInternal(FramePrepareParams params) override;
+
+            // Scope producer functions...
+            void CompileResources(const RHI::FrameGraphCompileContext& context) override;
+
+        protected:
+            MeshletsFeatureProcessor* m_featureProcessor = nullptr;
+
+            // The  shader that will be used by the pass
+            Data::Instance<RPI::Shader> m_shader = nullptr;
+
+            // Override the following in the inherited class
+            AZStd::string m_shaderPath = "dummyShaderPath";
+
+            // To help create the pipeline state 
+            RPI::PassDescriptor m_passDescriptor;
+
+            const RHI::PipelineState* m_pipelineState = nullptr;
+            RPI::ViewPtr m_currentView = nullptr;
+        };
+
+    } // namespace Meshlets
+} // namespace AZ

+ 233 - 0
Gems/Meshlets/Code/Source/Meshlets/MeshletsUtilities.cpp

@@ -0,0 +1,233 @@
+/*
+ * 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/RHI/Factory.h>
+#include <Atom/RHI/RHIUtils.h>
+
+#include <Atom/RPI.Public/Shader/Shader.h>
+#include <Atom/RPI.Public/Image/StreamingImage.h>
+#include <Atom/RPI.Reflect/Buffer/BufferAssetView.h>
+
+#include <MeshletsUtilities.h>
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+
+        //!=====================================================================================
+        //!
+        //!                                Utility Functions
+        //!
+        //!=====================================================================================
+
+        // [To Do] examine if most of these functions can become global in RPI
+
+        //! Utility function to generate the Srg given the shader and the desired Srg name to be associated to.
+        //! If several shaders are sharing the same Srg (for example perView, perScene), it is enough to
+        //! create the Srg by associating it with a single shader and since the GPU signature and the data
+        //! are referring to the same shared description (preferable set in an [SrgDeclaration].aszli file)
+        //! The association with all shaders will work properly.
+        Data::Instance<RPI::ShaderResourceGroup> UtilityClass::CreateShaderResourceGroup(
+            Data::Instance<RPI::Shader> shader,
+            const char* shaderResourceGroupId,
+            [[maybe_unused]] const char* moduleName)
+        {
+            Data::Instance<RPI::ShaderResourceGroup> srg = RPI::ShaderResourceGroup::Create(shader->GetAsset(), AZ::Name{ shaderResourceGroupId });
+            if (!srg)
+            {
+                AZ_Error(moduleName, false, "Failed to create shader resource group [%s]", shaderResourceGroupId);
+                return nullptr;
+            }
+            return srg;
+        }
+
+        //! Utility function to create a resource view of different type than the shared buffer data.
+        //! Since this class is sub-buffer container, this method should be used after creating
+        //!  a new allocation to be used as a sub-buffer.
+        RHI::BufferViewDescriptor UtilityClass::CreateResourceViewWithDifferentFormat(
+            uint32_t offsetInBytes, uint32_t elementCount, uint32_t elementSize,
+            RHI::Format format, RHI::BufferBindFlags overrideBindFlags)
+        {
+            RHI::BufferViewDescriptor viewDescriptor;
+
+            // In the following line I use the element size and not the size based of the
+            // element format since in the more interesting case of structured buffer, the
+            // size will result in an error.
+            uint32_t elementOffset = offsetInBytes / elementSize;
+            viewDescriptor.m_elementOffset = elementOffset;
+            viewDescriptor.m_elementCount = elementCount;
+            viewDescriptor.m_elementFormat = format;
+            viewDescriptor.m_elementSize = elementSize;
+            viewDescriptor.m_overrideBindFlags = overrideBindFlags;
+            return viewDescriptor;
+        }
+
+        //! If srg is nullptr the index handle will NOT be set.
+        //! This can be useful when creating a constant buffer or an image.
+        Data::Instance<RPI::Buffer> UtilityClass::CreateBuffer(
+            [[maybe_unused]] const char* warningHeader,
+            SrgBufferDescriptor& bufferDesc,
+            Data::Instance<RPI::ShaderResourceGroup> srg)
+        {
+            // If srg is provided, match the shader Srg bind index (returned via the descriptor)
+            if (srg)
+            {   // Not always do we want to associate Srg when creating a buffer
+                bufferDesc.m_resourceShaderIndex = srg->FindShaderInputBufferIndex(bufferDesc.m_paramNameInSrg).GetIndex();
+                if (bufferDesc.m_resourceShaderIndex == uint32_t(-1))
+                {
+                    AZ_Error(warningHeader, false, "Failed to find shader input index for [%s] in the SRG.",
+                        bufferDesc.m_paramNameInSrg.GetCStr());
+                    return nullptr;
+                }
+            }
+
+            // Descriptor setting
+            RPI::CommonBufferDescriptor desc;
+            desc.m_elementFormat = bufferDesc.m_elementFormat;
+            desc.m_poolType = bufferDesc.m_poolType;
+            desc.m_elementSize = bufferDesc.m_elementSize;
+            desc.m_bufferName = bufferDesc.m_bufferName.GetCStr();
+            desc.m_byteCount = (uint64_t)bufferDesc.m_elementCount * bufferDesc.m_elementSize;
+            desc.m_bufferData = nullptr;    // set during asset load - use Update
+
+            // Buffer creation
+            return RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc);
+        }
+
+        bool UtilityClass::BindBufferToSrg(
+            [[maybe_unused]] const char* warningHeader,
+            Data::Instance<RPI::Buffer> buffer,
+            SrgBufferDescriptor& bufferDesc,
+            Data::Instance<RPI::ShaderResourceGroup> srg)
+        {
+            if (!buffer)
+            {
+                AZ_Error(warningHeader, false, "Trying to bind a null buffer");
+                return false;
+            }
+
+            RHI::ShaderInputBufferIndex bufferIndex = srg->FindShaderInputBufferIndex(bufferDesc.m_paramNameInSrg);
+            if (!bufferIndex.IsValid())
+            {
+                AZ_Error(warningHeader, false, "Failed to find shader input index for [%s] in the SRG.",
+                    bufferDesc.m_paramNameInSrg.GetCStr());
+                return false;
+            }
+
+            if (!srg->SetBufferView(bufferIndex, buffer->GetBufferView()))
+            {
+                AZ_Error(warningHeader, false, "Failed to bind buffer view for [%s]", bufferDesc.m_bufferName.GetCStr());
+                return false;
+            }
+            return true;
+        }
+
+        Data::Instance<RPI::Buffer> UtilityClass::CreateBufferAndBindToSrg(
+            const char* warningHeader,
+            SrgBufferDescriptor& bufferDesc,
+            Data::Instance<RPI::ShaderResourceGroup> srg)
+        {
+            // Buffer creation
+            Data::Instance<RPI::Buffer> buffer = CreateBuffer(warningHeader, bufferDesc, srg);
+
+            if (!BindBufferToSrg(warningHeader, buffer, bufferDesc, srg))
+            {
+                return nullptr;
+            }
+            return buffer;
+        }
+
+        // Returns the buffer view instance as well as the buffer allocator
+        Data::Instance<RHI::BufferView> UtilityClass::CreateSharedBufferView(
+            const char* warningHeader,
+            SrgBufferDescriptor& bufferDesc,
+            Data::Instance<Meshlets::SharedBufferAllocation>& outputBufferAllocator)
+        {
+            size_t requiredSize = (uint64_t)bufferDesc.m_elementCount * bufferDesc.m_elementSize;
+            outputBufferAllocator = Meshlets::SharedBufferInterface::Get()->Allocate(requiredSize);
+            if (!outputBufferAllocator)
+            {
+                AZ_Error(warningHeader, false, "Shared buffer out of memory for [%s]", bufferDesc.m_bufferName.GetCStr());
+                return Data::Instance<RHI::BufferView>();
+            }
+
+            // Create the buffer view into the shared buffer - it will be used as a separate buffer
+            // by the PerObject Srg.
+            bufferDesc.m_viewOffsetInBytes = uint32_t(outputBufferAllocator->GetVirtualAddress().m_ptr);
+            AZ_Assert(bufferDesc.m_viewOffsetInBytes % bufferDesc.m_elementSize == 0, "Offset of buffer within The SharedBuffer is NOT aligned.");
+
+            // And here we create resource view from the shared buffer 
+            RHI::BufferViewDescriptor viewDescriptor = UtilityClass::CreateResourceViewWithDifferentFormat(
+                bufferDesc.m_viewOffsetInBytes, bufferDesc.m_elementCount, bufferDesc.m_elementSize,
+                bufferDesc.m_elementFormat, bufferDesc.m_bindFlags
+            );
+            // Notice the following - this is crucial in order to pass the RHI validation
+            // and force it not to fail the buffers views due to missing attachment.
+            // The attachment itself is created for the PerPass shared buffer.
+            viewDescriptor.m_ignoreFrameAttachmentValidation = true;
+
+            RHI::Buffer* rhiBuffer = Meshlets::SharedBufferInterface::Get()->GetBuffer()->GetRHIBuffer();
+            Data::Instance<RHI::BufferView> bufferView = RHI::Factory::Get().CreateBufferView();
+            RHI::ResultCode resultCode = bufferView->Init(*rhiBuffer, viewDescriptor);
+
+            if (resultCode != RHI::ResultCode::Success)
+            {
+                AZ_Error(warningHeader, false, "BufferView could not be retrieved for [%s]", bufferDesc.m_bufferName.GetCStr());
+                return Data::Instance<RHI::BufferView>();
+            }
+
+            return bufferView;
+        }
+
+        bool UtilityClass::BindBufferViewToSrg(
+            [[maybe_unused]] const char* warningHeader,
+            Data::Instance<RHI::BufferView> bufferView,
+            SrgBufferDescriptor& bufferDesc,
+            Data::Instance<RPI::ShaderResourceGroup> srg)
+        {
+            if (!bufferView || !srg)
+            {
+                AZ_Error(warningHeader, false, "Trying to bind a null buffer view or Srg");
+                return false;
+            }
+
+            RHI::ShaderInputBufferIndex bufferIndex = srg->FindShaderInputBufferIndex(bufferDesc.m_paramNameInSrg);
+            if (!bufferIndex.IsValid())
+            {
+                AZ_Error(warningHeader, false, "Failed to find shader input index for [%s] in the SRG.",
+                    bufferDesc.m_paramNameInSrg.GetCStr());
+                return false;
+            }
+
+            if (!srg->SetBufferView(bufferIndex, bufferView.get()))
+            {
+                AZ_Error(warningHeader, false, "Failed to bind buffer view for [%s]", bufferDesc.m_bufferName.GetCStr());
+                return false;
+            }
+            return true;
+        }
+
+        Data::Instance<RHI::BufferView> UtilityClass::CreateSharedBufferViewAndBindToSrg(
+            const char* warningHeader,
+            SrgBufferDescriptor& bufferDesc,
+            Data::Instance<Meshlets::SharedBufferAllocation>& outputBufferAllocator,
+            Data::Instance<RPI::ShaderResourceGroup> srg)
+        {
+            // BufferView creation
+            Data::Instance<RHI::BufferView> bufferView = CreateSharedBufferView(warningHeader, bufferDesc, outputBufferAllocator);
+
+            if (srg && !BindBufferViewToSrg(warningHeader, bufferView, bufferDesc, srg))
+            {
+               return nullptr;
+            }
+            return bufferView;
+        }
+
+    } // namespace Meshlets
+} // namespace AZ

+ 98 - 0
Gems/Meshlets/Code/Source/Meshlets/MeshletsUtilities.h

@@ -0,0 +1,98 @@
+/*
+* Modifications Copyright (c) Contributors to the Open 3D Engine Project. 
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+* 
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+#pragma once
+
+#include <AtomCore/Instance/InstanceData.h>
+#include <AtomCore/Instance/Instance.h>
+
+#include <Atom/RHI/ImagePool.h>
+
+#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
+#include <Atom/RPI.Public/Image/StreamingImage.h>
+#include <Atom/RPI.Public/Image/AttachmentImage.h>
+#include <Atom/RPI.Public/Shader/Shader.h>
+
+#include <SharedBuffer.h>
+
+namespace AZ
+{
+    namespace RHI
+    {
+        class BufferAssetView;
+    }
+
+    namespace RPI
+    {
+        class Buffer;
+    }
+
+    namespace Meshlets
+    {
+        class UtilityClass
+        {
+        public:
+            UtilityClass() = default;
+
+            static Data::Instance<RPI::ShaderResourceGroup> CreateShaderResourceGroup(
+                Data::Instance<RPI::Shader> shader,
+                const char* shaderResourceGroupId,
+                const char* moduleName
+            );
+
+            static Data::Instance<RPI::Buffer> CreateBuffer(
+                const char* warningHeader,
+                SrgBufferDescriptor& bufferDesc,
+                Data::Instance<RPI::ShaderResourceGroup> srg = nullptr
+            );
+
+            static Data::Instance<RPI::Buffer> CreateBufferAndBindToSrg(
+                const char* warningHeader,
+                SrgBufferDescriptor& bufferDesc,
+                Data::Instance<RPI::ShaderResourceGroup> srg
+            );
+
+            static bool BindBufferToSrg(
+                const char* warningHeader,
+                Data::Instance<RPI::Buffer> buffer,
+                SrgBufferDescriptor& bufferDesc,
+                Data::Instance<RPI::ShaderResourceGroup> srg = nullptr
+            );
+
+            static Data::Instance<RHI::BufferView> CreateSharedBufferView(
+                const char* warningHeader,
+                SrgBufferDescriptor& bufferDesc,
+                Data::Instance<Meshlets::SharedBufferAllocation>& bufferAllocator
+            );
+
+            static bool BindBufferViewToSrg(
+                [[maybe_unused]] const char* warningHeader,
+                Data::Instance<RHI::BufferView> bufferView,
+                SrgBufferDescriptor& bufferDesc,
+                Data::Instance<RPI::ShaderResourceGroup> srg
+            );
+
+            static Data::Instance<RHI::BufferView> CreateSharedBufferViewAndBindToSrg(
+                const char* warningHeader,
+                SrgBufferDescriptor& bufferDesc,
+                Data::Instance<Meshlets::SharedBufferAllocation>& outputBufferAllocator,
+                Data::Instance<RPI::ShaderResourceGroup> srg
+            );
+
+            //! Utility function to create a resource view into the shared buffer memory area. This
+            //! resource view can have a different type than the shared buffer data.
+            //! Since the shared buffer class is used as a buffer allocation for many sub-buffers, this
+            //! method should be used after creating a new allocation within the shared buffer.
+            static RHI::BufferViewDescriptor CreateResourceViewWithDifferentFormat(
+                uint32_t offsetInBytes, uint32_t elementCount, uint32_t elementSize,
+                RHI::Format format, RHI::BufferBindFlags overrideBindFlags
+            );
+        };
+
+    } // namespace Meshlets
+} // namespace AZ

+ 162 - 0
Gems/Meshlets/Code/Source/Meshlets/MultiDispatchComputePass.cpp

@@ -0,0 +1,162 @@
+/*
+ * 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/RHI/CommandList.h>
+
+#include <Atom/RHI/Factory.h>
+#include <Atom/RHI/FrameGraphAttachmentInterface.h>
+#include <Atom/RHI/FrameGraphInterface.h>
+#include <Atom/RHI/PipelineState.h>
+
+#include <Atom/RPI.Public/Base.h>
+#include <Atom/RPI.Public/Pass/PassUtils.h>
+#include <Atom/RPI.Public/RenderPipeline.h>
+#include <Atom/RHI/RHISystemInterface.h>
+#include <Atom/RPI.Public/RPIUtils.h>
+#include <Atom/RPI.Public/Scene.h>
+#include <Atom/RPI.Public/View.h>
+#include <Atom/RPI.Reflect/Pass/PassTemplate.h>
+#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
+
+#include <MultiDispatchComputePass.h>
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        Data::Instance<RPI::Shader> MultiDispatchComputePass::GetShader()
+        {
+            return m_shader;
+        }
+
+        RPI::Ptr<MultiDispatchComputePass> MultiDispatchComputePass::Create(const RPI::PassDescriptor& descriptor)
+        {
+            RPI::Ptr<MultiDispatchComputePass> pass = aznew MultiDispatchComputePass(descriptor);
+            return pass;
+        }
+
+        MultiDispatchComputePass::MultiDispatchComputePass(const RPI::PassDescriptor& descriptor)
+            : RPI::ComputePass(descriptor)
+        {
+        }
+
+        void MultiDispatchComputePass::BuildInternal()
+        {
+            ComputePass::BuildInternal();
+
+            // Output
+            // This is the buffer that is shared between all objects and dispatches and contains
+            // the dynamic data that can be changed between passes.
+            Name bufferName = Name{ "MeshletsSharedBuffer" };
+            RPI::PassAttachmentBinding* localBinding = FindAttachmentBinding(bufferName);
+            if (localBinding && !localBinding->GetAttachment() && Meshlets::SharedBufferInterface::Get())
+            {
+                AttachBufferToSlot(bufferName, Meshlets::SharedBufferInterface::Get()->GetBuffer());
+            }
+        }
+
+        void MultiDispatchComputePass::CompileResources([[maybe_unused]] const RHI::FrameGraphCompileContext& context)
+        {
+            // DON'T call the ComputePass:CompileResources as it will try to compile perDraw srg
+            // under the assumption that this is a single dispatch compute.  Here we have dispatch
+            // per hair object and each has its own perDraw srg.
+            if (m_shaderResourceGroup != nullptr)
+            {
+                BindPassSrg(context, m_shaderResourceGroup);
+                m_shaderResourceGroup->Compile();
+
+
+            }
+
+            // Instead of compiling per frame, have everything compiled only once after data initialization!
+            // Below is an example of compiling the dispatch if change is required.
+            /*
+            for (auto& dispatchItem : m_dispatchItems)
+            {
+                if (dispatchItem)
+                {
+                    for (RHI::ShaderResourceGroup* srgInDispatch : dispatchItem->m_shaderResourceGroups)
+                    {
+                        srgInDispatch->Compile()
+                    }
+                }
+            }
+            */
+        }
+
+        void MultiDispatchComputePass::AddDispatchItems(AZStd::list<RHI::DispatchItem*>& dispatchItems)
+        {
+            for (auto& dispatchItem : dispatchItems)
+            {
+                if (dispatchItem)
+                {
+                    m_dispatchItems.insert(dispatchItem);
+                }
+            }
+        }
+
+        // [To Do] Important remark
+        //-------------------------
+        // When the work load / amount of dispatches is high, the RHI will split work and distribute it
+        // between several threads.
+        // To avoid repeating the work or possibly corrupting data in such a case, split the work
+        // as per Github issue #9899 (https://github.com/o3de/o3de/pull/9899) as an example of how to
+        // prevent multiple threads trying to submit the same work.
+        // This was not done here yet due to the very limited work required but shuold be changed!
+        void MultiDispatchComputePass::BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context)
+        {
+            RHI::CommandList* commandList = context.GetCommandList();
+
+            for (const RHI::DispatchItem* dispatchItem : m_dispatchItems)
+            {
+                // The following will bind all registered Srgs set in m_shaderResourceGroupsToBind
+                // and sends them to the command list ahead of the dispatch.
+                // This includes the PerView, PerScene and PerPass srgs.
+                SetSrgsForDispatch(commandList);
+
+                // In a similar way, add the dispatch high frequencies srgs.
+                for (uint32_t srg = 0; srg < dispatchItem->m_shaderResourceGroupCount; ++srg)
+                {
+                    const RHI::ShaderResourceGroup* shaderResourceGroup = dispatchItem->m_shaderResourceGroups[srg];
+                    commandList->SetShaderResourceGroupForDispatch(*shaderResourceGroup);
+                }
+
+                // submit the dispatch
+                commandList->Submit(*dispatchItem);
+            }
+
+            // Clear the dispatch items. They will need to be re-populated next frame
+            m_dispatchItems.clear();
+        }
+
+        // [To Do] - implement in order to support hot reloading of the shaders
+        void MultiDispatchComputePass::BuildShaderAndRenderData()
+        {
+        }
+
+        // Before reloading shaders, we want to wait for existing dispatches to finish
+        // so shader reloading does not interfere in any way. Because AP reloads are async, there might
+        // be a case where dispatch resources are destructed and will most certainly cause a GPU crash.
+        // If we flag the need for rebuild, the build will be made at the start of the next frame - at
+        // this stage the dispatch items should have been cleared - now we can load the shader and data.
+        void MultiDispatchComputePass::OnShaderReinitialized([[maybe_unused]] const AZ::RPI::Shader& shader)
+        {
+            BuildShaderAndRenderData();
+        }
+
+        void MultiDispatchComputePass::OnShaderAssetReinitialized([[maybe_unused]] const Data::Asset<AZ::RPI::ShaderAsset>& shaderAsset)
+        {
+            BuildShaderAndRenderData();
+        }
+
+        void MultiDispatchComputePass::OnShaderVariantReinitialized([[maybe_unused]] const AZ::RPI::ShaderVariant& shaderVariant)
+        {
+            BuildShaderAndRenderData();
+        }
+    } // namespace Meshlets
+}   // namespace AZ

+ 78 - 0
Gems/Meshlets/Code/Source/Meshlets/MultiDispatchComputePass.h

@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzCore/Memory/SystemAllocator.h>
+
+#include <Atom/RHI.Reflect/Size.h>
+
+#include <Atom/RPI.Public/Pass/ComputePass.h>
+#include <Atom/RPI.Public/Shader/Shader.h>
+#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
+
+#include <MeshletsRenderObject.h>
+
+namespace AZ
+{
+    namespace RHI
+    {
+        struct DispatchItem;
+    }
+
+    namespace Meshlets
+    {
+        //! Multi Dispatch Pass - this pass will handle multiple dispatch submission
+        //! during each frame - one dispatch per mesh, each represents group of compute
+        //! threads that will be working to create meshlets of the given mesh.
+        //! This class can be generalized in the future to become a base class for this
+        //! dispatch submission.
+        //! [To Do] - revisit the 'BuildCommandListInternal' method and refactor to handle
+        //! 'under the hood' RHI CPU threads that carries the submissions in parallel
+        class MultiDispatchComputePass final
+            : public RPI::ComputePass
+        {
+            AZ_RPI_PASS(MultiDispatchComputePass);
+
+        public:
+            AZ_RTTI(MultiDispatchComputePass, "{13B3BAC7-0F12-4C23-BD9E-F82A7830195E}", RPI::ComputePass);
+            AZ_CLASS_ALLOCATOR(MultiDispatchComputePass, SystemAllocator, 0);
+            ~MultiDispatchComputePass() = default;
+
+            static RPI::Ptr<MultiDispatchComputePass> Create(const RPI::PassDescriptor& descriptor);
+
+            //! Thread-safe function for adding the frame's dispatch items
+            void AddDispatchItems(AZStd::list<RHI::DispatchItem*>& dispatchItems);
+
+            // Pass behavior overrides
+            void CompileResources(const RHI::FrameGraphCompileContext& context) override;
+
+            //! Returns the shader held by the ComputePass
+            Data::Instance<RPI::Shader> GetShader();
+
+        protected:
+            MultiDispatchComputePass(const RPI::PassDescriptor& descriptor);
+
+            // Overriding methods
+            void BuildInternal() override;
+            void BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) override;
+
+            // ShaderReloadNotificationBus::Handler overrides...
+            void OnShaderReinitialized(const AZ::RPI::Shader& shader) override;
+            void OnShaderAssetReinitialized(const Data::Asset<AZ::RPI::ShaderAsset>& shaderAsset) override;
+            void OnShaderVariantReinitialized(const AZ::RPI::ShaderVariant& shaderVariant) override;
+
+            void BuildShaderAndRenderData();
+
+        private:
+            AZStd::unordered_set<const RHI::DispatchItem*> m_dispatchItems;
+        };
+
+    }   // namespace Meshlets
+}   // namespace AZ
+

+ 213 - 0
Gems/Meshlets/Code/Source/Meshlets/SharedBuffer.cpp

@@ -0,0 +1,213 @@
+/*
+ * 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/RHI.Reflect/ShaderSemantic.h>
+#include <Atom/RHI/RHIUtils.h>
+#include <Atom/RHI/Factory.h>
+
+#include <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
+#include <Atom/RPI.Reflect/ResourcePoolAssetCreator.h>
+
+#include <SharedBuffer.h>
+
+#include <numeric>
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        //! When given null srg, the index handle is NOT set.
+        //! Useful when creating a specific Srg buffer.
+        void SharedBuffer::CreateSharedBuffer(SrgBufferDescriptor& bufferDesc)
+        {
+            // Descriptor setting
+            RPI::CommonBufferDescriptor descriptor;
+
+            descriptor.m_poolType = RPI::CommonBufferPoolType::ReadWrite;
+            descriptor.m_elementFormat = bufferDesc.m_elementFormat;
+            descriptor.m_elementSize = bufferDesc.m_elementSize;
+            descriptor.m_bufferName = bufferDesc.m_bufferName.GetCStr();
+            descriptor.m_byteCount = (uint64_t)bufferDesc.m_elementCount * bufferDesc.m_elementSize;
+            descriptor.m_bufferData = nullptr;
+
+            // The actual RPI shared buffer creation
+            m_buffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(descriptor);
+        }
+
+        //--------------------------------------------------------------------------
+        //! Setting the constructor as private will create compile error to remind the developer to set
+        //! the buffer Init in the FeatureProcessor and initialize properly
+        SharedBuffer::SharedBuffer()
+        {
+            AZ_Warning("SharedBuffer", false, "Missing information to properly create SharedBuffer.");
+
+            Interface<SharedBufferInterface>::Register(this);
+        }
+
+        SharedBuffer::SharedBuffer(
+            AZStd::string bufferName, uint32_t sharedBufferSize,
+            AZStd::vector<SrgBufferDescriptor>& buffersDescriptors)
+        {
+            m_sizeInBytes = sharedBufferSize;
+            m_bufferName = bufferName;
+            Init(bufferName, buffersDescriptors);
+        }
+
+        SharedBuffer::~SharedBuffer()
+        {
+        }
+
+        //! This method that ensures that the alignment over the various BufferViews is
+        //! always kept, given the various possible buffer descriptors using the buffer.
+        void SharedBuffer::CalculateAlignment(AZStd::vector<SrgBufferDescriptor>& buffersDescriptors)
+        {
+            m_alignment = MinAllowedAlignment;
+            for (uint8_t bufferIndex = 0; bufferIndex < buffersDescriptors.size(); ++bufferIndex)
+            {
+                // Using the least common multiple enables resource views to be typed and ensures they can get
+                // an offset in bytes that is a multiple of an element count
+                m_alignment = std::lcm(m_alignment, buffersDescriptors[bufferIndex].m_elementSize);
+            }
+            m_alignment = AZStd::max(m_alignment, MinAllowedAlignment) +
+                (MinAllowedAlignment - m_alignment % MinAllowedAlignment);
+        }
+
+        void SharedBuffer::InitAllocator()
+        {
+            RHI::FreeListAllocator::Descriptor allocatorDescriptor;
+            allocatorDescriptor.m_alignmentInBytes = m_alignment;
+            allocatorDescriptor.m_capacityInBytes = m_sizeInBytes;
+            allocatorDescriptor.m_policy = RHI::FreeListAllocatorPolicy::BestFit;
+            allocatorDescriptor.m_garbageCollectLatency = 0;
+            m_freeListAllocator.Init(allocatorDescriptor);
+        }
+
+        void SharedBuffer::Init(AZStd::string bufferName, AZStd::vector<SrgBufferDescriptor>& buffersDescriptors)
+        {
+            m_bufferName = bufferName;
+            AZStd::string sufferNameInShader = "m_" + bufferName;
+            // m_sizeInBytes = 256u * (1024u * 1024u);
+            //
+            // [To Do] replace this with max size request for allocation that can be given by the calling function
+            // This has the following problems:
+            //  1. The need to have this aggregated size in advance
+            //  2. The size might grow dynamically between frames
+            //  3. Due to having several stream buffers (position, tangent, structured), alignment padding
+            //      size calculation must be added.
+            // Requirement: the buffer already has an assert on allocation beyond the memory.  In the future it should
+            // support greedy memory allocation when memory has reached its end.  This must not invalidate the buffer during
+            // the current frame, hence allocation of second buffer, fence and a copy must take place.
+
+            // Create the global buffer that holds all buffer views
+            // Remark: in order to enable indirect usage, the file BufferSystem.cpp must
+            // be changed to support a pool that supports this type or else a buffer view
+            // validation test will fail.
+            // The change should be done in 'BufferSystem::CreateCommonBufferPool'.
+            SrgBufferDescriptor  sharedBufferDesc = SrgBufferDescriptor(
+                RPI::CommonBufferPoolType::ReadWrite, 
+                RHI::Format::Unknown,
+                RHI::BufferBindFlags::InputAssembly | RHI::BufferBindFlags::Indirect | RHI::BufferBindFlags::ShaderReadWrite,
+                sizeof(uint32_t), uint32_t(m_sizeInBytes / sizeof(uint32_t)),
+                Name{ bufferName }, Name{ sufferNameInShader }, 0, 0, nullptr
+            );
+
+            // Use the following method to calculate alignment given a list of descriptors
+            CalculateAlignment(buffersDescriptors);
+
+            InitAllocator();
+
+            CreateSharedBuffer(sharedBufferDesc);
+
+            SystemTickBus::Handler::BusConnect();
+        }
+
+        AZStd::intrusive_ptr<SharedBufferAllocation> SharedBuffer::Allocate(size_t byteCount)
+        {
+            RHI::VirtualAddress result;
+            {
+                AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
+                result = m_freeListAllocator.Allocate(byteCount, m_alignment);
+            }
+
+            if (result.IsValid())
+            {
+                return aznew SharedBufferAllocation(result);
+            }
+
+            return nullptr;
+        }
+
+        void SharedBuffer::DeAllocate(RHI::VirtualAddress allocation)
+        {
+            if (allocation.IsValid())
+            {
+                {
+                    AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
+                    m_freeListAllocator.DeAllocate(allocation);
+                }
+
+                m_memoryWasFreed = true;
+                m_broadcastMemoryAvailableEvent = true;
+            }
+        }
+
+        void SharedBuffer::DeAllocateNoSignal(RHI::VirtualAddress allocation)
+        {
+            if (allocation.IsValid())
+            {
+                {
+                    AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
+                    m_freeListAllocator.DeAllocate(allocation);
+                }
+                m_memoryWasFreed = true;
+            }
+        }
+
+        Data::Instance<RPI::Buffer> SharedBuffer::GetBuffer()
+        {
+            AZ_Assert(m_buffer, "SharedBuffer - the buffer doesn't exist yet");
+            return m_buffer;
+        }
+
+        //! Update buffer's content with sourceData at an offset of bufferByteOffset
+        bool SharedBuffer::UpdateData(const void* sourceData, uint64_t sourceDataSizeInBytes, uint64_t bufferByteOffset)
+        {
+            AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
+            if (m_buffer.get())
+            {
+                return m_buffer->UpdateData(sourceData, sourceDataSizeInBytes, bufferByteOffset);
+            }
+            AZ_Assert(false, "SharedBuffer error in data allocation - the buffer doesn't exist yet");
+            return false;
+        }
+
+        void SharedBuffer::OnSystemTick()
+        {
+            GarbageCollect();
+        }
+
+        void SharedBuffer::GarbageCollect()
+        {
+            if (m_memoryWasFreed)
+            {
+                m_memoryWasFreed = false;
+                {
+                    AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
+                    m_freeListAllocator.GarbageCollect();
+                }
+                if (m_broadcastMemoryAvailableEvent)
+                {
+                    SharedBufferNotificationBus::Broadcast(&SharedBufferNotificationBus::Events::OnSharedBufferMemoryAvailable);
+                    m_broadcastMemoryAvailableEvent = false;
+                }
+            }
+        }
+
+    } // namespace Meshlets
+} // namespace AZ
+

+ 148 - 0
Gems/Meshlets/Code/Source/Meshlets/SharedBuffer.h

@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzCore/Component/TickBus.h>
+#include <AzCore/Name/Name.h>
+
+#include <Atom/RHI/FreeListAllocator.h>
+#include <Atom/RHI.Reflect/Format.h>
+#include <Atom/RHI/BufferPool.h>
+
+#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
+
+#include <SharedBufferInterface.h>
+
+namespace AZ
+{
+    namespace RPI
+    {
+        class Buffer;
+    }
+
+    namespace Meshlets
+    {
+        //!=====================================================================
+        //! This structure contains information regarding the naming of the buffer on both
+        //!  the CPU and the GPU 
+        //! This structure is also used to determine the maximum alignment required for
+        //!  the buffer when allocating sub-buffers
+        //!=====================================================================
+        struct SrgBufferDescriptor
+        {
+            //! Pool type to determine how a resource pool should be generated.
+            //! This is used for buffers that are not part of the shared buffer.
+            RPI::CommonBufferPoolType m_poolType = RPI::CommonBufferPoolType::ReadOnly;
+            //! The format used for the buffer
+            //! Should be Unknown for structured buffers, or R32 for raw buffers.
+            RHI::Format m_elementFormat;
+            //! The size in bytes of each element in the stream
+            RHI::BufferBindFlags m_bindFlags;
+
+            uint32_t m_elementSize;
+            //! Amount of elements required to create the buffer
+            uint32_t m_elementCount;
+            //! The name used for the buffer view - used for debug and tracking
+            Name m_bufferName;
+            //! The name used by the shader Srg in the GPU for this shader parameter 
+            Name m_paramNameInSrg;
+            //! The assigned SRG slot in the CPU / GPU for this shader resource
+            uint32_t m_resourceShaderIndex;
+            //! If using a buffer view within a shared buffer, this represents
+            //! the view offset from the shared buffer origin in bytes.
+            uint32_t m_viewOffsetInBytes;
+
+            uint8_t* m_bufferData = nullptr;
+
+            SrgBufferDescriptor() = default;
+            SrgBufferDescriptor(
+                RPI::CommonBufferPoolType poolType,
+                RHI::Format elementFormat,
+                RHI::BufferBindFlags m_bindFlags,
+                uint32_t elementSize,
+                uint32_t elementCount,
+                Name bufferName,
+                Name paramNameInSrg,
+                uint32_t resourceShaderIndex,
+                uint32_t viewOffsetInBytes,
+                uint8_t* bufferData = nullptr
+            ) : m_poolType(poolType),
+                m_elementFormat(elementFormat), m_bindFlags(m_bindFlags),
+                m_elementSize(elementSize), m_elementCount(elementCount),
+                m_bufferName(bufferName), m_paramNameInSrg(paramNameInSrg),
+                m_resourceShaderIndex(resourceShaderIndex), m_viewOffsetInBytes(viewOffsetInBytes),
+                m_bufferData(bufferData)
+            {};
+        };
+
+        //!=====================================================================
+        //! This class represents a single RPI::Buffer used to allocate sub-buffers from the
+        //!  existing buffer that can then be used per draw.   In a way, this buffer is used as a memory
+        //!  pool from which sub-buffers are being created.
+        //! This is very useful when we want to synchronize the use of these buffers via barriers 
+        //!  so we declare and pass the entire buffer between passes and therefore we are creating
+        //!  a dependency and barrier for this single buffer, yet as a result all sub-buffers are
+        //!  now getting synced between passes.
+        //! 
+        // Adopted from SkinnedMeshOutputStreamManager in order to make it more generic and context free usage
+        //!=====================================================================
+        class SharedBuffer
+            : public Meshlets::SharedBufferInterface
+            , private SystemTickBus::Handler
+        {
+        public:
+            AZ_RTTI(AZ::Meshlets::SharedBuffer, "{6005990E-3BBF-4946-9F2B-6A7739912100}", AZ::Meshlets::SharedBufferInterface);
+
+            SharedBuffer();
+            SharedBuffer(
+                AZStd::string bufferName, uint32_t sharedBufferSize,
+                AZStd::vector<SrgBufferDescriptor>& buffersDescriptors);
+
+            ~SharedBuffer();
+            void Init(AZStd::string bufferName, AZStd::vector<SrgBufferDescriptor>& buffersDescriptors);
+
+            // SharedBufferInterface
+            AZStd::intrusive_ptr<SharedBufferAllocation> Allocate(size_t byteCount) override;
+            void DeAllocate(RHI::VirtualAddress allocation) override;
+            void DeAllocateNoSignal(RHI::VirtualAddress allocation) override;
+
+            Data::Instance<RPI::Buffer> GetBuffer() override;
+
+            //! Update the content of an area within the shared buffer
+            bool UpdateData(const void* sourceData, uint64_t sourceDataSizeInBytes, uint64_t bufferByteOffset = 0) override;
+
+        private:
+            // SystemTickBus
+            void OnSystemTick() override;
+
+            void GarbageCollect();
+            void CalculateAlignment(AZStd::vector<SrgBufferDescriptor>& buffersDescriptors);
+            void InitAllocator();
+            void CreateSharedBuffer(SrgBufferDescriptor& bufferDesc);
+
+            AZStd::string m_bufferName = "MeshletsSharedBuffer";
+            Data::Instance<RPI::Buffer> m_buffer = nullptr;
+
+            RHI::FreeListAllocator m_freeListAllocator;
+            AZStd::mutex m_allocatorMutex;
+            const uint32_t MinAllowedAlignment = 16;    // Due to Vulkan / DX12 various restrictions.
+            uint32_t m_alignment = 16;     // This will be overridden by the size of the largest allocated element
+
+            //! Currently the shared buffer size is fixed. Going towards dynamic size can be a better
+            //! solution but requires using re-allocations and proper synchronizing between all existing buffers.
+            //! Additional attention should be given to the fact that because the buffers in Atom are NOT triple
+            //! buffered but instead they are delayed via garbage collection mechanism, during reallocation
+            //! the amount of memory required might reach close to double of the run-time.
+            size_t m_sizeInBytes = 256u * (1024u * 1024u);   
+            bool m_memoryWasFreed = false;
+            bool m_broadcastMemoryAvailableEvent = false;
+        };
+    } // namespace Meshlets
+} // namespace AZ
+

+ 131 - 0
Gems/Meshlets/Code/Source/Meshlets/SharedBufferInterface.h

@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzCore/std/smart_ptr/intrusive_ptr.h>
+#include <AzCore/std/smart_ptr/intrusive_base.h>
+#include <AzCore/Interface/Interface.h>
+#include <AzCore/Asset/AssetCommon.h>
+#include <AzCore/EBus/Event.h>
+
+#include <AtomCore/Instance/Instance.h>
+#include <Atom/RHI/Allocator.h>
+
+namespace AZ
+{
+    namespace RHI
+    {
+        class Buffer;
+        class BufferView;
+    }
+
+    namespace RPI
+    {
+        class BufferAsset;
+        class Buffer;
+    }
+
+    namespace Meshlets
+    {
+        class SharedBufferAllocation;
+
+        //! A class for allocating memory for skinning buffers
+        class SharedBufferInterface
+        {
+        public:
+            AZ_RTTI(AZ::Meshlets::SharedBufferInterface, "{6048DAF9-7A05-41B3-94C8-FBBDB3A187D2}");
+
+            SharedBufferInterface()
+            {
+                Interface<SharedBufferInterface>::Register(this);
+            }
+
+            virtual ~SharedBufferInterface()
+            {
+                Interface<SharedBufferInterface>::Unregister(this);
+            }
+
+            static SharedBufferInterface* Get()
+            {
+                return Interface<SharedBufferInterface>::Get();
+            }
+
+            //! Returns the buffer that is used for all skinned mesh outputs
+            virtual Data::Instance<RPI::Buffer> GetBuffer() = 0;
+
+            //! If the allocation succeeds, returns a ref-counted pointer to a VirtualAddress which will be automatically freed if the ref-count drops to zero
+            //! If the allocation fails, returns nullptr
+            virtual AZStd::intrusive_ptr<SharedBufferAllocation> Allocate(size_t byteCount) = 0;
+
+            //! Mark the memory as available and queue garbage collection to recycle it later (see RHI::Allocator::DeAllocate)
+            //! After garbage collection is done signal handlers that memory has been freed
+            virtual void DeAllocate(RHI::VirtualAddress allocation) = 0;
+
+            //! Same as DeAllocate, but the signal after garbage collection is ignored
+            //! If multiple allocations succeeded before one failed, use this to release the successful allocations
+            //! without triggering new events indicating that new memory has been freed
+            virtual void DeAllocateNoSignal(RHI::VirtualAddress allocation) = 0;
+
+            //! Update buffer's content with sourceData at an offset of bufferByteOffset
+            virtual bool UpdateData(const void* sourceData, uint64_t sourceDataSizeInBytes, uint64_t bufferByteOffset = 0) = 0;
+
+            // Note that you have to delete these for safety reasons, you will trip a static_assert if you do not
+            AZ_DISABLE_COPY_MOVE(SharedBufferInterface);
+        };
+
+        class SharedBufferNotifications
+            : public AZ::EBusTraits
+        {
+        public:
+            static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
+
+            //! This event will fire if memory is freed up, so a listener can wait for there to be free space
+            //! and attempt to allocate memory again if it failed initially
+            virtual void OnSharedBufferMemoryAvailable() = 0;
+        };
+        using SharedBufferNotificationBus = AZ::EBus<SharedBufferNotifications>;
+
+        //======================================================================
+        //! An intrusive_ptr wrapper around an RHI::Allocation that will automatically free
+        //!  the memory from the SharedBuffer when the ref count drops to zero.
+        //! Allocated memory will be cleared using the underlying allocator system and
+        //!  indirectly the garbage collection.
+        //! Since the garbage collection is ran with delay of 3 frames due to CPU-GPU
+        //!  latency, this might result in over allocation at reset / back from game mode.
+        //======================================================================
+        class SharedBufferAllocation
+            : public AZStd::intrusive_base
+        {
+        public:
+            AZ_CLASS_ALLOCATOR(SharedBufferAllocation, AZ::SystemAllocator, 0);
+            explicit SharedBufferAllocation(RHI::VirtualAddress virtualAddress)
+                : m_virtualAddress(virtualAddress)
+            {}
+
+            ~SharedBufferAllocation()
+            {
+                if (!m_suppressSignalOnDeallocate)
+                {
+                    SharedBufferInterface::Get()->DeAllocate(m_virtualAddress);
+                }
+                else
+                {
+                    SharedBufferInterface::Get()->DeAllocateNoSignal(m_virtualAddress);
+                }
+            }
+
+            //! If this function is called, the SharedBuffer will not signal when the memory is freed
+            void SuppressSignalOnDeallocate() { m_suppressSignalOnDeallocate = true; }
+            RHI::VirtualAddress GetVirtualAddress() const { return m_virtualAddress; }
+        private:
+            RHI::VirtualAddress m_virtualAddress;
+            bool m_suppressSignalOnDeallocate = false;
+        };
+    } // namespace Meshlets
+} // namespace AZ

+ 50 - 0
Gems/Meshlets/Code/Source/MeshletsEditorModule.cpp

@@ -0,0 +1,50 @@
+
+/*
+* Modifications Copyright (c) Contributors to the Open 3D Engine Project.
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+*
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+#include <MeshletsModuleInterface.h>
+#include <MeshletsEditorSystemComponent.h>
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        class MeshletsEditorModule
+            : public MeshletsModuleInterface
+        {
+        public:
+            AZ_RTTI(MeshletsEditorModule, "{19bbf909-a4fc-48ec-915a-316046feb2f9}", MeshletsModuleInterface);
+            AZ_CLASS_ALLOCATOR(MeshletsEditorModule, AZ::SystemAllocator, 0);
+
+            MeshletsEditorModule()
+            {
+                // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
+                // Add ALL components descriptors associated with this gem to m_descriptors.
+                // This will associate the AzTypeInfo information for the components with the the SerializeContext, BehaviorContext and EditContext.
+                // This happens through the [MyComponent]::Reflect() function.
+                m_descriptors.insert(m_descriptors.end(), {
+                    MeshletsEditorSystemComponent::CreateDescriptor(),
+                });
+            }
+
+            /**
+             * Add required SystemComponents to the SystemEntity.
+             * Non-SystemComponents should not be added here
+             */
+            AZ::ComponentTypeList GetRequiredSystemComponents() const override
+            {
+                return AZ::ComponentTypeList {
+                    azrtti_typeid<MeshletsEditorSystemComponent>(),
+                };
+            }
+        };
+    } // namespace Meshlets
+} // namespace AZ
+
+
+AZ_DECLARE_MODULE_CLASS(Gem_Meshlets, AZ::Meshlets::MeshletsEditorModule)

+ 66 - 0
Gems/Meshlets/Code/Source/MeshletsEditorSystemComponent.cpp

@@ -0,0 +1,66 @@
+
+/*
+* 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 <AzCore/Serialization/SerializeContext.h>
+#include <MeshletsEditorSystemComponent.h>
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        void MeshletsEditorSystemComponent::Reflect(AZ::ReflectContext* context)
+        {
+            if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+            {
+                serializeContext->Class<MeshletsEditorSystemComponent, MeshletsSystemComponent>()
+                    ->Version(0);
+            }
+        }
+
+        MeshletsEditorSystemComponent::MeshletsEditorSystemComponent() = default;
+
+        MeshletsEditorSystemComponent::~MeshletsEditorSystemComponent() = default;
+
+        void MeshletsEditorSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+        {
+            BaseSystemComponent::GetProvidedServices(provided);
+            provided.push_back(AZ_CRC_CE("MeshletsEditorService"));
+        }
+
+        void MeshletsEditorSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+        {
+            BaseSystemComponent::GetIncompatibleServices(incompatible);
+            incompatible.push_back(AZ_CRC_CE("MeshletsEditorService"));
+        }
+
+        void MeshletsEditorSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
+        {
+            BaseSystemComponent::GetRequiredServices(required);
+        }
+
+        void MeshletsEditorSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
+        {
+            BaseSystemComponent::GetDependentServices(dependent);
+        }
+
+        void MeshletsEditorSystemComponent::Activate()
+        {
+            MeshletsSystemComponent::Activate();
+            AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
+        }
+
+        void MeshletsEditorSystemComponent::Deactivate()
+        {
+            AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
+            MeshletsSystemComponent::Deactivate();
+        }
+
+    } // namespace Meshlets
+} // namespace AZ
+

+ 45 - 0
Gems/Meshlets/Code/Source/MeshletsEditorSystemComponent.h

@@ -0,0 +1,45 @@
+
+/*
+* Copyright (c) Contributors to the Open 3D Engine Project.
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+*
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+#pragma once
+
+#include <MeshletsSystemComponent.h>
+
+#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        /// System component for Meshlets editor
+        class MeshletsEditorSystemComponent
+            : public MeshletsSystemComponent
+            , private AzToolsFramework::EditorEvents::Bus::Handler
+        {
+            using BaseSystemComponent = MeshletsSystemComponent;
+        public:
+            AZ_COMPONENT(MeshletsEditorSystemComponent, "{00c6370a-4390-41e4-aae3-a8425b2e776f}", BaseSystemComponent);
+            static void Reflect(AZ::ReflectContext* context);
+
+            MeshletsEditorSystemComponent();
+            ~MeshletsEditorSystemComponent();
+
+        private:
+            static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+            static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+            static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+            static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
+
+            // AZ::Component
+            void Activate() override;
+            void Deactivate() override;
+        };
+    } // namespace Meshlets
+} // namespace AZ
+

+ 27 - 0
Gems/Meshlets/Code/Source/MeshletsModule.cpp

@@ -0,0 +1,27 @@
+/*
+* 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 <MeshletsModuleInterface.h>
+#include <MeshletsSystemComponent.h>
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        class MeshletsModule
+            : public MeshletsModuleInterface
+        {
+        public:
+            AZ_RTTI(MeshletsModule, "{19bbf909-a4fc-48ec-915a-316046feb2f9}", MeshletsModuleInterface);
+            AZ_CLASS_ALLOCATOR(MeshletsModule, AZ::SystemAllocator, 0);
+        };
+    } // namespace Meshlets
+} // namespace AZ
+
+AZ_DECLARE_MODULE_CLASS(Gem_Meshlets, AZ::Meshlets::MeshletsModule)

+ 48 - 0
Gems/Meshlets/Code/Source/MeshletsModuleInterface.h

@@ -0,0 +1,48 @@
+
+/*
+* Modifications Copyright (c) Contributors to the Open 3D Engine Project. 
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+* 
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/Module/Module.h>
+#include <MeshletsSystemComponent.h>
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        class MeshletsModuleInterface
+            : public AZ::Module
+        {
+        public:
+            AZ_RTTI(MeshletsModuleInterface, "{78d5f887-59e1-4ffd-b3d5-b1b2b3e94039}", AZ::Module);
+            AZ_CLASS_ALLOCATOR(MeshletsModuleInterface, AZ::SystemAllocator, 0);
+
+            MeshletsModuleInterface()
+            {
+                // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
+                // Add ALL components descriptors associated with this gem to m_descriptors.
+                // This will associate the AzTypeInfo information for the components with the the SerializeContext, BehaviorContext and EditContext.
+                // This happens through the [MyComponent]::Reflect() function.
+                m_descriptors.insert(m_descriptors.end(), {
+                    MeshletsSystemComponent::CreateDescriptor(),
+                    });
+            }
+
+            /**
+             * Add required SystemComponents to the SystemEntity.
+             */
+            AZ::ComponentTypeList GetRequiredSystemComponents() const override
+            {
+                return AZ::ComponentTypeList{
+                    azrtti_typeid<MeshletsSystemComponent>(),
+                };
+            }
+        };
+    } // namespace Meshlets
+} // namespace AZ
+

+ 131 - 0
Gems/Meshlets/Code/Source/MeshletsSystemComponent.cpp

@@ -0,0 +1,131 @@
+
+/*
+* Modifications Copyright (c) Contributors to the Open 3D Engine Project. 
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+* 
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+#include <AzCore/Serialization/SerializeContext.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/EditContextConstants.inl>
+
+#include <Atom/RHI/Factory.h>
+#include <Atom/RPI.Public/RPISystemInterface.h>
+
+#include <Atom/RPI.Public/FeatureProcessorFactory.h>
+
+#include <MeshletsSystemComponent.h>
+#include <MeshletsFeatureProcessor.h>
+#include <MultiDispatchComputePass.h>
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        void MeshletsSystemComponent::Reflect(AZ::ReflectContext* context)
+        {
+            if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
+            {
+                serialize->Class<MeshletsSystemComponent, AZ::Component>()
+                    ->Version(0)
+                    ;
+
+                if (AZ::EditContext* ec = serialize->GetEditContext())
+                {
+                    ec->Class<MeshletsSystemComponent>("Meshlets", "[Description of functionality provided by this System Component]")
+                        ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                            ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System"))
+                            ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
+                        ;
+                }
+            }
+
+            Meshlets::MeshletsFeatureProcessor::Reflect(context);
+        }
+
+        void MeshletsSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+        {
+            provided.push_back(AZ_CRC_CE("MeshletsService"));
+        }
+
+        void MeshletsSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+        {
+            incompatible.push_back(AZ_CRC_CE("MeshletsService"));
+        }
+
+        void MeshletsSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
+        {
+            required.push_back(AZ::RHI::Factory::GetComponentService());
+            required.push_back(AZ_CRC("AssetDatabaseService", 0x3abf5601));
+            required.push_back(AZ_CRC("RPISystem", 0xf2add773));
+        }
+
+        void MeshletsSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
+        {
+        }
+
+        MeshletsSystemComponent::MeshletsSystemComponent()
+        {
+            if (MeshletsInterface::Get() == nullptr)
+            {
+                MeshletsInterface::Register(this);
+            }
+        }
+
+        MeshletsSystemComponent::~MeshletsSystemComponent()
+        {
+            if (MeshletsInterface::Get() == this)
+            {
+                MeshletsInterface::Unregister(this);
+            }
+        }
+
+        void MeshletsSystemComponent::Init()
+        {
+        }
+
+        void MeshletsSystemComponent::LoadPassTemplateMappings()
+        {
+            auto* passSystem = AZ::RPI::PassSystemInterface::Get();
+            AZ_Assert(passSystem, "Meshlets Gem - cannot get the pass system.");
+
+            const char* passTemplatesFile = "Passes/MeshletsPassTemplates.azasset";
+            passSystem->LoadPassTemplateMappings(passTemplatesFile);
+        }
+
+        void MeshletsSystemComponent::Activate()
+        {
+            // Feature processor
+            AZ::RPI::FeatureProcessorFactory::Get()->RegisterFeatureProcessor<Meshlets::MeshletsFeatureProcessor>();
+
+            auto* passSystem = AZ::RPI::PassSystemInterface::Get();
+            AZ_Assert(passSystem, "Cannot get the pass system.");
+
+            // Setup handler for load pass templates mappings
+            m_loadTemplatesHandler = AZ::RPI::PassSystemInterface::OnReadyLoadTemplatesEvent::Handler([this]() { this->LoadPassTemplateMappings(); });
+            passSystem->ConnectEvent(m_loadTemplatesHandler);
+
+            passSystem->AddPassCreator(AZ::Name("MultiDispatchComputePass"), &MultiDispatchComputePass::Create);
+            passSystem->AddPassCreator(AZ::Name("MeshletsRenderPass"), &MeshletsRenderPass::Create);
+
+            MeshletsRequestBus::Handler::BusConnect();
+            AZ::TickBus::Handler::BusConnect();
+        }
+
+        void MeshletsSystemComponent::Deactivate()
+        {
+            AZ::TickBus::Handler::BusDisconnect();
+            MeshletsRequestBus::Handler::BusDisconnect();
+
+            AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor<Meshlets::MeshletsFeatureProcessor>();
+        }
+
+        void MeshletsSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
+        {
+        }
+
+    } // namespace Meshlets
+} // namespace AZ
+

+ 67 - 0
Gems/Meshlets/Code/Source/MeshletsSystemComponent.h

@@ -0,0 +1,67 @@
+
+/*
+* Modifications Copyright (c) Contributors to the Open 3D Engine Project. 
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+* 
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+#pragma once
+
+#include <AzCore/Component/Component.h>
+#include <AzCore/Component/TickBus.h>
+
+#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
+
+#include <Meshlets/MeshletsBus.h>
+
+namespace AZ
+{
+    namespace Meshlets
+    {
+        class MeshletsSystemComponent
+            : public AZ::Component
+            , protected MeshletsRequestBus::Handler
+            , public AZ::TickBus::Handler
+        {
+        public:
+            AZ_COMPONENT(MeshletsSystemComponent, "{0a55656c-08ac-440d-a55c-f7e3c1b91712}");
+
+            static void Reflect(AZ::ReflectContext* context);
+
+            static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+            static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+            static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+            static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
+
+            MeshletsSystemComponent();
+            ~MeshletsSystemComponent();
+
+        protected:
+            ////////////////////////////////////////////////////////////////////////
+            // MeshletsRequestBus interface implementation
+
+            ////////////////////////////////////////////////////////////////////////
+
+            ////////////////////////////////////////////////////////////////////////
+            // AZ::Component interface implementation
+            void Init() override;
+            void Activate() override;
+            void Deactivate() override;
+            ////////////////////////////////////////////////////////////////////////
+
+            ////////////////////////////////////////////////////////////////////////
+            // AZTickBus interface implementation
+            void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
+            ////////////////////////////////////////////////////////////////////////
+
+            //! Loads the pass templates mapping file 
+            void LoadPassTemplateMappings();
+
+            //! Used for loading the pass templates of the hair gem.
+            AZ::RPI::PassSystemInterface::OnReadyLoadTemplatesEvent::Handler m_loadTemplatesHandler;
+        };
+
+    } // namespace Meshlets
+} // namespace AZ

+ 12 - 0
Gems/Meshlets/Code/Tests/MeshletsEditorTest.cpp

@@ -0,0 +1,12 @@
+
+/*
+* Modifications Copyright (c) Contributors to the Open 3D Engine Project.
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+*
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+#include <AzTest/AzTest.h>
+
+AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);

+ 11 - 0
Gems/Meshlets/Code/Tests/MeshletsTest.cpp

@@ -0,0 +1,11 @@
+/*
+* Modifications Copyright (c) Contributors to the Open 3D Engine Project.
+* For complete copyright and license terms please see the LICENSE at the root of this distribution.
+*
+* SPDX-License-Identifier: Apache-2.0 OR MIT
+*
+*/
+
+#include <AzTest/AzTest.h>
+
+AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);

+ 12 - 0
Gems/Meshlets/Code/meshlets_editor_files.cmake

@@ -0,0 +1,12 @@
+#
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+#
+#
+
+set(FILES
+    Source/MeshletsEditorSystemComponent.cpp
+    Source/MeshletsEditorSystemComponent.h
+)

+ 11 - 0
Gems/Meshlets/Code/meshlets_editor_shared_files.cmake

@@ -0,0 +1,11 @@
+#
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+#
+#
+
+set(FILES
+    Source/MeshletsEditorModule.cpp
+)

+ 11 - 0
Gems/Meshlets/Code/meshlets_editor_tests_files.cmake

@@ -0,0 +1,11 @@
+#
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+#
+#
+
+set(FILES
+    Tests/MeshletsEditorTest.cpp
+)

+ 47 - 0
Gems/Meshlets/Code/meshlets_files.cmake

@@ -0,0 +1,47 @@
+#
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+#
+#
+
+set(FILES
+    Include/Meshlets/MeshletsBus.h
+    Source/MeshletsModuleInterface.h
+    Source/MeshletsSystemComponent.cpp
+    Source/MeshletsSystemComponent.h
+
+    ../Assets/Passes/MeshletsPassTemplates.azasset
+    ../Assets/Passes/MeshletsPassRequest.azasset
+    ../Assets/Passes/MeshletsParent.pass
+    ../Assets/Passes/MeshletsCompute.pass
+    ../Assets/Passes/MeshletsRender.pass
+    
+    ../Assets/Shaders/MeshletsCompute.shader
+    ../Assets/Shaders/MeshletsCompute.azsl
+    ../Assets/Shaders/MeshletsDebugRenderShader.shader
+    ../Assets/Shaders/MeshletsDebugRenderShader.azsl
+    ../Assets/Shaders/MeshletsPerObjectRenderSrg.azsli
+    
+    Source/Meshlets/MeshletsRenderPass.h
+    Source/Meshlets/MeshletsRenderPass.cpp
+    Source/Meshlets/MultiDispatchComputePass.h
+    Source/Meshlets/MultiDispatchComputePass.cpp
+    Source/Meshlets/MeshletsData.h
+    Source/Meshlets/MeshletsDispatchItem.h
+    Source/Meshlets/MeshletsDispatchItem.cpp
+    Source/Meshlets/SharedBufferInterface.h
+    Source/Meshlets/MeshletsUtilities.h
+    Source/Meshlets/MeshletsUtilities.cpp
+    Source/Meshlets/SharedBuffer.h
+    Source/Meshlets/SharedBuffer.cpp
+    Source/Meshlets/MeshletsRenderObject.h
+    Source/Meshlets/MeshletsRenderObject.cpp
+    
+    Source/Meshlets/MeshletsFeatureProcessor.h
+    Source/Meshlets/MeshletsFeatureProcessor.cpp
+    
+    Source/Meshlets/MeshletsAssets.h
+    Source/Meshlets/MeshletsAssets.cpp
+)

+ 11 - 0
Gems/Meshlets/Code/meshlets_shared_files.cmake

@@ -0,0 +1,11 @@
+#
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+#
+#
+
+set(FILES
+    Source/MeshletsModule.cpp
+)

+ 11 - 0
Gems/Meshlets/Code/meshlets_tests_files.cmake

@@ -0,0 +1,11 @@
+#
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+#
+#
+
+set(FILES
+    Tests/MeshletsTest.cpp
+)

+ 10 - 0
Gems/Meshlets/External/Lib/meshoptimizer.txt

@@ -0,0 +1,10 @@
+The meshoptimizer library is not included as part of the O3DE.
+When adding and compiling this Gem, compile the meshoptimizer 
+library and add it as part of Meshlets.Static project (for example, in 
+Visual Studio you simply add the library file to the project).
+Once this is done, the Gem should compile and link properly to allow you 
+to run the ASV sample 'Meshlets' (created with the MeshletsExampleComponent)
+
+
+The meshoptimizer library and source can be found in the following Github link:
+https://github.com/zeux/meshoptimizer

+ 1050 - 0
Gems/Meshlets/External/meshoptimizer.h

@@ -0,0 +1,1050 @@
+/**
+ * meshoptimizer - version 0.17
+ *
+ * Copyright (C) 2016-2021, by Arseny Kapoulkine ([email protected])
+ * Report bugs and download new versions at https://github.com/zeux/meshoptimizer
+ *
+ * This library is distributed under the MIT License. See notice at the end of this file.
+ */
+#pragma once
+
+#include <assert.h>
+#include <stddef.h>
+
+/* Version macro; major * 1000 + minor * 10 + patch */
+#define MESHOPTIMIZER_VERSION 170 /* 0.17 */
+
+/* If no API is defined, assume default */
+#ifndef MESHOPTIMIZER_API
+#define MESHOPTIMIZER_API
+#endif
+
+/* Experimental APIs have unstable interface and might have implementation that's not fully tested or optimized */
+#define MESHOPTIMIZER_EXPERIMENTAL MESHOPTIMIZER_API
+
+/* C interface */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Vertex attribute stream, similar to glVertexPointer
+ * Each element takes size bytes, with stride controlling the spacing between successive elements.
+ */
+struct meshopt_Stream
+{
+	const void* data;
+	size_t size;
+	size_t stride;
+};
+
+/**
+ * Generates a vertex remap table from the vertex buffer and an optional index buffer and returns number of unique vertices
+ * As a result, all vertices that are binary equivalent map to the same (new) location, with no gaps in the resulting sequence.
+ * Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer/meshopt_remapIndexBuffer.
+ * Note that binary equivalence considers all vertex_size bytes, including padding which should be zero-initialized.
+ *
+ * destination must contain enough space for the resulting remap table (vertex_count elements)
+ * indices can be NULL if the input is unindexed
+ */
+MESHOPTIMIZER_API size_t meshopt_generateVertexRemap(unsigned int* destination, const unsigned int* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size);
+
+/**
+ * Generates a vertex remap table from multiple vertex streams and an optional index buffer and returns number of unique vertices
+ * As a result, all vertices that are binary equivalent map to the same (new) location, with no gaps in the resulting sequence.
+ * Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer/meshopt_remapIndexBuffer.
+ * To remap vertex buffers, you will need to call meshopt_remapVertexBuffer for each vertex stream.
+ * Note that binary equivalence considers all size bytes in each stream, including padding which should be zero-initialized.
+ *
+ * destination must contain enough space for the resulting remap table (vertex_count elements)
+ * indices can be NULL if the input is unindexed
+ */
+MESHOPTIMIZER_API size_t meshopt_generateVertexRemapMulti(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const struct meshopt_Stream* streams, size_t stream_count);
+
+/**
+ * Generates vertex buffer from the source vertex buffer and remap table generated by meshopt_generateVertexRemap
+ *
+ * destination must contain enough space for the resulting vertex buffer (unique_vertex_count elements, returned by meshopt_generateVertexRemap)
+ * vertex_count should be the initial vertex count and not the value returned by meshopt_generateVertexRemap
+ */
+MESHOPTIMIZER_API void meshopt_remapVertexBuffer(void* destination, const void* vertices, size_t vertex_count, size_t vertex_size, const unsigned int* remap);
+
+/**
+ * Generate index buffer from the source index buffer and remap table generated by meshopt_generateVertexRemap
+ *
+ * destination must contain enough space for the resulting index buffer (index_count elements)
+ * indices can be NULL if the input is unindexed
+ */
+MESHOPTIMIZER_API void meshopt_remapIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const unsigned int* remap);
+
+/**
+ * Generate index buffer that can be used for more efficient rendering when only a subset of the vertex attributes is necessary
+ * All vertices that are binary equivalent (wrt first vertex_size bytes) map to the first vertex in the original vertex buffer.
+ * This makes it possible to use the index buffer for Z pre-pass or shadowmap rendering, while using the original index buffer for regular rendering.
+ * Note that binary equivalence considers all vertex_size bytes, including padding which should be zero-initialized.
+ *
+ * destination must contain enough space for the resulting index buffer (index_count elements)
+ */
+MESHOPTIMIZER_API void meshopt_generateShadowIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size, size_t vertex_stride);
+
+/**
+ * Generate index buffer that can be used for more efficient rendering when only a subset of the vertex attributes is necessary
+ * All vertices that are binary equivalent (wrt specified streams) map to the first vertex in the original vertex buffer.
+ * This makes it possible to use the index buffer for Z pre-pass or shadowmap rendering, while using the original index buffer for regular rendering.
+ * Note that binary equivalence considers all size bytes in each stream, including padding which should be zero-initialized.
+ *
+ * destination must contain enough space for the resulting index buffer (index_count elements)
+ */
+MESHOPTIMIZER_API void meshopt_generateShadowIndexBufferMulti(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const struct meshopt_Stream* streams, size_t stream_count);
+
+/**
+ * Generate index buffer that can be used as a geometry shader input with triangle adjacency topology
+ * Each triangle is converted into a 6-vertex patch with the following layout:
+ * - 0, 2, 4: original triangle vertices
+ * - 1, 3, 5: vertices adjacent to edges 02, 24 and 40
+ * The resulting patch can be rendered with geometry shaders using e.g. VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY.
+ * This can be used to implement algorithms like silhouette detection/expansion and other forms of GS-driven rendering.
+ *
+ * destination must contain enough space for the resulting index buffer (index_count*2 elements)
+ * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
+ */
+MESHOPTIMIZER_API void meshopt_generateAdjacencyIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+
+/**
+ * Generate index buffer that can be used for PN-AEN tessellation with crack-free displacement
+ * Each triangle is converted into a 12-vertex patch with the following layout:
+ * - 0, 1, 2: original triangle vertices
+ * - 3, 4: opposing edge for edge 0, 1
+ * - 5, 6: opposing edge for edge 1, 2
+ * - 7, 8: opposing edge for edge 2, 0
+ * - 9, 10, 11: dominant vertices for corners 0, 1, 2
+ * The resulting patch can be rendered with hardware tessellation using PN-AEN and displacement mapping.
+ * See "Tessellation on Any Budget" (John McDonald, GDC 2011) for implementation details.
+ *
+ * destination must contain enough space for the resulting index buffer (index_count*4 elements)
+ * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
+ */
+MESHOPTIMIZER_API void meshopt_generateTessellationIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+
+/**
+ * Vertex transform cache optimizer
+ * Reorders indices to reduce the number of GPU vertex shader invocations
+ * If index buffer contains multiple ranges for multiple draw calls, this functions needs to be called on each range individually.
+ *
+ * destination must contain enough space for the resulting index buffer (index_count elements)
+ */
+MESHOPTIMIZER_API void meshopt_optimizeVertexCache(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count);
+
+/**
+ * Vertex transform cache optimizer for strip-like caches
+ * Produces inferior results to meshopt_optimizeVertexCache from the GPU vertex cache perspective
+ * However, the resulting index order is more optimal if the goal is to reduce the triangle strip length or improve compression efficiency
+ *
+ * destination must contain enough space for the resulting index buffer (index_count elements)
+ */
+MESHOPTIMIZER_API void meshopt_optimizeVertexCacheStrip(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count);
+
+/**
+ * Vertex transform cache optimizer for FIFO caches
+ * Reorders indices to reduce the number of GPU vertex shader invocations
+ * Generally takes ~3x less time to optimize meshes but produces inferior results compared to meshopt_optimizeVertexCache
+ * If index buffer contains multiple ranges for multiple draw calls, this functions needs to be called on each range individually.
+ *
+ * destination must contain enough space for the resulting index buffer (index_count elements)
+ * cache_size should be less than the actual GPU cache size to avoid cache thrashing
+ */
+MESHOPTIMIZER_API void meshopt_optimizeVertexCacheFifo(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int cache_size);
+
+/**
+ * Overdraw optimizer
+ * Reorders indices to reduce the number of GPU vertex shader invocations and the pixel overdraw
+ * If index buffer contains multiple ranges for multiple draw calls, this functions needs to be called on each range individually.
+ *
+ * destination must contain enough space for the resulting index buffer (index_count elements)
+ * indices must contain index data that is the result of meshopt_optimizeVertexCache (*not* the original mesh indices!)
+ * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
+ * threshold indicates how much the overdraw optimizer can degrade vertex cache efficiency (1.05 = up to 5%) to reduce overdraw more efficiently
+ */
+MESHOPTIMIZER_API void meshopt_optimizeOverdraw(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float threshold);
+
+/**
+ * Vertex fetch cache optimizer
+ * Reorders vertices and changes indices to reduce the amount of GPU memory fetches during vertex processing
+ * Returns the number of unique vertices, which is the same as input vertex count unless some vertices are unused
+ * This functions works for a single vertex stream; for multiple vertex streams, use meshopt_optimizeVertexFetchRemap + meshopt_remapVertexBuffer for each stream.
+ *
+ * destination must contain enough space for the resulting vertex buffer (vertex_count elements)
+ * indices is used both as an input and as an output index buffer
+ */
+MESHOPTIMIZER_API size_t meshopt_optimizeVertexFetch(void* destination, unsigned int* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size);
+
+/**
+ * Vertex fetch cache optimizer
+ * Generates vertex remap to reduce the amount of GPU memory fetches during vertex processing
+ * Returns the number of unique vertices, which is the same as input vertex count unless some vertices are unused
+ * The resulting remap table should be used to reorder vertex/index buffers using meshopt_remapVertexBuffer/meshopt_remapIndexBuffer
+ *
+ * destination must contain enough space for the resulting remap table (vertex_count elements)
+ */
+MESHOPTIMIZER_API size_t meshopt_optimizeVertexFetchRemap(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count);
+
+/**
+ * Index buffer encoder
+ * Encodes index data into an array of bytes that is generally much smaller (<1.5 bytes/triangle) and compresses better (<1 bytes/triangle) compared to original.
+ * Input index buffer must represent a triangle list.
+ * Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space
+ * For maximum efficiency the index buffer being encoded has to be optimized for vertex cache and vertex fetch first.
+ *
+ * buffer must contain enough space for the encoded index buffer (use meshopt_encodeIndexBufferBound to compute worst case size)
+ */
+MESHOPTIMIZER_API size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_size, const unsigned int* indices, size_t index_count);
+MESHOPTIMIZER_API size_t meshopt_encodeIndexBufferBound(size_t index_count, size_t vertex_count);
+
+/**
+ * Set index encoder format version
+ * version must specify the data format version to encode; valid values are 0 (decodable by all library versions) and 1 (decodable by 0.14+)
+ */
+MESHOPTIMIZER_API void meshopt_encodeIndexVersion(int version);
+
+/**
+ * Index buffer decoder
+ * Decodes index data from an array of bytes generated by meshopt_encodeIndexBuffer
+ * Returns 0 if decoding was successful, and an error code otherwise
+ * The decoder is safe to use for untrusted input, but it may produce garbage data (e.g. out of range indices).
+ *
+ * destination must contain enough space for the resulting index buffer (index_count elements)
+ */
+MESHOPTIMIZER_API int meshopt_decodeIndexBuffer(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size);
+
+/**
+ * Index sequence encoder
+ * Encodes index sequence into an array of bytes that is generally smaller and compresses better compared to original.
+ * Input index sequence can represent arbitrary topology; for triangle lists meshopt_encodeIndexBuffer is likely to be better.
+ * Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space
+ *
+ * buffer must contain enough space for the encoded index sequence (use meshopt_encodeIndexSequenceBound to compute worst case size)
+ */
+MESHOPTIMIZER_API size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const unsigned int* indices, size_t index_count);
+MESHOPTIMIZER_API size_t meshopt_encodeIndexSequenceBound(size_t index_count, size_t vertex_count);
+
+/**
+ * Index sequence decoder
+ * Decodes index data from an array of bytes generated by meshopt_encodeIndexSequence
+ * Returns 0 if decoding was successful, and an error code otherwise
+ * The decoder is safe to use for untrusted input, but it may produce garbage data (e.g. out of range indices).
+ *
+ * destination must contain enough space for the resulting index sequence (index_count elements)
+ */
+MESHOPTIMIZER_API int meshopt_decodeIndexSequence(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size);
+
+/**
+ * Vertex buffer encoder
+ * Encodes vertex data into an array of bytes that is generally smaller and compresses better compared to original.
+ * Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space
+ * This function works for a single vertex stream; for multiple vertex streams, call meshopt_encodeVertexBuffer for each stream.
+ * Note that all vertex_size bytes of each vertex are encoded verbatim, including padding which should be zero-initialized.
+ *
+ * buffer must contain enough space for the encoded vertex buffer (use meshopt_encodeVertexBufferBound to compute worst case size)
+ */
+MESHOPTIMIZER_API size_t meshopt_encodeVertexBuffer(unsigned char* buffer, size_t buffer_size, const void* vertices, size_t vertex_count, size_t vertex_size);
+MESHOPTIMIZER_API size_t meshopt_encodeVertexBufferBound(size_t vertex_count, size_t vertex_size);
+
+/**
+ * Set vertex encoder format version
+ * version must specify the data format version to encode; valid values are 0 (decodable by all library versions)
+ */
+MESHOPTIMIZER_API void meshopt_encodeVertexVersion(int version);
+
+/**
+ * Vertex buffer decoder
+ * Decodes vertex data from an array of bytes generated by meshopt_encodeVertexBuffer
+ * Returns 0 if decoding was successful, and an error code otherwise
+ * The decoder is safe to use for untrusted input, but it may produce garbage data.
+ *
+ * destination must contain enough space for the resulting vertex buffer (vertex_count * vertex_size bytes)
+ */
+MESHOPTIMIZER_API int meshopt_decodeVertexBuffer(void* destination, size_t vertex_count, size_t vertex_size, const unsigned char* buffer, size_t buffer_size);
+
+/**
+ * Vertex buffer filters
+ * These functions can be used to filter output of meshopt_decodeVertexBuffer in-place.
+ *
+ * meshopt_decodeFilterOct decodes octahedral encoding of a unit vector with K-bit (K <= 16) signed X/Y as an input; Z must store 1.0f.
+ * Each component is stored as an 8-bit or 16-bit normalized integer; stride must be equal to 4 or 8. W is preserved as is.
+ *
+ * meshopt_decodeFilterQuat decodes 3-component quaternion encoding with K-bit (4 <= K <= 16) component encoding and a 2-bit component index indicating which component to reconstruct.
+ * Each component is stored as an 16-bit integer; stride must be equal to 8.
+ *
+ * meshopt_decodeFilterExp decodes exponential encoding of floating-point data with 8-bit exponent and 24-bit integer mantissa as 2^E*M.
+ * Each 32-bit component is decoded in isolation; stride must be divisible by 4.
+ */
+MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterOct(void* buffer, size_t count, size_t stride);
+MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterQuat(void* buffer, size_t count, size_t stride);
+MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterExp(void* buffer, size_t count, size_t stride);
+
+/**
+ * Vertex buffer filter encoders
+ * These functions can be used to encode data in a format that meshopt_decodeFilter can decode
+ *
+ * meshopt_encodeFilterOct encodes unit vectors with K-bit (K <= 16) signed X/Y as an output.
+ * Each component is stored as an 8-bit or 16-bit normalized integer; stride must be equal to 4 or 8. W is preserved as is.
+ * Input data must contain 4 floats for every vector (count*4 total).
+ *
+ * meshopt_encodeFilterQuat encodes unit quaternions with K-bit (4 <= K <= 16) component encoding.
+ * Each component is stored as an 16-bit integer; stride must be equal to 8.
+ * Input data must contain 4 floats for every quaternion (count*4 total).
+ *
+ * meshopt_encodeFilterExp encodes arbitrary (finite) floating-point data with 8-bit exponent and K-bit integer mantissa (1 <= K <= 24).
+ * Mantissa is shared between all components of a given vector as defined by stride; stride must be divisible by 4.
+ * Input data must contain stride/4 floats for every vector (count*stride/4 total).
+ * When individual (scalar) encoding is desired, simply pass stride=4 and adjust count accordingly.
+ */
+MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterOct(void* destination, size_t count, size_t stride, int bits, const float* data);
+MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterQuat(void* destination, size_t count, size_t stride, int bits, const float* data);
+MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterExp(void* destination, size_t count, size_t stride, int bits, const float* data);
+
+/**
+ * Experimental: Mesh simplifier
+ * Reduces the number of triangles in the mesh, attempting to preserve mesh appearance as much as possible
+ * The algorithm tries to preserve mesh topology and can stop short of the target goal based on topology constraints or target error.
+ * If not all attributes from the input mesh are required, it's recommended to reindex the mesh using meshopt_generateShadowIndexBuffer prior to simplification.
+ * Returns the number of indices after simplification, with destination containing new index data
+ * The resulting index buffer references vertices from the original vertex buffer.
+ * If the original vertex data isn't required, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.
+ *
+ * destination must contain enough space for the target index buffer, worst case is index_count elements (*not* target_index_count)!
+ * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
+ * target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation
+ * result_error can be NULL; when it's not NULL, it will contain the resulting (relative) error after simplification
+ */
+MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float* result_error);
+
+/**
+ * Experimental: Mesh simplifier (sloppy)
+ * Reduces the number of triangles in the mesh, sacrificing mesh appearance for simplification performance
+ * The algorithm doesn't preserve mesh topology but can stop short of the target goal based on target error.
+ * Returns the number of indices after simplification, with destination containing new index data
+ * The resulting index buffer references vertices from the original vertex buffer.
+ * If the original vertex data isn't required, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.
+ *
+ * destination must contain enough space for the target index buffer, worst case is index_count elements (*not* target_index_count)!
+ * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
+ * target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation
+ * result_error can be NULL; when it's not NULL, it will contain the resulting (relative) error after simplification
+ */
+MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float* result_error);
+
+/**
+ * Experimental: Point cloud simplifier
+ * Reduces the number of points in the cloud to reach the given target
+ * Returns the number of points after simplification, with destination containing new index data
+ * The resulting index buffer references vertices from the original vertex buffer.
+ * If the original vertex data isn't required, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.
+ *
+ * destination must contain enough space for the target index buffer (target_vertex_count elements)
+ * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
+ */
+MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyPoints(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_vertex_count);
+
+/**
+ * Experimental: Returns the error scaling factor used by the simplifier to convert between absolute and relative extents
+ * 
+ * Absolute error must be *divided* by the scaling factor before passing it to meshopt_simplify as target_error
+ * Relative error returned by meshopt_simplify via result_error must be *multiplied* by the scaling factor to get absolute error.
+ */
+MESHOPTIMIZER_EXPERIMENTAL float meshopt_simplifyScale(const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+
+/**
+ * Mesh stripifier
+ * Converts a previously vertex cache optimized triangle list to triangle strip, stitching strips using restart index or degenerate triangles
+ * Returns the number of indices in the resulting strip, with destination containing new index data
+ * For maximum efficiency the index buffer being converted has to be optimized for vertex cache first.
+ * Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance.
+ *
+ * destination must contain enough space for the target index buffer, worst case can be computed with meshopt_stripifyBound
+ * restart_index should be 0xffff or 0xffffffff depending on index size, or 0 to use degenerate triangles
+ */
+MESHOPTIMIZER_API size_t meshopt_stripify(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int restart_index);
+MESHOPTIMIZER_API size_t meshopt_stripifyBound(size_t index_count);
+
+/**
+ * Mesh unstripifier
+ * Converts a triangle strip to a triangle list
+ * Returns the number of indices in the resulting list, with destination containing new index data
+ *
+ * destination must contain enough space for the target index buffer, worst case can be computed with meshopt_unstripifyBound
+ */
+MESHOPTIMIZER_API size_t meshopt_unstripify(unsigned int* destination, const unsigned int* indices, size_t index_count, unsigned int restart_index);
+MESHOPTIMIZER_API size_t meshopt_unstripifyBound(size_t index_count);
+
+struct meshopt_VertexCacheStatistics
+{
+	unsigned int vertices_transformed;
+	unsigned int warps_executed;
+	float acmr; /* transformed vertices / triangle count; best case 0.5, worst case 3.0, optimum depends on topology */
+	float atvr; /* transformed vertices / vertex count; best case 1.0, worst case 6.0, optimum is 1.0 (each vertex is transformed once) */
+};
+
+/**
+ * Vertex transform cache analyzer
+ * Returns cache hit statistics using a simplified FIFO model
+ * Results may not match actual GPU performance
+ */
+MESHOPTIMIZER_API struct meshopt_VertexCacheStatistics meshopt_analyzeVertexCache(const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int cache_size, unsigned int warp_size, unsigned int primgroup_size);
+
+struct meshopt_OverdrawStatistics
+{
+	unsigned int pixels_covered;
+	unsigned int pixels_shaded;
+	float overdraw; /* shaded pixels / covered pixels; best case 1.0 */
+};
+
+/**
+ * Overdraw analyzer
+ * Returns overdraw statistics using a software rasterizer
+ * Results may not match actual GPU performance
+ *
+ * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
+ */
+MESHOPTIMIZER_API struct meshopt_OverdrawStatistics meshopt_analyzeOverdraw(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+
+struct meshopt_VertexFetchStatistics
+{
+	unsigned int bytes_fetched;
+	float overfetch; /* fetched bytes / vertex buffer size; best case 1.0 (each byte is fetched once) */
+};
+
+/**
+ * Vertex fetch cache analyzer
+ * Returns cache hit statistics using a simplified direct mapped model
+ * Results may not match actual GPU performance
+ */
+MESHOPTIMIZER_API struct meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch(const unsigned int* indices, size_t index_count, size_t vertex_count, size_t vertex_size);
+
+struct meshopt_Meshlet
+{
+	/* offsets within meshlet_vertices and meshlet_triangles arrays with meshlet data */
+	unsigned int vertex_offset;
+	unsigned int triangle_offset;
+
+	/* number of vertices and triangles used in the meshlet; data is stored in consecutive range defined by offset and count */
+	unsigned int vertex_count;
+	unsigned int triangle_count;
+};
+
+/**
+ * Meshlet builder
+ * Splits the mesh into a set of meshlets where each meshlet has a micro index buffer indexing into meshlet vertices that refer to the original vertex buffer
+ * The resulting data can be used to render meshes using NVidia programmable mesh shading pipeline, or in other cluster-based renderers.
+ * When using buildMeshlets, vertex positions need to be provided to minimize the size of the resulting clusters.
+ * When using buildMeshletsScan, for maximum efficiency the index buffer being converted has to be optimized for vertex cache first.
+ *
+ * meshlets must contain enough space for all meshlets, worst case size can be computed with meshopt_buildMeshletsBound
+ * meshlet_vertices must contain enough space for all meshlets, worst case size is equal to max_meshlets * max_vertices
+ * meshlet_triangles must contain enough space for all meshlets, worst case size is equal to max_meshlets * max_triangles * 3
+ * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
+ * max_vertices and max_triangles must not exceed implementation limits (max_vertices <= 255 - not 256!, max_triangles <= 512)
+ * cone_weight should be set to 0 when cone culling is not used, and a value between 0 and 1 otherwise to balance between cluster size and cone culling efficiency
+ */
+MESHOPTIMIZER_API size_t meshopt_buildMeshlets(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t max_triangles, float cone_weight);
+MESHOPTIMIZER_API size_t meshopt_buildMeshletsScan(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles);
+MESHOPTIMIZER_API size_t meshopt_buildMeshletsBound(size_t index_count, size_t max_vertices, size_t max_triangles);
+
+struct meshopt_Bounds
+{
+	/* bounding sphere, useful for frustum and occlusion culling */
+	float center[3];
+	float radius;
+
+	/* normal cone, useful for backface culling */
+	float cone_apex[3];
+	float cone_axis[3];
+	float cone_cutoff; /* = cos(angle/2) */
+
+	/* normal cone axis and cutoff, stored in 8-bit SNORM format; decode using x/127.0 */
+	signed char cone_axis_s8[3];
+	signed char cone_cutoff_s8;
+};
+
+/**
+ * Cluster bounds generator
+ * Creates bounding volumes that can be used for frustum, backface and occlusion culling.
+ *
+ * For backface culling with orthographic projection, use the following formula to reject backfacing clusters:
+ *   dot(view, cone_axis) >= cone_cutoff
+ *
+ * For perspective projection, you can the formula that needs cone apex in addition to axis & cutoff:
+ *   dot(normalize(cone_apex - camera_position), cone_axis) >= cone_cutoff
+ *
+ * Alternatively, you can use the formula that doesn't need cone apex and uses bounding sphere instead:
+ *   dot(normalize(center - camera_position), cone_axis) >= cone_cutoff + radius / length(center - camera_position)
+ * or an equivalent formula that doesn't have a singularity at center = camera_position:
+ *   dot(center - camera_position, cone_axis) >= cone_cutoff * length(center - camera_position) + radius
+ *
+ * The formula that uses the apex is slightly more accurate but needs the apex; if you are already using bounding sphere
+ * to do frustum/occlusion culling, the formula that doesn't use the apex may be preferable.
+ *
+ * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
+ * index_count/3 should be less than or equal to 512 (the function assumes clusters of limited size)
+ */
+MESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeClusterBounds(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+MESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeMeshletBounds(const unsigned int* meshlet_vertices, const unsigned char* meshlet_triangles, size_t triangle_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+
+/**
+ * Experimental: Spatial sorter
+ * Generates a remap table that can be used to reorder points for spatial locality.
+ * Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer.
+ *
+ * destination must contain enough space for the resulting remap table (vertex_count elements)
+ */
+MESHOPTIMIZER_EXPERIMENTAL void meshopt_spatialSortRemap(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+
+/**
+ * Experimental: Spatial sorter
+ * Reorders triangles for spatial locality, and generates a new index buffer. The resulting index buffer can be used with other functions like optimizeVertexCache.
+ *
+ * destination must contain enough space for the resulting index buffer (index_count elements)
+ * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
+ */
+MESHOPTIMIZER_EXPERIMENTAL void meshopt_spatialSortTriangles(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+
+/**
+ * Set allocation callbacks
+ * These callbacks will be used instead of the default operator new/operator delete for all temporary allocations in the library.
+ * Note that all algorithms only allocate memory for temporary use.
+ * allocate/deallocate are always called in a stack-like order - last pointer to be allocated is deallocated first.
+ */
+MESHOPTIMIZER_API void meshopt_setAllocator(void* (*allocate)(size_t), void (*deallocate)(void*));
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+/* Quantization into commonly supported data formats */
+#ifdef __cplusplus
+/**
+ * Quantize a float in [0..1] range into an N-bit fixed point unorm value
+ * Assumes reconstruction function (q / (2^N-1)), which is the case for fixed-function normalized fixed point conversion
+ * Maximum reconstruction error: 1/2^(N+1)
+ */
+inline int meshopt_quantizeUnorm(float v, int N);
+
+/**
+ * Quantize a float in [-1..1] range into an N-bit fixed point snorm value
+ * Assumes reconstruction function (q / (2^(N-1)-1)), which is the case for fixed-function normalized fixed point conversion (except early OpenGL versions)
+ * Maximum reconstruction error: 1/2^N
+ */
+inline int meshopt_quantizeSnorm(float v, int N);
+
+/**
+ * Quantize a float into half-precision floating point value
+ * Generates +-inf for overflow, preserves NaN, flushes denormals to zero, rounds to nearest
+ * Representable magnitude range: [6e-5; 65504]
+ * Maximum relative reconstruction error: 5e-4
+ */
+inline unsigned short meshopt_quantizeHalf(float v);
+
+/**
+ * Quantize a float into a floating point value with a limited number of significant mantissa bits
+ * Generates +-inf for overflow, preserves NaN, flushes denormals to zero, rounds to nearest
+ * Assumes N is in a valid mantissa precision range, which is 1..23
+ */
+inline float meshopt_quantizeFloat(float v, int N);
+#endif
+
+/**
+ * C++ template interface
+ *
+ * These functions mirror the C interface the library provides, providing template-based overloads so that
+ * the caller can use an arbitrary type for the index data, both for input and output.
+ * When the supplied type is the same size as that of unsigned int, the wrappers are zero-cost; when it's not,
+ * the wrappers end up allocating memory and copying index data to convert from one type to another.
+ */
+#if defined(__cplusplus) && !defined(MESHOPTIMIZER_NO_WRAPPERS)
+template <typename T>
+inline size_t meshopt_generateVertexRemap(unsigned int* destination, const T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size);
+template <typename T>
+inline size_t meshopt_generateVertexRemapMulti(unsigned int* destination, const T* indices, size_t index_count, size_t vertex_count, const meshopt_Stream* streams, size_t stream_count);
+template <typename T>
+inline void meshopt_remapIndexBuffer(T* destination, const T* indices, size_t index_count, const unsigned int* remap);
+template <typename T>
+inline void meshopt_generateShadowIndexBuffer(T* destination, const T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size, size_t vertex_stride);
+template <typename T>
+inline void meshopt_generateShadowIndexBufferMulti(T* destination, const T* indices, size_t index_count, size_t vertex_count, const meshopt_Stream* streams, size_t stream_count);
+template <typename T>
+inline void meshopt_generateAdjacencyIndexBuffer(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+template <typename T>
+inline void meshopt_generateTessellationIndexBuffer(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+template <typename T>
+inline void meshopt_optimizeVertexCache(T* destination, const T* indices, size_t index_count, size_t vertex_count);
+template <typename T>
+inline void meshopt_optimizeVertexCacheStrip(T* destination, const T* indices, size_t index_count, size_t vertex_count);
+template <typename T>
+inline void meshopt_optimizeVertexCacheFifo(T* destination, const T* indices, size_t index_count, size_t vertex_count, unsigned int cache_size);
+template <typename T>
+inline void meshopt_optimizeOverdraw(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float threshold);
+template <typename T>
+inline size_t meshopt_optimizeVertexFetchRemap(unsigned int* destination, const T* indices, size_t index_count, size_t vertex_count);
+template <typename T>
+inline size_t meshopt_optimizeVertexFetch(void* destination, T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size);
+template <typename T>
+inline size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_size, const T* indices, size_t index_count);
+template <typename T>
+inline int meshopt_decodeIndexBuffer(T* destination, size_t index_count, const unsigned char* buffer, size_t buffer_size);
+template <typename T>
+inline size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const T* indices, size_t index_count);
+template <typename T>
+inline int meshopt_decodeIndexSequence(T* destination, size_t index_count, const unsigned char* buffer, size_t buffer_size);
+template <typename T>
+inline size_t meshopt_simplify(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float* result_error = 0);
+template <typename T>
+inline size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float* result_error = 0);
+template <typename T>
+inline size_t meshopt_stripify(T* destination, const T* indices, size_t index_count, size_t vertex_count, T restart_index);
+template <typename T>
+inline size_t meshopt_unstripify(T* destination, const T* indices, size_t index_count, T restart_index);
+template <typename T>
+inline meshopt_VertexCacheStatistics meshopt_analyzeVertexCache(const T* indices, size_t index_count, size_t vertex_count, unsigned int cache_size, unsigned int warp_size, unsigned int buffer_size);
+template <typename T>
+inline meshopt_OverdrawStatistics meshopt_analyzeOverdraw(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+template <typename T>
+inline meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch(const T* indices, size_t index_count, size_t vertex_count, size_t vertex_size);
+template <typename T>
+inline size_t meshopt_buildMeshlets(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t max_triangles, float cone_weight);
+template <typename T>
+inline size_t meshopt_buildMeshletsScan(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles);
+template <typename T>
+inline meshopt_Bounds meshopt_computeClusterBounds(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+template <typename T>
+inline void meshopt_spatialSortTriangles(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+#endif
+
+/* Inline implementation */
+#ifdef __cplusplus
+inline int meshopt_quantizeUnorm(float v, int N)
+{
+	const float scale = float((1 << N) - 1);
+
+	v = (v >= 0) ? v : 0;
+	v = (v <= 1) ? v : 1;
+
+	return int(v * scale + 0.5f);
+}
+
+inline int meshopt_quantizeSnorm(float v, int N)
+{
+	const float scale = float((1 << (N - 1)) - 1);
+
+	float round = (v >= 0 ? 0.5f : -0.5f);
+
+	v = (v >= -1) ? v : -1;
+	v = (v <= +1) ? v : +1;
+
+	return int(v * scale + round);
+}
+
+inline unsigned short meshopt_quantizeHalf(float v)
+{
+	union { float f; unsigned int ui; } u = {v};
+	unsigned int ui = u.ui;
+
+	int s = (ui >> 16) & 0x8000;
+	int em = ui & 0x7fffffff;
+
+	/* bias exponent and round to nearest; 112 is relative exponent bias (127-15) */
+	int h = (em - (112 << 23) + (1 << 12)) >> 13;
+
+	/* underflow: flush to zero; 113 encodes exponent -14 */
+	h = (em < (113 << 23)) ? 0 : h;
+
+	/* overflow: infinity; 143 encodes exponent 16 */
+	h = (em >= (143 << 23)) ? 0x7c00 : h;
+
+	/* NaN; note that we convert all types of NaN to qNaN */
+	h = (em > (255 << 23)) ? 0x7e00 : h;
+
+	return (unsigned short)(s | h);
+}
+
+inline float meshopt_quantizeFloat(float v, int N)
+{
+	union { float f; unsigned int ui; } u = {v};
+	unsigned int ui = u.ui;
+
+	const int mask = (1 << (23 - N)) - 1;
+	const int round = (1 << (23 - N)) >> 1;
+
+	int e = ui & 0x7f800000;
+	unsigned int rui = (ui + round) & ~mask;
+
+	/* round all numbers except inf/nan; this is important to make sure nan doesn't overflow into -0 */
+	ui = e == 0x7f800000 ? ui : rui;
+
+	/* flush denormals to zero */
+	ui = e == 0 ? 0 : ui;
+
+	u.ui = ui;
+	return u.f;
+}
+#endif
+
+/* Internal implementation helpers */
+#ifdef __cplusplus
+class meshopt_Allocator
+{
+public:
+	template <typename T>
+	struct StorageT
+	{
+		static void* (*allocate)(size_t);
+		static void (*deallocate)(void*);
+	};
+
+	typedef StorageT<void> Storage;
+
+	meshopt_Allocator()
+		: blocks()
+		, count(0)
+	{
+	}
+
+	~meshopt_Allocator()
+	{
+		for (size_t i = count; i > 0; --i)
+			Storage::deallocate(blocks[i - 1]);
+	}
+
+	template <typename T> T* allocate(size_t size)
+	{
+		assert(count < sizeof(blocks) / sizeof(blocks[0]));
+		T* result = static_cast<T*>(Storage::allocate(size > size_t(-1) / sizeof(T) ? size_t(-1) : size * sizeof(T)));
+		blocks[count++] = result;
+		return result;
+	}
+
+private:
+	void* blocks[24];
+	size_t count;
+};
+
+// This makes sure that allocate/deallocate are lazily generated in translation units that need them and are deduplicated by the linker
+template <typename T> void* (*meshopt_Allocator::StorageT<T>::allocate)(size_t) = operator new;
+template <typename T> void (*meshopt_Allocator::StorageT<T>::deallocate)(void*) = operator delete;
+#endif
+
+/* Inline implementation for C++ templated wrappers */
+#if defined(__cplusplus) && !defined(MESHOPTIMIZER_NO_WRAPPERS)
+template <typename T, bool ZeroCopy = sizeof(T) == sizeof(unsigned int)>
+struct meshopt_IndexAdapter;
+
+template <typename T>
+struct meshopt_IndexAdapter<T, false>
+{
+	T* result;
+	unsigned int* data;
+	size_t count;
+
+	meshopt_IndexAdapter(T* result_, const T* input, size_t count_)
+	    : result(result_)
+	    , data(0)
+	    , count(count_)
+	{
+		size_t size = count > size_t(-1) / sizeof(unsigned int) ? size_t(-1) : count * sizeof(unsigned int);
+
+		data = static_cast<unsigned int*>(meshopt_Allocator::Storage::allocate(size));
+
+		if (input)
+		{
+			for (size_t i = 0; i < count; ++i)
+				data[i] = input[i];
+		}
+	}
+
+	~meshopt_IndexAdapter()
+	{
+		if (result)
+		{
+			for (size_t i = 0; i < count; ++i)
+				result[i] = T(data[i]);
+		}
+
+		meshopt_Allocator::Storage::deallocate(data);
+	}
+};
+
+template <typename T>
+struct meshopt_IndexAdapter<T, true>
+{
+	unsigned int* data;
+
+	meshopt_IndexAdapter(T* result, const T* input, size_t)
+	    : data(reinterpret_cast<unsigned int*>(result ? result : const_cast<T*>(input)))
+	{
+	}
+};
+
+template <typename T>
+inline size_t meshopt_generateVertexRemap(unsigned int* destination, const T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size)
+{
+	meshopt_IndexAdapter<T> in(0, indices, indices ? index_count : 0);
+
+	return meshopt_generateVertexRemap(destination, indices ? in.data : 0, index_count, vertices, vertex_count, vertex_size);
+}
+
+template <typename T>
+inline size_t meshopt_generateVertexRemapMulti(unsigned int* destination, const T* indices, size_t index_count, size_t vertex_count, const meshopt_Stream* streams, size_t stream_count)
+{
+	meshopt_IndexAdapter<T> in(0, indices, indices ? index_count : 0);
+
+	return meshopt_generateVertexRemapMulti(destination, indices ? in.data : 0, index_count, vertex_count, streams, stream_count);
+}
+
+template <typename T>
+inline void meshopt_remapIndexBuffer(T* destination, const T* indices, size_t index_count, const unsigned int* remap)
+{
+	meshopt_IndexAdapter<T> in(0, indices, indices ? index_count : 0);
+	meshopt_IndexAdapter<T> out(destination, 0, index_count);
+
+	meshopt_remapIndexBuffer(out.data, indices ? in.data : 0, index_count, remap);
+}
+
+template <typename T>
+inline void meshopt_generateShadowIndexBuffer(T* destination, const T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size, size_t vertex_stride)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+	meshopt_IndexAdapter<T> out(destination, 0, index_count);
+
+	meshopt_generateShadowIndexBuffer(out.data, in.data, index_count, vertices, vertex_count, vertex_size, vertex_stride);
+}
+
+template <typename T>
+inline void meshopt_generateShadowIndexBufferMulti(T* destination, const T* indices, size_t index_count, size_t vertex_count, const meshopt_Stream* streams, size_t stream_count)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+	meshopt_IndexAdapter<T> out(destination, 0, index_count);
+
+	meshopt_generateShadowIndexBufferMulti(out.data, in.data, index_count, vertex_count, streams, stream_count);
+}
+
+template <typename T>
+inline void meshopt_generateAdjacencyIndexBuffer(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+	meshopt_IndexAdapter<T> out(destination, 0, index_count * 2);
+
+	meshopt_generateAdjacencyIndexBuffer(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride);
+}
+
+template <typename T>
+inline void meshopt_generateTessellationIndexBuffer(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+	meshopt_IndexAdapter<T> out(destination, 0, index_count * 4);
+
+	meshopt_generateTessellationIndexBuffer(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride);
+}
+
+template <typename T>
+inline void meshopt_optimizeVertexCache(T* destination, const T* indices, size_t index_count, size_t vertex_count)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+	meshopt_IndexAdapter<T> out(destination, 0, index_count);
+
+	meshopt_optimizeVertexCache(out.data, in.data, index_count, vertex_count);
+}
+
+template <typename T>
+inline void meshopt_optimizeVertexCacheStrip(T* destination, const T* indices, size_t index_count, size_t vertex_count)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+	meshopt_IndexAdapter<T> out(destination, 0, index_count);
+
+	meshopt_optimizeVertexCacheStrip(out.data, in.data, index_count, vertex_count);
+}
+
+template <typename T>
+inline void meshopt_optimizeVertexCacheFifo(T* destination, const T* indices, size_t index_count, size_t vertex_count, unsigned int cache_size)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+	meshopt_IndexAdapter<T> out(destination, 0, index_count);
+
+	meshopt_optimizeVertexCacheFifo(out.data, in.data, index_count, vertex_count, cache_size);
+}
+
+template <typename T>
+inline void meshopt_optimizeOverdraw(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float threshold)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+	meshopt_IndexAdapter<T> out(destination, 0, index_count);
+
+	meshopt_optimizeOverdraw(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, threshold);
+}
+
+template <typename T>
+inline size_t meshopt_optimizeVertexFetchRemap(unsigned int* destination, const T* indices, size_t index_count, size_t vertex_count)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+
+	return meshopt_optimizeVertexFetchRemap(destination, in.data, index_count, vertex_count);
+}
+
+template <typename T>
+inline size_t meshopt_optimizeVertexFetch(void* destination, T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size)
+{
+	meshopt_IndexAdapter<T> inout(indices, indices, index_count);
+
+	return meshopt_optimizeVertexFetch(destination, inout.data, index_count, vertices, vertex_count, vertex_size);
+}
+
+template <typename T>
+inline size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_size, const T* indices, size_t index_count)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+
+	return meshopt_encodeIndexBuffer(buffer, buffer_size, in.data, index_count);
+}
+
+template <typename T>
+inline int meshopt_decodeIndexBuffer(T* destination, size_t index_count, const unsigned char* buffer, size_t buffer_size)
+{
+	char index_size_valid[sizeof(T) == 2 || sizeof(T) == 4 ? 1 : -1];
+	(void)index_size_valid;
+
+	return meshopt_decodeIndexBuffer(destination, index_count, sizeof(T), buffer, buffer_size);
+}
+
+template <typename T>
+inline size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const T* indices, size_t index_count)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+
+	return meshopt_encodeIndexSequence(buffer, buffer_size, in.data, index_count);
+}
+
+template <typename T>
+inline int meshopt_decodeIndexSequence(T* destination, size_t index_count, const unsigned char* buffer, size_t buffer_size)
+{
+	char index_size_valid[sizeof(T) == 2 || sizeof(T) == 4 ? 1 : -1];
+	(void)index_size_valid;
+
+	return meshopt_decodeIndexSequence(destination, index_count, sizeof(T), buffer, buffer_size);
+}
+
+template <typename T>
+inline size_t meshopt_simplify(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float* result_error)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+	meshopt_IndexAdapter<T> out(destination, 0, index_count);
+
+	return meshopt_simplify(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, target_index_count, target_error, result_error);
+}
+
+template <typename T>
+inline size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float* result_error)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+	meshopt_IndexAdapter<T> out(destination, 0, index_count);
+
+	return meshopt_simplifySloppy(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, target_index_count, target_error, result_error);
+}
+
+template <typename T>
+inline size_t meshopt_stripify(T* destination, const T* indices, size_t index_count, size_t vertex_count, T restart_index)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+	meshopt_IndexAdapter<T> out(destination, 0, (index_count / 3) * 5);
+
+	return meshopt_stripify(out.data, in.data, index_count, vertex_count, unsigned(restart_index));
+}
+
+template <typename T>
+inline size_t meshopt_unstripify(T* destination, const T* indices, size_t index_count, T restart_index)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+	meshopt_IndexAdapter<T> out(destination, 0, (index_count - 2) * 3);
+
+	return meshopt_unstripify(out.data, in.data, index_count, unsigned(restart_index));
+}
+
+template <typename T>
+inline meshopt_VertexCacheStatistics meshopt_analyzeVertexCache(const T* indices, size_t index_count, size_t vertex_count, unsigned int cache_size, unsigned int warp_size, unsigned int buffer_size)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+
+	return meshopt_analyzeVertexCache(in.data, index_count, vertex_count, cache_size, warp_size, buffer_size);
+}
+
+template <typename T>
+inline meshopt_OverdrawStatistics meshopt_analyzeOverdraw(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+
+	return meshopt_analyzeOverdraw(in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride);
+}
+
+template <typename T>
+inline meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch(const T* indices, size_t index_count, size_t vertex_count, size_t vertex_size)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+
+	return meshopt_analyzeVertexFetch(in.data, index_count, vertex_count, vertex_size);
+}
+
+template <typename T>
+inline size_t meshopt_buildMeshlets(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t max_triangles, float cone_weight)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+
+	return meshopt_buildMeshlets(meshlets, meshlet_vertices, meshlet_triangles, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, max_vertices, max_triangles, cone_weight);
+}
+
+template <typename T>
+inline size_t meshopt_buildMeshletsScan(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+
+	return meshopt_buildMeshletsScan(meshlets, meshlet_vertices, meshlet_triangles, in.data, index_count, vertex_count, max_vertices, max_triangles);
+}
+
+template <typename T>
+inline meshopt_Bounds meshopt_computeClusterBounds(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+
+	return meshopt_computeClusterBounds(in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride);
+}
+
+template <typename T>
+inline void meshopt_spatialSortTriangles(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)
+{
+	meshopt_IndexAdapter<T> in(0, indices, index_count);
+	meshopt_IndexAdapter<T> out(destination, 0, index_count);
+
+	meshopt_spatialSortTriangles(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride);
+}
+#endif
+
+/**
+ * Copyright (c) 2016-2021 Arseny Kapoulkine
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */

+ 21 - 0
Gems/Meshlets/gem.json

@@ -0,0 +1,21 @@
+{
+    "gem_name": "Meshlets",
+    "display_name": "Meshlets",
+        "license": "Apache-2.0 Or MIT",
+    "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
+    "origin": "Open 3D Engine - o3de.org",
+    "origin_url": "https://github.com/o3de/o3de",
+    "type": "Code",
+    "summary": "The Meshlets Gem is a POC for rendering meshes solely on the GPU using meshlets / mesh clusters.",
+    "canonical_tags": [
+        "Gem"
+    ],
+    "user_tags": [
+        "Meshlets"
+    ],
+    "icon_path": "preview.png",
+    "requirements": "Compiled library for meshlets generation is required to be linked. The open source library meshoptimizer is required for that: https://github.com/zeux/meshoptimizer",
+    "documentation_url": "",
+    "dependencies": [],
+    "restricted": "Meshlets"
+}

+ 3 - 0
Gems/Meshlets/preview.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7ac9dd09bde78f389e3725ac49d61eff109857e004840bc0bc3881739df9618d
+size 2217

+ 0 - 1
Gems/PhysX/Code/CMakeLists.txt

@@ -107,7 +107,6 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS)
                 AZ::AzCore
                 AZ::AzFramework
                 AZ::AzToolsFramework
-                AZ::GFxFramework
                 AZ::AssetBuilderSDK
                 AZ::SceneCore
                 AZ::SceneData

+ 19 - 2
Gems/PhysX/Code/Editor/DebugDraw.cpp

@@ -705,12 +705,29 @@ namespace PhysX
 
             if (!vertices.empty())
             {
+                // Each heightfield quad consists of 6 vertices, or 2 triangles.
+                // If we naively draw each triangle, we'll need 6 lines per quad. However, the diagonal line would be drawn twice,
+                // and the quad borders with adjacent quads would also be drawn twice, so we can reduce this down to 3 lines, so
+                // that we're drawing a per-quad pattern like this:
+                // 2 --- 3
+                //   |\
+                // 0 | \ 1
+                //  
+                // To draw 3 lines, we need 6 vertices. Because our results *already* have 6 vertices per quad, we just need to make
+                // sure each set of 6 is the *right* set of vertices for what we want to draw, and then we can submit the entire set
+                // directly to DrawLines().
+                // We currently get back 6 vertices in the pattern 0-1-2, 1-3-2, for our two triangles. The lines we want to draw
+                // are 0-2, 2-1, and 3-2. We can create this pattern by just copying the third vertex onto the second vertex for
+                // every quad so that 0 1 2 1 3 2 becomes 0 2 2 1 3 2.
+                for (size_t vertex = 0; vertex < vertices.size(); vertex += 6)
+                {
+                    vertices[vertex + 1] = vertices[vertex + 2];
+                }
+
                 // Returned vertices are in the shape-local space, so need to adjust the debug display matrix
                 const AZ::Transform shapeOffsetTransform = AZ::Transform::CreateTranslation(shapeOffset);
                 debugDisplay.PushMatrix(shapeOffsetTransform);
-
                 debugDisplay.DrawLines(vertices, AZ::Colors::White);
-
                 debugDisplay.PopMatrix();
             }
         }

+ 2 - 2
Gems/PhysX/Code/Include/PhysX/SystemComponentBus.h

@@ -69,10 +69,10 @@ namespace PhysX
 
         /// Creates a new heightfield.
         /// @param samples Pointer to beginning of heightfield sample data.
-        /// @param numRows Number of rows in the heightfield.
         /// @param numColumns Number of columns in the heightfield.
+        /// @param numRows Number of rows in the heightfield.
         /// @return Pointer to the created heightfield.
-        virtual physx::PxHeightField* CreateHeightField(const physx::PxHeightFieldSample* samples, AZ::u32 numRows, AZ::u32 numColumns) = 0;
+        virtual physx::PxHeightField* CreateHeightField(const physx::PxHeightFieldSample* samples, size_t numColumns, size_t numRows) = 0;
 
         /// Creates PhysX collision filter data from generic collision filtering settings.
         /// @param layer The collision layer the object belongs to.

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно