ソースを参照

Multi-GPU RPI sample

Renders each half of the screen on two different devices and copies the other half to the displaying device which composites the final output.

Note: uses a modified copy pass that can copy between two devices via the CPU and synchronizes that via fences.

Signed-off-by: Joerg H. Mueller <[email protected]>
Joerg H. Mueller 1 年間 前
コミット
5617b71f02

+ 137 - 0
Gem/Code/Source/MultiGPURPIExampleComponent.cpp

@@ -0,0 +1,137 @@
+/*
+ * 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 <AtomSampleViewerOptions.h>
+#include <MultiGPURPIExampleComponent.h>
+
+#include <Atom/Component/DebugCamera/CameraComponent.h>
+#include <Atom/Component/DebugCamera/NoClipControllerComponent.h>
+
+#include <Atom/RHI/RHISystemInterface.h>
+
+#include <Atom/RPI.Public/ViewProviderBus.h>
+#include <Atom/RPI.Public/RenderPipeline.h>
+#include <Atom/RPI.Public/Scene.h>
+#include <Atom/RPI.Public/RPISystemInterface.h>
+#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
+#include <Atom/RPI.Reflect/Model/ModelAsset.h>
+
+#include <Atom/Feature/ImGui/ImGuiUtils.h>
+
+#include <AzCore/Math/MatrixUtils.h>
+
+#include <Automation/ScriptableImGui.h>
+#include <Automation/ScriptRunnerBus.h>
+
+#include <AzCore/Component/Entity.h>
+
+#include <AzFramework/Components/TransformComponent.h>
+#include <AzFramework/Scene/SceneSystemInterface.h>
+#include <AzFramework/Entity/GameEntityContextComponent.h>
+
+#include <EntityUtilityFunctions.h>
+#include <SampleComponentConfig.h>
+#include <SampleComponentManager.h>
+
+#include <Utils/Utils.h>
+
+#include <RHI/BasicRHIComponent.h>
+
+namespace AtomSampleViewer
+{
+    using namespace AZ;
+
+    void MultiGPURPIExampleComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<MultiGPURPIExampleComponent, AZ::Component>()
+                ->Version(0)
+                ;
+        }
+    }
+
+    MultiGPURPIExampleComponent::MultiGPURPIExampleComponent()
+    {
+    }
+
+    void MultiGPURPIExampleComponent::Activate()
+    {
+        AZ::TickBus::Handler::BusConnect();
+
+        AZ::Render::Bootstrap::DefaultWindowNotificationBus::Handler::BusConnect();
+
+        // preload assets
+        AZStd::vector<AssetCollectionAsyncLoader::AssetToLoadInfo> assetList = {
+            {CubeModelFilePath, azrtti_typeid<RPI::ModelAsset>()}
+        };
+
+        PreloadAssets(assetList);
+
+        // save original render pipeline first and remove it from the scene
+        m_originalPipeline = m_scene->GetDefaultRenderPipeline();
+        m_scene->RemoveRenderPipeline(m_originalPipeline->GetId());
+
+        // add the checker board pipeline
+        const AZStd::string pipelineName("MultiGPUPipeline");
+        AZ::RPI::RenderPipelineDescriptor pipelineDesc;
+        pipelineDesc.m_name = pipelineName;
+        pipelineDesc.m_rootPassTemplate = "MultiGPUPipeline";
+        m_pipeline = AZ::RPI::RenderPipeline::CreateRenderPipelineForWindow(pipelineDesc, *m_windowContext);
+        m_scene->AddRenderPipeline(m_pipeline);
+
+        m_imguiScope = AZ::Render::ImGuiActiveContextScope::FromPass({ "MultiGPUPipeline", "ImGuiPass" });
+    }
+
+    void MultiGPURPIExampleComponent::Deactivate()
+    {
+        // remove cb pipeline before adding original pipeline.
+        if (!m_pipeline)
+        {
+            return;
+        }
+
+        m_imguiScope = {}; // restores previous ImGui context.
+
+        m_scene->RemoveRenderPipeline(m_pipeline->GetId());
+        m_scene->AddRenderPipeline(m_originalPipeline);
+
+        m_pipeline = nullptr;
+
+        AZ::Render::Bootstrap::DefaultWindowNotificationBus::Handler::BusDisconnect();
+
+        AZ::TickBus::Handler::BusDisconnect();
+    }
+
+    void MultiGPURPIExampleComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint timePoint)
+    {
+    }
+
+    void MultiGPURPIExampleComponent::OnAllAssetsReadyActivate()
+    {
+        auto meshFeatureProcessor = GetMeshFeatureProcessor();
+
+        /*
+        auto asset = RPI::AssetUtils::LoadAssetByProductPath<RPI::ModelAsset>(BunnyModelFilePath,
+                                                                              RPI::AssetUtils::TraceLevel::Assert); //*/
+        auto asset = RPI::AssetUtils::LoadAssetByProductPath<RPI::ModelAsset>(CubeModelFilePath,
+                                                                              RPI::AssetUtils::TraceLevel::Assert);
+        m_meshHandle = meshFeatureProcessor->AcquireMesh(Render::MeshHandleDescriptor(asset));
+
+        //const Vector3 nonUniformScale{ 12.f, 12.f, 0.1f };
+        const Vector3 translation{ 0.f, 0.f, -1.0f };
+        Transform transform = Transform::CreateTranslation(translation);
+        meshFeatureProcessor->SetTransform(m_meshHandle, transform);//, nonUniformScale);
+    }
+
+    void MultiGPURPIExampleComponent::DefaultWindowCreated()
+    {
+        AZ::Render::Bootstrap::DefaultWindowBus::BroadcastResult(m_windowContext,
+                                                                 &AZ::Render::Bootstrap::DefaultWindowBus::Events::GetDefaultWindowContext);
+    }
+} // namespace AtomSampleViewer

+ 77 - 0
Gem/Code/Source/MultiGPURPIExampleComponent.h

@@ -0,0 +1,77 @@
+/*
+ * 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 <Atom/Bootstrap/DefaultWindowBus.h>
+#include <Atom/Feature/ImGui/ImGuiUtils.h>
+
+#include <Atom/RPI.Public/Base.h>
+#include <Atom/RPI.Public/WindowContext.h>
+
+#include <AzCore/Asset/AssetCommon.h>
+#include <AzCore/Component/TickBus.h>
+
+#include <AzFramework/Windowing/WindowBus.h>
+#include <AzFramework/Windowing/NativeWindow.h>
+
+#include <Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h>
+#include <Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h>
+#include <Atom/Feature/CoreLights/ShadowConstants.h>
+#include <Atom/Feature/SkyBox/SkyBoxFeatureProcessorInterface.h>
+#include <Atom/Feature/PostProcess/PostProcessFeatureProcessorInterface.h>
+
+#include <Utils/ImGuiSidebar.h>
+#include <Utils/Utils.h>
+
+struct ImGuiContext;
+
+namespace AtomSampleViewer
+{
+    //! A sample component which render the same scene with different render pipelines in different windows
+    //! It has a imgui menu to switch on/off the second render pipeline as well as turn on/off different graphics features
+    //! There is also an option to have the second render pipeline to use the second camera. 
+    class MultiGPURPIExampleComponent final
+        : public CommonSampleComponentBase
+        , public AZ::TickBus::Handler
+        , public AZ::Render::Bootstrap::DefaultWindowNotificationBus::Handler
+    {
+    public:
+        AZ_COMPONENT(MultiGPURPIExampleComponent, "{F7DD0D21-A0EF-4B66-98FA-2DB6B19A8C35}", CommonSampleComponentBase);
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        MultiGPURPIExampleComponent();
+        ~MultiGPURPIExampleComponent() final = default;
+
+        // AZ::Component
+        void Activate() override;
+        void Deactivate() override;
+        
+    private:
+        // AZ::TickBus::Handler overrides ...
+        void OnTick(float deltaTime, AZ::ScriptTimePoint timePoint) override;
+
+        // CommonSampleComponentBase overrides...
+        void OnAllAssetsReadyActivate() override;
+
+        // DefaultWindowNotificationBus::Handler overrides...
+        void DefaultWindowCreated() override;
+
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_meshHandle;
+
+        AZ::RPI::RenderPipelinePtr m_pipeline;
+        AZ::RPI::RenderPipelinePtr m_originalPipeline;
+        AZStd::shared_ptr<AZ::RPI::WindowContext> m_windowContext;
+
+        AZ::Render::ImGuiActiveContextScope m_imguiScope;
+    };
+
+} // namespace AtomSampleViewer

+ 1 - 0
Gem/Code/Source/ProceduralSkinnedMeshUtils.cpp

@@ -12,6 +12,7 @@
 #include <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
 #include <Atom/RPI.Reflect/ResourcePoolAssetCreator.h>
 #include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
+#include <Atom/RPI.Reflect/Model/ModelAssetHelpers.h>
 #include <Atom/RPI.Reflect/Model/ModelLodAssetCreator.h>
 #include <Atom/RPI.Reflect/Model/SkinJointIdPadding.h>
 #include <Atom/Feature/SkinnedMesh/SkinnedMeshInputBuffers.h>

+ 2 - 0
Gem/Code/Source/SampleComponentManager.cpp

@@ -92,6 +92,7 @@
 #include <LightCullingExampleComponent.h>
 #include <MeshExampleComponent.h>
 #include <MSAA_RPI_ExampleComponent.h>
+#include <MultiGPURPIExampleComponent.h>
 #include <MultiRenderPipelineExampleComponent.h>
 #include <MultiSceneExampleComponent.h>
 #include <ParallaxMappingExampleComponent.h>
@@ -308,6 +309,7 @@ namespace AtomSampleViewer
             NewRPISample<DynamicMaterialTestComponent>("DynamicMaterialTest"),
             NewRPISample<MeshExampleComponent>("Mesh"),
             NewRPISample<MSAA_RPI_ExampleComponent>("MSAA"),
+            NewRPISample<MultiGPURPIExampleComponent>("MultiGPU", []() { return AZ::RHI::RHISystemInterface::Get()->GetDeviceCount() >= 2; }),
             NewRPISample<MultiRenderPipelineExampleComponent>("MultiRenderPipeline"),
             NewRPISample<MultiSceneExampleComponent>("MultiScene"),
             NewRPISample<MultiViewSingleSceneAuxGeomExampleComponent>("MultiViewSingleSceneAuxGeom"),

+ 2 - 0
Gem/Code/atomsampleviewergem_private_files.cmake

@@ -143,6 +143,8 @@ set(FILES
     Source/MeshExampleComponent.h
     Source/MSAA_RPI_ExampleComponent.cpp
     Source/MSAA_RPI_ExampleComponent.h
+    Source/MultiGPURPIExampleComponent.cpp
+    Source/MultiGPURPIExampleComponent.h
     Source/MultiRenderPipelineExampleComponent.cpp
     Source/MultiRenderPipelineExampleComponent.h
     Source/MultiSceneExampleComponent.cpp

+ 12 - 0
Passes/ASV/PassTemplates.azasset

@@ -64,6 +64,18 @@
                 "Name": "FullscreenPipeline",
                 "Path": "Passes/FullscreenPipeline.pass"
             },
+            {
+                "Name": "MultiGPUPipeline",
+                "Path": "Passes/MultiGPUPipeline.pass"
+            },
+            {
+                "Name": "MultiGPUCompositePassTemplate",
+                "Path": "Passes/MultiGPUCompositePass.pass"
+            },
+            {
+                "Name": "MultiGPUTrianglePassTemplate",
+                "Path": "Passes/MultiGPUTrianglePass.pass"
+            },
             {
                 "Name": "ReadbackFillerPassTemplate",
                 "Path": "Passes/ReadbackFiller.pass"

+ 54 - 0
Passes/MultiGPUCompositePass.pass

@@ -0,0 +1,54 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "MultiGPUCompositePassTemplate",
+            "PassClass": "FullScreenTriangle",
+            "Slots": [
+                {
+                    "Name": "Input1",
+                    "ShaderInputName": "m_image1",
+                    "SlotType": "Input",
+                    "ScopeAttachmentUsage": "Shader"
+                },
+                {
+                    "Name": "Input2",
+                    "ShaderInputName": "m_image2",
+                    "SlotType": "Input",
+                    "ScopeAttachmentUsage": "Shader"
+                },
+                {
+                    "Name": "Output",
+                    "SlotType": "Output",
+                    "ScopeAttachmentUsage": "RenderTarget"
+                }
+            ],
+            "ImageAttachments": [
+                {
+                    "Name": "OutputAttachment",
+                    "SizeSource": {
+                        "Source": {
+                            "Pass": "Parent",
+                            "Attachment": "PipelineOutput"
+                        }
+                    },
+                    "FormatSource": {
+                        "Pass": "Parent",
+                        "Attachment": "PipelineOutput"
+                    }
+                }
+            ],
+            "Connections": [
+                {
+                    "LocalSlot": "Output",
+                    "AttachmentRef": {
+                        "Pass": "This",
+                        "Attachment": "OutputAttachment"
+                    }
+                }
+            ]
+        }
+    }
+}

+ 180 - 0
Passes/MultiGPUPipeline.pass

@@ -0,0 +1,180 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "MultiGPUPipeline",
+            "PassClass": "ParentPass",
+            "Slots": [
+                {
+                    "Name": "PipelineOutput",
+                    "SlotType": "InputOutput",
+                    "ScopeAttachmentUsage": "RenderTarget"
+                }
+            ],
+            "PassRequests": [
+                {
+                    "Name": "TrianglePass1",
+                    "TemplateName": "MultiGPUTrianglePassTemplate",
+                    "PassData": {
+                        "$type": "FullscreenTrianglePassData",
+                        "ShaderAsset": {
+                            "FilePath": "Shaders/MultiGPURPIExample/Triangle.shader"
+                        },
+                        "ShaderDataMappings": {
+                            "Matrix4x4Mappings": [
+                                {
+                                    "Name": "m_objectMatrix",
+                                    "Value": [
+                                        2.0,
+                                        0.0,
+                                        0.0,
+                                        0.0,
+                                        0.0,
+                                        1.0,
+                                        0.0,
+                                        0.0,
+                                        0.0,
+                                        0.0,
+                                        1.0,
+                                        0.0,
+                                        1.0,
+                                        0.0,
+                                        0.0,
+                                        1.0
+                                    ]
+                                }
+                            ]
+                        }
+                    }
+                },
+                {
+                    "Name": "TrianglePass2",
+                    "TemplateName": "MultiGPUTrianglePassTemplate",
+                    "PassData": {
+                        "$type": "FullscreenTrianglePassData",
+                        "ShaderAsset": {
+                            "FilePath": "Shaders/MultiGPURPIExample/Triangle.shader"
+                        },
+                        "DeviceIndex": 1,
+                        "ShaderDataMappings": {
+                            "Matrix4x4Mappings": [
+                                {
+                                    "Name": "m_objectMatrix",
+                                    "Value": [
+                                        2.0,
+                                        0.0,
+                                        0.0,
+                                        0.0,
+                                        0.0,
+                                        1.0,
+                                        0.0,
+                                        0.0,
+                                        0.0,
+                                        0.0,
+                                        1.0,
+                                        0.0,
+                                        -1.0,
+                                        0.0,
+                                        0.0,
+                                        1.0
+                                    ]
+                                }
+                            ]
+                        }
+                    }
+                },
+                {
+                    "Name": "CopyPass",
+                    "TemplateName": "CopyPassTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "Input",
+                            "AttachmentRef": {
+                                "Pass": "TrianglePass2",
+                                "Attachment": "Output"
+                            }
+                        },
+                        {
+                            "LocalSlot": "Output",
+                            "AttachmentRef": {
+                                "Pass": "TrianglePass2",
+                                "Attachment": "Output"
+                            }
+                        }
+                    ],
+                    "PassData": {
+                        "$type": "CopyPassData",
+                        "DestinationDeviceIndex": 0,
+                        "SourceDeviceIndex": 1
+                    }
+                },
+                {
+                    "Name": "CompositePass",
+                    "TemplateName": "MultiGPUCompositePassTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "Input1",
+                            "AttachmentRef": {
+                                "Pass": "TrianglePass1",
+                                "Attachment": "Output"
+                            }
+                        },
+                        {
+                            "LocalSlot": "Input2",
+                            "AttachmentRef": {
+                                "Pass": "CopyPass",
+                                "Attachment": "Output"
+                            }
+                        }
+                    ],
+                    "PassData": {
+                        "$type": "FullscreenTrianglePassData",
+                        "ShaderAsset": {
+                            "FilePath": "Shaders/MultiGPURPIExample/Composite.shader"
+                        }
+                    }
+                },
+                {
+                    "Name": "ImGuiPass",
+                    "TemplateName": "ImGuiPassTemplate",
+                    "Enabled": true,
+                    "Connections": [
+                        {
+                            "LocalSlot": "InputOutput",
+                            "AttachmentRef": {
+                                "Pass": "CompositePass",
+                                "Attachment": "Output"
+                            }
+                        }
+                    ],
+                    "PassData": {
+                        "$type": "ImGuiPassData",
+                        "IsDefaultImGui": true
+                    }
+                },
+                {
+                    "Name": "CopyToSwapChain",
+                    "TemplateName": "FullscreenCopyTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "Input",
+                            "AttachmentRef": {
+                                "Pass": "ImGuiPass",
+                                "Attachment": "InputOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "Output",
+                            "AttachmentRef": {
+                                "Pass": "Parent",
+                                "Attachment": "PipelineOutput"
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}

+ 55 - 0
Passes/MultiGPUTrianglePass.pass

@@ -0,0 +1,55 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "MultiGPUTrianglePassTemplate",
+            "PassClass": "FullScreenTriangle",
+            "Slots": [
+                {
+                    "Name": "Output",
+                    "SlotType": "Output",
+                    "ScopeAttachmentUsage": "RenderTarget",
+                    "LoadStoreAction": {
+                        "ClearValue": {
+                            "Value": [
+                                0.0,
+                                0.0,
+                                0.0,
+                                0.0
+                            ]
+                        },
+                        "LoadAction": "Clear"
+                    }
+                }
+            ],
+            "ImageAttachments": [
+                {
+                    "Name": "OutputAttachment",
+                    "SizeSource": {
+                        "Source": {
+                            "Pass": "Parent",
+                            "Attachment": "PipelineOutput"
+                        },
+                        "Multipliers": {
+                            "WidthMultiplier": "0.5"
+                        }
+                    },
+                    "ImageDescriptor": {
+                        "Format": "R8G8B8A8_UNORM"
+                    }
+                }
+            ],
+            "Connections": [
+                {
+                    "LocalSlot": "Output",
+                    "AttachmentRef": {
+                        "Pass": "This",
+                        "Attachment": "OutputAttachment"
+                    }
+                }
+            ]
+        }
+    }
+}

+ 44 - 0
Shaders/MultiGPURPIExample/Composite.azsl

@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <Atom/Features/SrgSemantics.azsli>
+
+#include <Atom/Features/PostProcessing/FullscreenPixelInfo.azsli>
+#include <Atom/Features/PostProcessing/FullscreenVertex.azsli>
+
+ShaderResourceGroup PassSrg : SRG_PerPass
+{
+    Texture2D<float4> m_image1;
+    Texture2D<float4> m_image2;
+
+    Sampler m_sampler
+    {
+        MinFilter = Linear;
+        MagFilter = Linear;
+        MipFilter = Linear;
+        AddressU = Clamp;
+        AddressV = Clamp;
+        AddressW = Clamp;
+    };
+}
+
+PSOutput MainPS(VSOutput IN)
+{
+    PSOutput OUT;
+
+    if(IN.m_texCoord.x <= 0.5)
+    {
+        OUT.m_color = PassSrg::m_image1.Sample(PassSrg::m_sampler, float2(IN.m_texCoord.x * 2, IN.m_texCoord.y));
+    }
+    else
+    {
+        OUT.m_color = PassSrg::m_image2.Sample(PassSrg::m_sampler, float2(IN.m_texCoord.x * 2 - 1, IN.m_texCoord.y));
+    }
+
+    return OUT;
+}

+ 22 - 0
Shaders/MultiGPURPIExample/Composite.shader

@@ -0,0 +1,22 @@
+{
+    "Source" : "Composite.azsl",
+
+    "DepthStencilState" : { 
+        "Depth" : { "Enable" : false, "CompareFunc" : "GreaterEqual" }
+    },
+
+    "ProgramSettings":
+    {
+      "EntryPoints":
+      [
+        {
+          "name": "MainVS",
+          "type": "Vertex"
+        },
+        {
+          "name": "MainPS",
+          "type": "Fragment"
+        }
+      ]
+    }
+}

+ 66 - 0
Shaders/MultiGPURPIExample/Triangle.azsl

@@ -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 <Atom/Features/SrgSemantics.azsli>
+
+ShaderResourceGroup PassSrg : SRG_PerPass
+{
+    column_major float4x4 m_objectMatrix;
+}
+
+struct VSInput
+{
+    uint m_vertexID : SV_VertexID;
+};
+
+struct VSOutput
+{
+    float4 m_position : SV_Position;
+    float4 m_color : COLOR0;
+};
+
+VSOutput MainVS(VSInput vsInput)
+{
+    VSOutput OUT;
+
+    switch(vsInput.m_vertexID)
+    {
+    case 0:
+        OUT.m_position = float4(0.0,  0.5, 0.0, 1.0);
+        OUT.m_color = float4(1.0, 0.0, 0.0, 1.0);
+        break;
+    case 1:
+        OUT.m_position = float4(-0.5, -0.5, 0.0, 1.0);
+        OUT.m_color = float4(0.0, 1.0, 0.0, 1.0);
+        break;
+    default:
+        OUT.m_position = float4(0.5, -0.5, 0.0, 1.0);
+        OUT.m_color = float4(0.0, 0.0, 1.0, 1.0);
+        break;
+    }
+
+    OUT.m_position = mul(PassSrg::m_objectMatrix, OUT.m_position);
+
+    return OUT;
+}
+
+struct PSOutput
+{
+    float4 m_color : SV_Target0;
+};
+
+PSOutput MainPS(VSOutput vsOutput)
+{
+    PSOutput OUT;
+
+    OUT.m_color = vsOutput.m_color;
+
+    // Simple tonemapping:
+    OUT.m_color.rgb = pow(OUT.m_color.rgb, 1.0/2.2);
+    return OUT;
+}

+ 22 - 0
Shaders/MultiGPURPIExample/Triangle.shader

@@ -0,0 +1,22 @@
+{
+    "Source" : "Triangle.azsl",
+
+    "DepthStencilState" : { 
+        "Depth" : { "Enable" : false, "CompareFunc" : "GreaterEqual" }
+    },
+
+    "ProgramSettings":
+    {
+      "EntryPoints":
+      [
+        {
+          "name": "MainVS",
+          "type": "Vertex"
+        },
+        {
+          "name": "MainPS",
+          "type": "Fragment"
+        }
+      ]
+    }
+}

+ 8 - 0
atomsampleviewer_asset_files.cmake

@@ -17,6 +17,9 @@ set(FILES
     Passes/CheckerboardPipeline.pass
     Passes/Fullscreen.pass
     Passes/FullscreenPipeline.pass
+    Passes/MultiGPUPipeline.pass
+    Passes/MultiGPUCompositePass.pass
+    Passes/MultiGPUTrianglePass.pass
     Passes/RayTracingAmbientOcclusion.pass
     Passes/ReadbackFiller.pass
     Passes/ReadbackPipeline.pass
@@ -26,6 +29,7 @@ set(FILES
     Passes/RHISamplePipeline.pass
     Passes/SelectorPass.pass
     Passes/SsaoPipeline.pass
+    Passes/ASV/PassTemplates.azasset
     scripts/AreaLightTest.bv.lua
     scripts/AuxGeom.bv.lua
     scripts/CheckerboardTest.bv.lua
@@ -63,6 +67,10 @@ set(FILES
     Shaders/Instanced.azsl
     Shaders/DynamicDraw/DynamicDrawExample.azsl
     Shaders/DynamicDraw/DynamicDrawExample.shader
+    Shaders/MultiGPURPIExample/Composite.azsl
+    Shaders/MultiGPURPIExample/Composite.shader
+    Shaders/MultiGPURPIExample/Triangle.azsl
+    Shaders/MultiGPURPIExample/Triangle.shader
     Shaders/OptimizationTests/DummyTransformColor.azsl
     Shaders/OptimizationTests/DummyTransformColor.shader
     Shaders/PostProcessing/ColorInvertCS.azsl