Răsfoiți Sursa

Merge pull request #556 from aws-lumberyard-dev/openxr

Merge openxr branch to development
amzn-phist 2 ani în urmă
părinte
comite
93ad40c247

+ 6 - 0
Gem/Code/Source/CommonSampleComponentBase.cpp

@@ -235,6 +235,12 @@ namespace AtomSampleViewer
         }
     }
 
+    void CommonSampleComponentBase::IterateToNextLightingPreset()
+    {
+        m_currentLightingPresetIndex = (m_currentLightingPresetIndex + 1) % m_lightingPresets.size();
+        OnLightingPresetSelected(m_lightingPresets[m_currentLightingPresetIndex].m_preset, m_useAlternateSkybox);
+    }
+
     void CommonSampleComponentBase::OnLightingPresetSelected(const AZ::Render::LightingPreset& preset, bool useAlternateSkybox)
     {
         AZ::Render::SkyBoxFeatureProcessorInterface* skyboxFeatureProcessor = AZ::RPI::Scene::GetFeatureProcessorForEntityContextId<AZ::Render::SkyBoxFeatureProcessorInterface>(m_entityContextId);

+ 3 - 0
Gem/Code/Source/CommonSampleComponentBase.h

@@ -78,6 +78,9 @@ namespace AtomSampleViewer
         // Preload assets 
         void PreloadAssets(const AZStd::vector<AZ::AssetCollectionAsyncLoader::AssetToLoadInfo>& assetList);
 
+        //! Iterate to the next lighting preset in a loop
+        void IterateToNextLightingPreset();
+
         //! Async asset load
         AZ::AssetCollectionAsyncLoader m_assetLoadManager;
 

+ 12 - 0
Gem/Code/Source/Platform/Windows/SampleComponentManager_Windows.cpp

@@ -21,11 +21,23 @@ namespace AtomSampleViewer
 
     const char* SampleComponentManager::GetRootPassTemplateName()
     {
+        // Use Low end pipeline template for VR
+        AZ::RPI::XRRenderingInterface* xrSystem = AZ::RPI::RPISystemInterface::Get()->GetXRSystem();
+        if(xrSystem)
+        {
+            return "LowEndPipelineTemplate";
+        }
         return "MainPipeline";
     }
 
     int SampleComponentManager::GetDefaultNumMSAASamples()
     {
+        // Use sample count of 1 for VR pipelines
+        AZ::RPI::XRRenderingInterface* xrSystem = AZ::RPI::RPISystemInterface::Get()->GetXRSystem();
+        if (xrSystem)
+        {
+            return 1;
+        }
         return 4;
     }
 } // namespace AtomSampleViewer

+ 16 - 1
Gem/Code/Source/RHI/BasicRHIComponent.cpp

@@ -57,7 +57,7 @@ namespace AtomSampleViewer
         // The RHISamplePass template should have one owned image attachment which is the render target
         m_outputAttachment = m_ownedAttachments[0];
 
-        // Force udpate pass attachment to get currect size and save it to local variables
+        // Force update pass attachment to get correct size and save it to local variables
         m_outputAttachment->Update();
 
         // update output info for the rhi sample
@@ -90,6 +90,16 @@ namespace AtomSampleViewer
         }
     }
 
+    uint32_t RHISamplePass::GetViewIndex() const
+    {
+        return m_viewIndex;
+    }
+
+    void RHISamplePass::SetViewIndex(const uint32_t viewIndex)
+    {
+        m_viewIndex = viewIndex;
+    }
+
     bool BasicRHIComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
     {
         using namespace AZ;
@@ -610,4 +620,9 @@ namespace AtomSampleViewer
     {
         return m_viewport.m_maxY - m_viewport.m_minY;
     }
+
+    void BasicRHIComponent::SetViewIndex(const uint32_t viewIndex)
+    {
+        m_viewIndex = viewIndex;
+    }
 }

+ 12 - 0
Gem/Code/Source/RHI/BasicRHIComponent.h

@@ -66,6 +66,10 @@ namespace AtomSampleViewer
         // Pass overrides
         const AZ::RPI::PipelineViewTag& GetPipelineViewTag() const override;
 
+        // Return the view index of the pass
+        uint32_t GetViewIndex() const;
+        void SetViewIndex(const uint32_t viewIndex);
+
     protected:
         explicit RHISamplePass(const AZ::RPI::PassDescriptor& descriptor);
 
@@ -78,6 +82,9 @@ namespace AtomSampleViewer
         AZ::RPI::Ptr<AZ::RPI::PassAttachment> m_outputAttachment;
 
         AZ::RPI::PipelineViewTag m_pipelineViewTag;
+
+        // Used to determine view index for XR sample
+        uint32_t m_viewIndex = 0;
     };
 
     class BasicRHIComponent
@@ -103,6 +110,8 @@ namespace AtomSampleViewer
         
         float GetViewportHeight();
         
+        void SetViewIndex(const uint32_t viewIndex);
+       
     protected:
         AZ_DISABLE_COPY(BasicRHIComponent);
 
@@ -237,5 +246,8 @@ namespace AtomSampleViewer
 
         // whether this sample supports RHI sample render pipeline or not
         bool m_supportRHISamplePipeline = false;
+        
+        // view index. Used by XR related samples
+        uint32_t m_viewIndex = 0;
     };
 } // namespace AtomSampleViewer

+ 407 - 0
Gem/Code/Source/RHI/XRExampleComponent.cpp

@@ -0,0 +1,407 @@
+/*
+ * 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 <RHI/XRExampleComponent.h>
+#include <Atom/RHI/CommandList.h>
+#include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
+#include <Atom/RHI.Reflect/RenderAttachmentLayoutBuilder.h>
+#include <Atom/RPI.Public/Shader/Shader.h>
+#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
+#include <AzCore/Math/Color.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <SampleComponentManager.h>
+#include <Utils/Utils.h>
+
+namespace AtomSampleViewer
+{
+    void XRExampleComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<XRExampleComponent, AZ::Component>()->Version(0);
+        }
+    }
+
+    XRExampleComponent::XRExampleComponent()
+    {
+        m_supportRHISamplePipeline = true;
+        
+    }
+
+    void XRExampleComponent::Activate()
+    {
+        if (!AZ::RPI::RPISystemInterface::Get()->GetXRSystem())
+        {
+            return;
+        }
+
+        m_depthStencilID = AZ::RHI::AttachmentId{ AZStd::string::format("DepthStencilID_%llu", GetId()) };
+        CreateCubeInputAssemblyBuffer();
+        CreateCubePipeline();
+        CreateScope();
+        AZ::RHI::RHISystemNotificationBus::Handler::BusConnect();
+        AZ::TickBus::Handler::BusConnect();
+    }
+
+    void XRExampleComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
+    {
+        m_time += deltaTime;
+    }
+
+    void XRExampleComponent::OnFramePrepare(AZ::RHI::FrameGraphBuilder& frameGraphBuilder)
+    {
+        AZ::Matrix4x4 projection = AZ::Matrix4x4::CreateIdentity();
+
+        AZ::RPI::XRRenderingInterface* xrSystem = AZ::RPI::RPISystemInterface::Get()->GetXRSystem();
+        if (xrSystem && xrSystem->ShouldRender())
+        {
+            AZ::RPI::FovData fovData;
+            AZ::RPI::PoseData poseData, frontViewPoseData;
+            [[maybe_unused]] AZ::RHI::ResultCode resultCode = xrSystem->GetViewFov(m_viewIndex, fovData);
+            resultCode = xrSystem->GetViewPose(m_viewIndex, poseData);
+                
+            static const float clip_near = 0.05f;
+            static const float clip_far = 100.0f;
+            bool reverseDepth = false;
+            projection = xrSystem->CreateStereoscopicProjection(fovData.m_angleLeft, fovData.m_angleRight,
+                                                          fovData.m_angleDown, fovData.m_angleUp, 
+                                                          clip_near, clip_far, reverseDepth);
+
+            AZ::Quaternion poseOrientation = poseData.m_orientation; 
+            poseOrientation.InvertFast(); 
+            AZ::Matrix4x4 viewMat = AZ::Matrix4x4::CreateFromQuaternionAndTranslation(poseOrientation, -poseData.m_position);
+            m_viewProjMatrix = projection * viewMat;
+ 
+            const AZ::Matrix4x4 initialScaleMat = AZ::Matrix4x4::CreateScale(AZ::Vector3(0.1f, 0.1f, 0.1f));
+
+            //Model matrix for the cube related to the front view
+            resultCode = xrSystem->GetViewFrontPose(frontViewPoseData);
+            m_modelMatrices[0] = AZ::Matrix4x4::CreateFromQuaternionAndTranslation(frontViewPoseData.m_orientation, frontViewPoseData.m_position) * initialScaleMat;
+                      
+            //Model matrix for the cube related to the left controller
+            AZ::RPI::PoseData controllerLeftPose, controllerRightPose;
+            resultCode = xrSystem->GetControllerPose(0, controllerLeftPose);
+            AZ::Matrix4x4 leftScaleMat = initialScaleMat * AZ::Matrix4x4::CreateScale(AZ::Vector3(xrSystem->GetControllerScale(0)));
+            m_modelMatrices[1] = AZ::Matrix4x4::CreateFromQuaternionAndTranslation(controllerLeftPose.m_orientation, controllerLeftPose.m_position) * leftScaleMat;
+
+            //Model matrix for the cube related to the right controller
+            AZ::Matrix4x4 rightScaleMat = initialScaleMat * AZ::Matrix4x4::CreateScale(AZ::Vector3(xrSystem->GetControllerScale(1)));
+            resultCode = xrSystem->GetControllerPose(1, controllerRightPose);
+            m_modelMatrices[2] = AZ::Matrix4x4::CreateFromQuaternionAndTranslation(controllerRightPose.m_orientation, controllerRightPose.m_position) * rightScaleMat;
+        }      
+        
+        for (int i = 0; i < NumberOfCubes; ++i)
+        {
+            m_shaderResourceGroups[i]->SetConstant(m_shaderIndexWorldMat, m_modelMatrices[i]);
+            m_shaderResourceGroups[i]->SetConstant(m_shaderIndexViewProj, m_viewProjMatrix);
+            m_shaderResourceGroups[i]->Compile();
+        }
+
+        BasicRHIComponent::OnFramePrepare(frameGraphBuilder);
+    }
+
+    XRExampleComponent::SingleCubeBufferData XRExampleComponent::CreateSingleCubeBufferData()
+    {
+        const AZStd::fixed_vector<AZ::Color, GeometryVertexCount> vertexColor =
+        {
+            //Front Face
+            AZ::Colors::DarkBlue,   AZ::Colors::DarkBlue,   AZ::Colors::DarkBlue,   AZ::Colors::DarkBlue,
+            //Back Face                                                                       
+            AZ::Colors::Blue,       AZ::Colors::Blue,       AZ::Colors::Blue,       AZ::Colors::Blue,
+            //Left Face                                                                      
+            AZ::Colors::DarkGreen,  AZ::Colors::DarkGreen,  AZ::Colors::DarkGreen,  AZ::Colors::DarkGreen,
+            //Right Face                                                                    
+            AZ::Colors::Green,      AZ::Colors::Green,      AZ::Colors::Green,      AZ::Colors::Green,
+            //Top Face                                                                  
+            AZ::Colors::DarkRed,    AZ::Colors::DarkRed,    AZ::Colors::DarkRed,    AZ::Colors::DarkRed,
+            //Bottom Face                                                                    
+            AZ::Colors::Red,        AZ::Colors::Red,        AZ::Colors::Red,        AZ::Colors::Red,
+        };
+
+        // Create vertices, colors and normals for a cube and a plane
+        SingleCubeBufferData bufferData;
+        {
+            
+            const AZStd::fixed_vector<AZ::Vector3, GeometryVertexCount> vertices =
+            {
+                //Front Face
+                AZ::Vector3(1.0, 1.0, 1.0),         AZ::Vector3(-1.0, 1.0, 1.0),     AZ::Vector3(-1.0, -1.0, 1.0),    AZ::Vector3(1.0, -1.0, 1.0),
+                //Back Face                                                                       
+                AZ::Vector3(1.0, 1.0, -1.0),        AZ::Vector3(-1.0, 1.0, -1.0),    AZ::Vector3(-1.0, -1.0, -1.0),   AZ::Vector3(1.0, -1.0, -1.0),
+                //Left Face                                                                      
+                AZ::Vector3(-1.0, 1.0, 1.0),        AZ::Vector3(-1.0, -1.0, 1.0),    AZ::Vector3(-1.0, -1.0, -1.0),   AZ::Vector3(-1.0, 1.0, -1.0),
+                //Right Face                                                                    
+                AZ::Vector3(1.0, 1.0, 1.0),         AZ::Vector3(1.0, -1.0, 1.0),     AZ::Vector3(1.0, -1.0, -1.0),    AZ::Vector3(1.0, 1.0, -1.0),
+                //Top Face                                                                  
+                AZ::Vector3(1.0, 1.0, 1.0),         AZ::Vector3(-1.0, 1.0, 1.0),     AZ::Vector3(-1.0, 1.0, -1.0),    AZ::Vector3(1.0, 1.0, -1.0),
+                //Bottom Face                                                                    
+                AZ::Vector3(1.0, -1.0, 1.0),        AZ::Vector3(-1.0, -1.0, 1.0),    AZ::Vector3(-1.0, -1.0, -1.0),   AZ::Vector3(1.0, -1.0, -1.0),
+            };
+
+            for (int i = 0; i < GeometryVertexCount; ++i)
+            {
+                SetVertexPosition(bufferData.m_positions.data(), i, vertices[i]);
+                SetVertexColor(bufferData.m_colors.data(), i, vertexColor[i].GetAsVector4());
+            }
+
+            bufferData.m_indices =
+            {
+                {
+                    //Back
+                    2, 0, 1,
+                    0, 2, 3,
+                    //Front
+                    4, 6, 5,
+                    6, 4, 7,
+                    //Left
+                    8, 10, 9,
+                    10, 8, 11,
+                    //Right
+                    14, 12, 13,
+                    15, 12, 14,
+                    //Top
+                    16, 18, 17,
+                    18, 16, 19,
+                    //Bottom
+                    22, 20, 21,
+                    23, 20, 22,
+                }
+            };
+        }
+        return bufferData;
+    }
+
+    void XRExampleComponent::CreateCubeInputAssemblyBuffer()
+    {
+        const AZ::RHI::Ptr<AZ::RHI::Device> device = Utils::GetRHIDevice();
+        AZ::RHI::ResultCode result = AZ::RHI::ResultCode::Success;
+
+        m_bufferPool = AZ::RHI::Factory::Get().CreateBufferPool();
+        AZ::RHI::BufferPoolDescriptor bufferPoolDesc;
+        bufferPoolDesc.m_bindFlags = AZ::RHI::BufferBindFlags::InputAssembly;
+        bufferPoolDesc.m_heapMemoryLevel = AZ::RHI::HeapMemoryLevel::Device;
+        result = m_bufferPool->Init(*device, bufferPoolDesc);
+        if (result != AZ::RHI::ResultCode::Success)
+        {
+            AZ_Error("XRExampleComponent", false, "Failed to initialize buffer pool with error code %d", result);
+            return;
+        }
+
+        SingleCubeBufferData bufferData = CreateSingleCubeBufferData();
+
+        m_inputAssemblyBuffer = AZ::RHI::Factory::Get().CreateBuffer();
+        AZ::RHI::BufferInitRequest request;
+
+        request.m_buffer = m_inputAssemblyBuffer.get();
+        request.m_descriptor = AZ::RHI::BufferDescriptor{ AZ::RHI::BufferBindFlags::InputAssembly, sizeof(SingleCubeBufferData) };
+        request.m_initialData = &bufferData;
+        result = m_bufferPool->InitBuffer(request);
+        if (result != AZ::RHI::ResultCode::Success)
+        {
+            AZ_Error("XRExampleComponent", false, "Failed to initialize buffer with error code %d", result);
+            return;
+        }
+
+        m_streamBufferViews[0] =
+        {
+            *m_inputAssemblyBuffer,
+            offsetof(SingleCubeBufferData, m_positions),
+            sizeof(SingleCubeBufferData::m_positions),
+            sizeof(VertexPosition)
+        };
+
+        m_streamBufferViews[1] =
+        {
+            *m_inputAssemblyBuffer,
+            offsetof(SingleCubeBufferData, m_colors),
+            sizeof(SingleCubeBufferData::m_colors),
+            sizeof(VertexColor)
+        };
+
+        m_indexBufferView =
+        {
+            *m_inputAssemblyBuffer,
+            offsetof(SingleCubeBufferData, m_indices),
+            sizeof(SingleCubeBufferData::m_indices),
+            AZ::RHI::IndexFormat::Uint16
+        };
+
+        AZ::RHI::InputStreamLayoutBuilder layoutBuilder;
+        layoutBuilder.SetTopology(AZ::RHI::PrimitiveTopology::TriangleList);
+        layoutBuilder.AddBuffer()->Channel("POSITION", AZ::RHI::Format::R32G32B32_FLOAT);
+        layoutBuilder.AddBuffer()->Channel("COLOR", AZ::RHI::Format::R32G32B32A32_FLOAT);
+        m_streamLayoutDescriptor.Clear();
+        m_streamLayoutDescriptor = layoutBuilder.End();
+
+        AZ::RHI::ValidateStreamBufferViews(m_streamLayoutDescriptor, m_streamBufferViews);
+    }
+
+    void XRExampleComponent::CreateCubePipeline()
+    {
+        const char* shaderFilePath = "Shaders/RHI/OpenXrSample.azshader";
+        const char* sampleName = "XRExampleComponent";
+
+        auto shader = LoadShader(shaderFilePath, sampleName);
+        if (shader == nullptr)
+        { 
+            return;
+        }
+
+        const AZ::RHI::Ptr<AZ::RHI::Device> device = Utils::GetRHIDevice();
+        AZ::RHI::PipelineStateDescriptorForDraw pipelineDesc;
+        shader->GetVariant(AZ::RPI::ShaderAsset::RootShaderVariantStableId).ConfigurePipelineState(pipelineDesc);
+        pipelineDesc.m_inputStreamLayout = m_streamLayoutDescriptor;
+        pipelineDesc.m_renderStates.m_depthStencilState.m_depth.m_enable = 1;
+        pipelineDesc.m_renderStates.m_depthStencilState.m_depth.m_func = AZ::RHI::ComparisonFunc::LessEqual;
+
+        AZ::RHI::RenderAttachmentLayoutBuilder attachmentsBuilder;
+        attachmentsBuilder.AddSubpass()
+            ->RenderTargetAttachment(m_outputFormat)
+            ->DepthStencilAttachment(device->GetNearestSupportedFormat(AZ::RHI::Format::D24_UNORM_S8_UINT, AZ::RHI::FormatCapabilities::DepthStencil));
+            
+        [[maybe_unused]] AZ::RHI::ResultCode result = attachmentsBuilder.End(pipelineDesc.m_renderAttachmentConfiguration.m_renderAttachmentLayout);
+        AZ_Assert(result == AZ::RHI::ResultCode::Success, "Failed to create render attachment layout");
+
+        m_pipelineState = shader->AcquirePipelineState(pipelineDesc);
+        if (!m_pipelineState)
+        {
+            AZ_Error("XRExampleComponent", false, "Failed to acquire default pipeline state for shader '%s'", shaderFilePath);
+            return;
+        }
+
+        auto perInstanceSrgLayout = shader->FindShaderResourceGroupLayout(AZ::Name{ "OpenXrSrg" });
+        if (!perInstanceSrgLayout)
+        {
+            AZ_Error("XRExampleComponent", false, "Failed to get shader resource group layout");
+            return;
+        }
+
+        for (int i = 0; i < NumberOfCubes; ++i)
+        {
+            m_shaderResourceGroups[i] = CreateShaderResourceGroup(shader, "OpenXrSrg", sampleName);
+        }
+
+        // Using the first SRG to get the correct index as all the SRGs will have the same indices.
+        FindShaderInputIndex(&m_shaderIndexWorldMat, m_shaderResourceGroups[0], AZ::Name{ "m_worldMatrix" }, "XRExampleComponent");
+        FindShaderInputIndex(&m_shaderIndexViewProj, m_shaderResourceGroups[0], AZ::Name{ "m_viewProjMatrix" }, "XRExampleComponent");
+    }
+
+    void XRExampleComponent::CreateScope()
+    {
+        // Creates a scope for rendering the triangle.
+        struct ScopeData
+        {
+
+        };
+        const auto prepareFunction = [this](AZ::RHI::FrameGraphInterface frameGraph, [[maybe_unused]] ScopeData& scopeData)
+        {
+            // Binds the swap chain as a color attachment. Clears it to black.
+            {
+                AZ::RHI::ImageScopeAttachmentDescriptor descriptor;
+                descriptor.m_attachmentId = m_outputAttachmentId;
+                descriptor.m_loadStoreAction.m_loadAction = AZ::RHI::AttachmentLoadAction::Load;
+                frameGraph.UseColorAttachment(descriptor);
+            }
+
+            // Create & Binds DepthStencil image
+            {
+                const AZ::RHI::Ptr<AZ::RHI::Device> device = Utils::GetRHIDevice();
+                const AZ::RHI::ImageDescriptor imageDescriptor = AZ::RHI::ImageDescriptor::Create2D(
+                    AZ::RHI::ImageBindFlags::DepthStencil,
+                    m_outputWidth,
+                    m_outputHeight,
+                    device->GetNearestSupportedFormat(AZ::RHI::Format::D24_UNORM_S8_UINT, AZ::RHI::FormatCapabilities::DepthStencil));
+                const AZ::RHI::TransientImageDescriptor transientImageDescriptor(m_depthStencilID, imageDescriptor);
+
+                frameGraph.GetAttachmentDatabase().CreateTransientImage(transientImageDescriptor);
+
+                AZ::RHI::ImageScopeAttachmentDescriptor dsDesc;
+                dsDesc.m_attachmentId = m_depthStencilID;
+                dsDesc.m_imageViewDescriptor.m_overrideFormat = device->GetNearestSupportedFormat(AZ::RHI::Format::D24_UNORM_S8_UINT, AZ::RHI::FormatCapabilities::DepthStencil);
+                dsDesc.m_loadStoreAction.m_clearValue = AZ::RHI::ClearValue::CreateDepthStencil(1.0f, 0);
+                dsDesc.m_loadStoreAction.m_loadAction = AZ::RHI::AttachmentLoadAction::Clear;
+                dsDesc.m_loadStoreAction.m_loadActionStencil = AZ::RHI::AttachmentLoadAction::DontCare;
+                frameGraph.UseDepthStencilAttachment(dsDesc, AZ::RHI::ScopeAttachmentAccess::Write);
+            }
+
+            // We will submit NumberOfCubes draw items.
+            frameGraph.SetEstimatedItemCount(NumberOfCubes);
+        };
+
+        AZ::RHI::EmptyCompileFunction<ScopeData> compileFunction;
+
+        const auto executeFunction = [this](const AZ::RHI::FrameGraphExecuteContext& context, [[maybe_unused]] const ScopeData& scopeData)
+        {
+            AZ::RHI::CommandList* commandList = context.GetCommandList();
+
+            // Set persistent viewport and scissor state.
+            commandList->SetViewports(&m_viewport, 1);
+            commandList->SetScissors(&m_scissor, 1);
+
+            AZ::RHI::DrawIndexed drawIndexed;
+            drawIndexed.m_indexCount = GeometryIndexCount;
+            drawIndexed.m_instanceCount = 1;
+
+            // Dividing NumberOfCubes by context.GetCommandListCount() to balance to number 
+            // of draw call equally between each thread.
+            uint32_t numberOfCubesPerCommandList = NumberOfCubes / context.GetCommandListCount();
+            uint32_t indexStart = context.GetCommandListIndex() * numberOfCubesPerCommandList;
+            uint32_t indexEnd = indexStart + numberOfCubesPerCommandList;
+
+            if (context.GetCommandListIndex() == context.GetCommandListCount() - 1)
+            {
+                indexEnd = NumberOfCubes;
+            }
+
+            for (uint32_t i = indexStart; i < indexEnd; ++i)
+            {
+                const AZ::RHI::ShaderResourceGroup* shaderResourceGroups[] = { m_shaderResourceGroups[i]->GetRHIShaderResourceGroup() };
+
+                AZ::RHI::DrawItem drawItem;
+                drawItem.m_arguments = drawIndexed;
+                drawItem.m_pipelineState = m_pipelineState.get();
+                drawItem.m_indexBufferView = &m_indexBufferView;
+                drawItem.m_shaderResourceGroupCount = static_cast<uint8_t>(AZ::RHI::ArraySize(shaderResourceGroups));
+                drawItem.m_shaderResourceGroups = shaderResourceGroups;
+                drawItem.m_streamBufferViewCount = static_cast<uint8_t>(m_streamBufferViews.size());
+                drawItem.m_streamBufferViews = m_streamBufferViews.data();
+
+                commandList->Submit(drawItem);
+            }
+        };
+
+        m_scopeProducers.emplace_back(
+            aznew AZ::RHI::ScopeProducerFunction<
+            ScopeData,
+            decltype(prepareFunction),
+            decltype(compileFunction),
+            decltype(executeFunction)>(
+                AZ::RHI::ScopeId{ AZStd::string::format("XRSample_Id_%llu", GetId()) },
+                ScopeData{},
+                prepareFunction,
+                compileFunction,
+                executeFunction));
+    }
+
+    void XRExampleComponent::Deactivate()
+    {
+        if (!AZ::RPI::RPISystemInterface::Get()->GetXRSystem())
+        {
+            return;
+        }
+
+        m_inputAssemblyBuffer = nullptr;
+        m_bufferPool = nullptr;
+        m_pipelineState = nullptr;
+        m_shaderResourceGroups.fill(nullptr);
+
+        AZ::RHI::RHISystemNotificationBus::Handler::BusDisconnect();
+        m_windowContext = nullptr;
+        m_scopeProducers.clear();
+    }
+} 

+ 101 - 0
Gem/Code/Source/RHI/XRExampleComponent.h

@@ -0,0 +1,101 @@
+/*
+ * 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 <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
+
+#include <Atom/RHI/FrameScheduler.h>
+#include <Atom/RHI/DrawItem.h>
+#include <Atom/RHI/Device.h>
+#include <Atom/RHI/Factory.h>
+#include <Atom/RHI/PipelineState.h>
+#include <Atom/RHI/BufferPool.h>
+
+#include <AzCore/Math/Matrix4x4.h>
+
+#include <RHI/BasicRHIComponent.h>
+#include <AzCore/Component/TickBus.h>
+
+namespace AtomSampleViewer
+{
+    //! The purpose of this sample is to establish a simple XR sample utilizing a simple VR pipeline
+    //! It will render a mesh per controller plus one for the front view. It will prove out all the 
+    //! code related related to openxr device, instance, swapchain, session, input, space.       
+    class XRExampleComponent final
+        : public BasicRHIComponent
+        , public AZ::TickBus::Handler
+    {
+    public:
+        AZ_COMPONENT(XRExampleComponent, "{A7D9A921-1FF9-4078-92BD-169E258456E7}");
+        AZ_DISABLE_COPY(XRExampleComponent);
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        XRExampleComponent();
+        ~XRExampleComponent() override = default;
+
+    protected:
+        // 1 cube for view + 2 cubes for the controller
+        static const uint32_t NumberOfCubes = 3; 
+        static const uint32_t GeometryVertexCount = 24;
+        static const uint32_t GeometryIndexCount = 36;
+
+        struct SingleCubeBufferData
+        {
+            AZStd::array<VertexPosition, GeometryVertexCount> m_positions;
+            AZStd::array<VertexColor, GeometryVertexCount> m_colors;
+            AZStd::array<uint16_t, GeometryIndexCount> m_indices;
+        };
+
+        // AZ::Component
+        void Activate() override;
+        void Deactivate() override;
+
+        // RHISystemNotificationBus::Handler
+        void OnFramePrepare(AZ::RHI::FrameGraphBuilder& frameGraphBuilder) override;
+
+        // TickBus::Handler
+        void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
+
+        //! Create IA data
+        void CreateCubeInputAssemblyBuffer();
+        //! Create Cube data
+        SingleCubeBufferData CreateSingleCubeBufferData();
+        //! Create PSO data
+        void CreateCubePipeline();
+        //! Create the relevant Scope
+        void CreateScope();
+
+        AZ::RHI::Ptr<AZ::RHI::BufferPool> m_bufferPool;
+        AZ::RHI::IndexBufferView m_indexBufferView;
+        AZ::RHI::Ptr<AZ::RHI::Buffer> m_inputAssemblyBuffer;
+        AZ::RHI::InputStreamLayout m_streamLayoutDescriptor;
+        AZ::RHI::ConstPtr<AZ::RHI::PipelineState> m_pipelineState;
+
+        struct BufferData
+        {
+            AZStd::array<VertexPosition, 3> m_positions;
+            AZStd::array<VertexColor, 3> m_colors;
+            AZStd::array<uint16_t, 3> m_indices;
+        };
+
+        AZStd::array<AZ::RHI::StreamBufferView, 2> m_streamBufferViews;
+        AZ::RHI::DrawItem m_drawItem;
+        float m_time = 0.0f;
+        AZStd::array<AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>, NumberOfCubes> m_shaderResourceGroups;
+        AZStd::array<AZ::Matrix4x4, NumberOfCubes> m_modelMatrices;
+        AZ::Matrix4x4 m_viewProjMatrix;
+
+        AZ::RHI::ShaderInputConstantIndex m_shaderIndexWorldMat;
+        AZ::RHI::ShaderInputConstantIndex m_shaderIndexViewProj;  
+        AZ::RHI::AttachmentId m_depthStencilID;
+    };
+} // namespace AtomSampleViewer

+ 211 - 52
Gem/Code/Source/SampleComponentManager.cpp

@@ -60,6 +60,7 @@
 #include <RHI/TextureExampleComponent.h>
 #include <RHI/TextureMapExampleComponent.h>
 #include <RHI/TriangleExampleComponent.h>
+#include <RHI/XRExampleComponent.h>
 #include <RHI/TrianglesConstantBufferExampleComponent.h>
 #include <RHI/BindlessPrototypeExampleComponent.h>
 #include <RHI/RayTracingExampleComponent.h>
@@ -102,6 +103,7 @@
 #include <TransparencyExampleComponent.h>
 #include <DiffuseGIExampleComponent.h>
 #include <SSRExampleComponent.h>
+#include <XRRPIExampleComponent.h>
 #include <ShaderReloadTestComponent.h>
 #include <ReadbackExampleComponent.h>
 
@@ -305,6 +307,7 @@ namespace AtomSampleViewer
             NewRHISample<TextureMapExampleComponent>("TextureMap"),
             NewRHISample<TriangleExampleComponent>("Triangle"),
             NewRHISample<TrianglesConstantBufferExampleComponent>("TrianglesConstantBuffer"),
+            NewRHISample<XRExampleComponent>("OpenXr", []() { return AZ::RHI::RHISystemInterface::Get()->GetXRSystem() != nullptr; }),
             NewRHISample<MatrixAlignmentTestExampleComponent>("MatrixAlignmentTest"),
             NewRPISample<AssetLoadTestComponent>("AssetLoadTest"),
             NewRPISample<AuxGeomExampleComponent>("AuxGeom"),
@@ -339,6 +342,7 @@ namespace AtomSampleViewer
             NewFeaturesSample<SkinnedMeshExampleComponent>("SkinnedMesh"),
             NewFeaturesSample<SsaoExampleComponent>("SSAO"),
             NewFeaturesSample<SSRExampleComponent>("SSR"),
+            NewFeaturesSample<XRRPIExampleComponent>("OpenXR", []() { return AZ::RPI::RPISystemInterface::Get()->GetXRSystem() != nullptr; }),
             NewFeaturesSample<TonemappingExampleComponent>("Tonemapping"),
             NewFeaturesSample<TransparencyExampleComponent>("Transparency"),
             NewPerfSample<_100KDrawableExampleComponent>("100KDrawable_SingleView"),
@@ -571,6 +575,14 @@ namespace AtomSampleViewer
 
     void SampleComponentManager::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
     {
+        if (auto* xrSystem = AZ::RPI::RPISystemInterface::Get()->GetXRSystem())
+        {
+            EnableRenderPipeline(xrSystem->GetRHIXRRenderingInterface()->IsDefaultRenderPipelineEnabledOnHost());
+
+            //Only enable XR pipelines if the XR drivers indicate we have accurate pose information from the device
+            EnableXrPipelines(xrSystem->ShouldRender());
+        }
+
         if (m_imGuiFrameTimer)
         {
             m_imGuiFrameTimer->PushValue(deltaTime * 1000.0f);
@@ -1298,6 +1310,39 @@ namespace AtomSampleViewer
         ReleaseRPIScene();
     }
 
+    void SampleComponentManager::EnableRenderPipeline(bool value)
+    {
+        if (m_renderPipeline)
+        {
+            if (value)
+            {
+                m_renderPipeline->AddToRenderTick();
+            }
+            else
+            {
+                m_renderPipeline->RemoveFromRenderTick();
+            }
+        }
+    }
+
+    void SampleComponentManager::EnableXrPipelines(bool value)
+    {
+        for (RPI::RenderPipelinePtr xrPipeline : m_xrPipelines)
+        {
+            if (xrPipeline)
+            {
+                if (value)
+                {
+                    xrPipeline->AddToRenderTick();
+                }
+                else
+                {
+                    xrPipeline->RemoveFromRenderTick();
+                }
+            }
+        }
+    }
+
     void SampleComponentManager::ShowFrameCaptureDialog()
     {
         static bool requestCaptureOnNextFrame = false;
@@ -1351,22 +1396,21 @@ namespace AtomSampleViewer
     {
         m_exampleEntity->Deactivate();
 
-        // Pointer to the m_activeSample must be nullified before m_activeSample is destroyed.
-        if (m_rhiSamplePass)
-        {
-            m_rhiSamplePass->SetRHISample(nullptr);
-        }
+        // Pointer to all the passes within m_rhiSamplePasses must be nullified before all the samples within m_activeSamples are destroyed.
+        SetRHISamplePass(nullptr);
 
-        if (m_activeSample != nullptr)
+        for (AZ::Component* activeComponent : m_activeSamples)
         {
-            // Disable the camera controller just in case the active sample enabled it and didn't disable in Deactivate().
-            AZ::Debug::CameraControllerRequestBus::Event(m_cameraEntity->GetId(), &AZ::Debug::CameraControllerRequestBus::Events::Disable);
+            if (activeComponent != nullptr)
+            {
+                // Disable the camera controller just in case the active sample enabled it and didn't disable in Deactivate().
+                AZ::Debug::CameraControllerRequestBus::Event(m_cameraEntity->GetId(), &AZ::Debug::CameraControllerRequestBus::Events::Disable);
 
-            m_exampleEntity->RemoveComponent(m_activeSample);
-            delete m_activeSample;
+                m_exampleEntity->RemoveComponent(activeComponent);
+                delete activeComponent;
+            }
         }
-
-        m_activeSample = nullptr;
+        m_activeSamples.clear();
 
         // Force a reset of the shader variant finder to get more consistent testing of samples every time they are run, rather
         // than the first time for each sample being "special".
@@ -1382,7 +1426,7 @@ namespace AtomSampleViewer
 
         // Reset to RHI sample pipeline
         SwitchSceneForRHISample();
-        m_rhiSamplePass->SetRHISample(nullptr);
+        SetRHISamplePass(nullptr);
     }
 
     void SampleComponentManager::CreateDefaultCamera()
@@ -1503,24 +1547,33 @@ namespace AtomSampleViewer
             SwitchSceneForRPISample();
         }
 
-        SampleComponentConfig config(m_windowContext, m_cameraEntity->GetId(), m_entityContextId);
-        m_activeSample = m_exampleEntity->CreateComponent(sampleEntry.m_sampleUuid);
-        m_activeSample->SetConfiguration(config);
-
+        SampleComponentConfig config(m_windowContext, m_cameraEntity->GetId(), m_entityContextId); 
         // special setup for RHI samples
         if (sampleEntry.m_pipelineType == SamplePipelineType::RHI)
         {
-            BasicRHIComponent* rhiSampleComponent = static_cast<BasicRHIComponent*>(m_activeSample);
-            if (rhiSampleComponent->IsSupportedRHISamplePipeline())
+            for (AZ::RPI::Ptr<RHISamplePass> samplePass : m_rhiSamplePasses)
             {
-                m_rhiSamplePass->SetRHISample(rhiSampleComponent);
-            }
-            else
-            {
-                m_rhiSamplePass->SetRHISample(nullptr);
-            }
+                BasicRHIComponent* rhiSampleComponent = static_cast<BasicRHIComponent*>(m_exampleEntity->CreateComponent(sampleEntry.m_sampleUuid));
+                rhiSampleComponent->SetConfiguration(config);
+                rhiSampleComponent->SetViewIndex(samplePass->GetViewIndex());
+                if (rhiSampleComponent->IsSupportedRHISamplePipeline())
+                {
+                    samplePass->SetRHISample(rhiSampleComponent);
+                }
+                else
+                {
+                    samplePass->SetRHISample(nullptr);
+                }
+                m_activeSamples.push_back(rhiSampleComponent);
+            }   
         }
-
+        else
+        {
+            AZ::Component* newComponent = m_exampleEntity->CreateComponent(sampleEntry.m_sampleUuid);
+            newComponent->SetConfiguration(config);
+            m_activeSamples.push_back(newComponent);
+        }
+        
         m_exampleEntity->Activate();
 
         // Even though this is done in CameraReset(), the example component wasn't activated at the time so we have to send this event again.
@@ -1548,22 +1601,74 @@ namespace AtomSampleViewer
         m_rhiScene = RPI::Scene::CreateScene(sceneDesc);
         m_rhiScene->Activate();
 
-        RPI::RenderPipelineDescriptor pipelineDesc;
-        pipelineDesc.m_name = "RHISamplePipeline";
-        pipelineDesc.m_rootPassTemplate = "RHISamplePipelineTemplate";
-        // Add view to pipeline since there are few RHI samples are using ViewSrg
-        pipelineDesc.m_mainViewTagName = "MainCamera";
+        auto* xrSystem = AZ::RHI::RHISystemInterface::Get()->GetXRSystem();
+        const bool createDefaultRenderPipeline = !xrSystem || xrSystem->IsDefaultRenderPipelineNeeded();
 
-        RPI::RenderPipelinePtr renderPipeline = RPI::RenderPipeline::CreateRenderPipelineForWindow(pipelineDesc, *m_windowContext.get());
-        m_rhiScene->AddRenderPipeline(renderPipeline);
-        renderPipeline->SetDefaultViewFromEntity(m_cameraEntity->GetId());
+        if (createDefaultRenderPipeline)
+        {
+            RPI::RenderPipelineDescriptor pipelineDesc;
+            pipelineDesc.m_name = "RHISamplePipeline";
+            pipelineDesc.m_rootPassTemplate = "RHISamplePipelineTemplate";
+            // Add view to pipeline since there are few RHI samples are using ViewSrg
+            pipelineDesc.m_mainViewTagName = "MainCamera";
 
-        RPI::RPISystemInterface::Get()->RegisterScene(m_rhiScene);
+            m_renderPipeline = RPI::RenderPipeline::CreateRenderPipelineForWindow(pipelineDesc, *m_windowContext.get());
+            m_rhiScene->AddRenderPipeline(m_renderPipeline);
+            m_renderPipeline->SetDefaultViewFromEntity(m_cameraEntity->GetId());
 
-        // Get RHISamplePass
-        AZ::RPI::PassFilter passFilter = AZ::RPI::PassFilter::CreateWithPassName(AZ::Name("RHISamplePass"), renderPipeline.get());
-        m_rhiSamplePass = azrtti_cast<RHISamplePass*>(AZ::RPI::PassSystemInterface::Get()->FindFirstPass(passFilter));
+            // Get RHISamplePass
+            AZ::RPI::PassFilter passFilter = AZ::RPI::PassFilter::CreateWithPassName(AZ::Name("RHISamplePass"), m_renderPipeline.get());
+            m_rhiSamplePasses.push_back(azrtti_cast<RHISamplePass*>(AZ::RPI::PassSystemInterface::Get()->FindFirstPass(passFilter)));
 
+            // Enable or disable default pipeline on host while there is an xr system.
+            if (xrSystem)
+            {
+                EnableRenderPipeline(xrSystem->IsDefaultRenderPipelineEnabledOnHost());
+            }
+        }
+
+        if (xrSystem)
+        {
+            RPI::RenderPipelineDescriptor xrPipelineDesc;
+            xrPipelineDesc.m_mainViewTagName = "MainCamera";
+
+            // Build the pipeline for left eye
+            xrPipelineDesc.m_name = "RHISamplePipelineXRLeft";
+            xrPipelineDesc.m_rootPassTemplate = "RHISamplePipelineXRLeftTemplate";
+            RPI::RenderPipelinePtr renderPipelineLeft = RPI::RenderPipeline::CreateRenderPipelineForWindow(xrPipelineDesc, *m_windowContext.get(), AZ::RPI::ViewType::XrLeft);
+
+            // Build the pipeline for right eye
+            xrPipelineDesc.m_name = "RHISamplePipelineXRRight";
+            xrPipelineDesc.m_rootPassTemplate = "RHISamplePipelineXRRightTemplate";
+            RPI::RenderPipelinePtr renderPipelineRight = RPI::RenderPipeline::CreateRenderPipelineForWindow(xrPipelineDesc, *m_windowContext.get(), AZ::RPI::ViewType::XrRight);
+
+            //Add both the pipelines to the scene
+            m_rhiScene->AddRenderPipeline(renderPipelineLeft);
+            m_rhiScene->AddRenderPipeline(renderPipelineRight);
+            renderPipelineLeft->SetDefaultViewFromEntity(m_cameraEntity->GetId());
+            renderPipelineRight->SetDefaultViewFromEntity(m_cameraEntity->GetId());
+            
+            // Set the correct view index for the RHI sample passes
+            AZ::RPI::PassFilter rhiSamplePassFilterLeft = AZ::RPI::PassFilter::CreateWithPassName(AZ::Name("RHISamplePass"), renderPipelineLeft.get());
+            AZ::RPI::Ptr<RHISamplePass> rhiSamplePassLeft = azrtti_cast<RHISamplePass*>(AZ::RPI::PassSystemInterface::Get()->FindFirstPass(rhiSamplePassFilterLeft));
+            rhiSamplePassLeft->SetViewIndex(0);
+            m_rhiSamplePasses.push_back(rhiSamplePassLeft);
+
+            AZ::RPI::PassFilter rhiSamplePassFilterRight = AZ::RPI::PassFilter::CreateWithPassName(AZ::Name("RHISamplePass"), renderPipelineRight.get());
+            AZ::RPI::Ptr<RHISamplePass> rhiSamplePassRight = azrtti_cast<RHISamplePass*>(AZ::RPI::PassSystemInterface::Get()->FindFirstPass(rhiSamplePassFilterRight));
+            rhiSamplePassRight->SetViewIndex(1);
+            m_rhiSamplePasses.push_back(rhiSamplePassRight);
+
+            //Cache the pipelines in case we want to enable/disable them
+            m_xrPipelines.push_back(renderPipelineLeft);
+            m_xrPipelines.push_back(renderPipelineRight);
+
+            //Disable XR pipelines by default
+            EnableXrPipelines(false);
+        }
+
+        // Register the RHi scene
+        RPI::RPISystemInterface::Get()->RegisterScene(m_rhiScene);  
         // Setup imGui since a new render pipeline with imgui pass was created
         SetupImGuiContext();
     }
@@ -1572,7 +1677,9 @@ namespace AtomSampleViewer
     {
         if (m_rhiScene)
         {
-            m_rhiSamplePass = nullptr;
+            m_rhiSamplePasses.clear();
+            m_xrPipelines.clear();
+            m_renderPipeline = nullptr;
             RPI::RPISystemInterface::Get()->UnregisterScene(m_rhiScene);
             m_rhiScene = nullptr;
         }
@@ -1607,24 +1714,67 @@ namespace AtomSampleViewer
         // Register scene to RPI system so it will be processed/rendered per tick
         RPI::RPISystemInterface::Get()->RegisterScene(m_rpiScene);
 
-        // Create MainPipeline as its render pipeline
-        RPI::RenderPipelineDescriptor pipelineDesc;
-        pipelineDesc.m_name = "RPISamplePipeline";
-        pipelineDesc.m_rootPassTemplate = GetRootPassTemplateName();
-        pipelineDesc.m_mainViewTagName = "MainCamera";
-        pipelineDesc.m_allowModification = true;
-
         // set pipeline MSAA samples
         AZ_Assert(IsValidNumMSAASamples(m_numMSAASamples), "Invalid MSAA sample setting");
-        pipelineDesc.m_renderSettings.m_multisampleState.m_samples = static_cast<uint16_t>(m_numMSAASamples);
-        bool isNonMsaaPipeline = (pipelineDesc.m_renderSettings.m_multisampleState.m_samples == 1);
+        const bool isNonMsaaPipeline = (m_numMSAASamples == 1);
         const char* supervariantName = isNonMsaaPipeline ? AZ::RPI::NoMsaaSupervariantName : "";
         AZ::RPI::ShaderSystemInterface::Get()->SetSupervariantName(AZ::Name(supervariantName));
 
-        RPI::RenderPipelinePtr renderPipeline = RPI::RenderPipeline::CreateRenderPipelineForWindow(pipelineDesc, *m_windowContext.get());
-        m_rpiScene->AddRenderPipeline(renderPipeline);
+        auto* xrSystem = AZ::RHI::RHISystemInterface::Get()->GetXRSystem();
+        const bool createDefaultRenderPipeline = !xrSystem || xrSystem->IsDefaultRenderPipelineNeeded();
 
-        renderPipeline->SetDefaultViewFromEntity(m_cameraEntity->GetId());
+        if (createDefaultRenderPipeline)
+        {
+            // Create MainPipeline as its render pipeline
+            RPI::RenderPipelineDescriptor pipelineDesc;
+            pipelineDesc.m_name = "RPISamplePipeline";
+            pipelineDesc.m_rootPassTemplate = GetRootPassTemplateName();
+            pipelineDesc.m_mainViewTagName = "MainCamera";
+            pipelineDesc.m_allowModification = true;
+            pipelineDesc.m_renderSettings.m_multisampleState.m_samples = static_cast<uint16_t>(m_numMSAASamples);
+
+            m_renderPipeline = RPI::RenderPipeline::CreateRenderPipelineForWindow(pipelineDesc, *m_windowContext.get());
+            m_rpiScene->AddRenderPipeline(m_renderPipeline);
+
+            m_renderPipeline->SetDefaultViewFromEntity(m_cameraEntity->GetId());
+
+            // Enable or disable default pipeline on host while there is an xr system.
+            if (xrSystem)
+            {
+                EnableRenderPipeline(xrSystem->IsDefaultRenderPipelineEnabledOnHost());
+            }
+        }
+
+        if (xrSystem)
+        {
+            RPI::RenderPipelineDescriptor xrPipelineDesc;
+            xrPipelineDesc.m_mainViewTagName = "MainCamera";
+            xrPipelineDesc.m_renderSettings.m_multisampleState.m_samples = static_cast<uint16_t>(m_numMSAASamples);
+
+            // Build the pipeline for left eye
+            xrPipelineDesc.m_name = "RPISamplePipelineXRLeft";
+            xrPipelineDesc.m_rootPassTemplate = "LowEndPipelineXRLeftTemplate";
+            RPI::RenderPipelinePtr renderPipelineLeft = RPI::RenderPipeline::CreateRenderPipelineForWindow(xrPipelineDesc, *m_windowContext.get(), AZ::RPI::ViewType::XrLeft);
+
+            // Build the pipeline for right eye
+            xrPipelineDesc.m_name = "RHISamplePipelineXRRight";
+            xrPipelineDesc.m_rootPassTemplate = "LowEndPipelineXRRightTemplate";
+            RPI::RenderPipelinePtr renderPipelineRight = RPI::RenderPipeline::CreateRenderPipelineForWindow(xrPipelineDesc, *m_windowContext.get(), AZ::RPI::ViewType::XrRight);
+
+            //Add both the pipelines to the scene
+            m_rpiScene->AddRenderPipeline(renderPipelineLeft);
+            m_rpiScene->AddRenderPipeline(renderPipelineRight);
+
+            renderPipelineLeft->SetDefaultStereoscopicViewFromEntity(m_cameraEntity->GetId(), RPI::ViewType::XrLeft); //Left eye
+            renderPipelineRight->SetDefaultStereoscopicViewFromEntity(m_cameraEntity->GetId(), RPI::ViewType::XrRight); //Right eye
+
+            //Cache the pipelines in case we want to enable/disable them
+            m_xrPipelines.push_back(renderPipelineLeft);
+            m_xrPipelines.push_back(renderPipelineRight);
+
+            // Disable XR pipelines by default
+            EnableXrPipelines(false);
+        }
 
         // As part of our initialization we need to create the BRDF texture generation pipeline
         AZ::RPI::RenderPipelineDescriptor brdfPipelineDesc;
@@ -1636,7 +1786,7 @@ namespace AtomSampleViewer
         RPI::RenderPipelinePtr brdfTexturePipeline = AZ::RPI::RenderPipeline::CreateRenderPipeline(brdfPipelineDesc);
         m_rpiScene->AddRenderPipeline(brdfTexturePipeline);
         
-        // Save a reference to the generated BRDF texture so it doesn't get deleted if all the passes refering to it get deleted and it's ref count goes to zero
+        // Save a reference to the generated BRDF texture so it doesn't get deleted if all the passes referring to it get deleted and it's ref count goes to zero
         if (!m_brdfTexture)
         {
             const AZStd::shared_ptr<const RPI::PassTemplate> brdfTextureTemplate = RPI::PassSystemInterface::Get()->GetPassTemplate(Name("BRDFTextureTemplate"));
@@ -1665,6 +1815,7 @@ namespace AtomSampleViewer
             [[maybe_unused]] bool result = scene->UnsetSubsystem(m_rpiScene);
             AZ_Assert(result, "SampleComponentManager failed to unregister its RPI scene from the general scene.");
             
+            m_xrPipelines.clear();
             m_rpiScene = nullptr;
         }
     }
@@ -1684,5 +1835,13 @@ namespace AtomSampleViewer
                 ActivateInternal();
             });
     }
+    
+    void SampleComponentManager::SetRHISamplePass(BasicRHIComponent* sampleComponent)
+    {
+        for (AZ::RPI::Ptr<RHISamplePass> samplePass : m_rhiSamplePasses)
+        {
+            samplePass->SetRHISample(sampleComponent);
+        }
+    }
 
 } // namespace AtomSampleViewer

+ 9 - 2
Gem/Code/Source/SampleComponentManager.h

@@ -148,6 +148,7 @@ namespace AtomSampleViewer
         void SampleChange();
         void CameraReset();
         void ShutdownActiveSample();
+        void SetRHISamplePass(BasicRHIComponent* sampleComponent);
 
         // SampleComponentManagerRequestBus overrides...
         void Reset() override;
@@ -160,6 +161,8 @@ namespace AtomSampleViewer
         void ResetNumMSAASamples() override;
         void ResetRPIScene() override;
         void ClearRPIScene() override;
+        void EnableRenderPipeline(bool value) override;
+        void EnableXrPipelines(bool value) override;
 
         // FrameCaptureNotificationBus overrides...
         void OnFrameCaptureFinished(AZ::Render::FrameCaptureResult result, const AZStd::string& info) override;
@@ -192,7 +195,7 @@ namespace AtomSampleViewer
         // Entity to hold only example component. It doesn't need an entity context.
         AZ::Entity* m_exampleEntity = nullptr;
 
-        AZ::Component* m_activeSample = nullptr;
+        AZStd::vector<AZ::Component*> m_activeSamples;
 
         AZ::Entity* m_cameraEntity = nullptr;
 
@@ -260,12 +263,16 @@ namespace AtomSampleViewer
 
         // Scene and some variables for RHI samples
         AZ::RPI::ScenePtr m_rhiScene;
-        AZ::RPI::Ptr<RHISamplePass> m_rhiSamplePass = nullptr;
+        AZStd::vector<AZ::RPI::Ptr<RHISamplePass>> m_rhiSamplePasses;
 
         // Scene and some variables for RPI samples
         AZ::RPI::ScenePtr m_rpiScene;
 
         // number of MSAA samples, initialized in Activate() and can vary by platform
         int m_numMSAASamples = 0;
+
+        // Cache PC and XR pipelines
+        AZ::RPI::RenderPipelinePtr m_renderPipeline = nullptr;
+        AZStd::vector<AZ::RPI::RenderPipelinePtr> m_xrPipelines;
     };
 } // namespace AtomSampleViewer

+ 6 - 0
Gem/Code/Source/SampleComponentManagerBus.h

@@ -51,6 +51,12 @@ namespace AtomSampleViewer
 
         //! Clear the RPI scene
         virtual void ClearRPIScene() = 0;
+
+        //! Enables or disables the default render pipeline.
+        virtual void EnableRenderPipeline(bool value) = 0;
+
+        //! Enables or disables the XR pipelines.
+        virtual void EnableXrPipelines(bool value) = 0;
     };
     using SampleComponentManagerRequestBus = AZ::EBus<SampleComponentManagerRequests>;
 

+ 296 - 0
Gem/Code/Source/XRRPIExampleComponent.cpp

@@ -0,0 +1,296 @@
+/*
+ * 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 <XRRPIExampleComponent.h>
+#include <Atom/Component/DebugCamera/NoClipControllerComponent.h>
+#include <Atom/RPI.Public/Scene.h>
+#include <Atom/RPI.Public/RPISystem.h>
+#include <Atom/RPI.Public/RPISystemInterface.h>
+#include <Atom/RPI.Public/Pass/PassFilter.h>
+#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
+#include <Atom/RPI.Reflect/Model/ModelAsset.h>
+#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
+#include <Automation/ScriptableImGui.h>
+#include <Automation/ScriptRunnerBus.h>
+#include <Utils/Utils.h>
+
+#include <SSRExampleComponent_Traits_Platform.h>
+
+
+namespace AtomSampleViewer
+{
+    static const float ControllerOffsetScale = 2.0f;
+    static const float ViewOrientationScale = 10.0f;
+    static const float PixelToDegree = 1.0 / 360.0f;
+
+    void XRRPIExampleComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<XRRPIExampleComponent, AZ::Component>()
+                ->Version(0);
+        }
+    }
+
+    void XRRPIExampleComponent::Activate()
+    {
+        if (m_xrSystem = AZ::RPI::RPISystemInterface::Get()->GetXRSystem();
+            m_xrSystem == nullptr)
+        {
+            return;
+        }
+
+        AZ::TickBus::Handler::BusConnect();
+
+        // setup the camera
+        Camera::CameraRequestBus::EventResult(m_originalFarClipDistance, GetCameraEntityId(), &Camera::CameraRequestBus::Events::GetFarClipDistance);
+        Camera::CameraRequestBus::Event(GetCameraEntityId(), &Camera::CameraRequestBus::Events::SetFarClipDistance, 180.f);
+
+        m_numXrViews = m_xrSystem->GetNumViews();
+
+        // create scene
+        CreateModels();
+        CreateGroundPlane();
+
+        InitLightingPresets(true);
+    }
+
+    void XRRPIExampleComponent::Deactivate()
+    {
+        if (!m_xrSystem)
+        {
+            return;
+        }
+
+        ShutdownLightingPresets();
+
+        GetMeshFeatureProcessor()->ReleaseMesh(m_statueMeshHandle);
+        GetMeshFeatureProcessor()->ReleaseMesh(m_boxMeshHandle);
+        GetMeshFeatureProcessor()->ReleaseMesh(m_shaderBallMeshHandle);
+        GetMeshFeatureProcessor()->ReleaseMesh(m_groundMeshHandle);
+        GetMeshFeatureProcessor()->ReleaseMesh(m_leftControllerMeshHandle);
+        GetMeshFeatureProcessor()->ReleaseMesh(m_rightControllerMeshHandle);
+
+        Camera::CameraRequestBus::Event(GetCameraEntityId(), &Camera::CameraRequestBus::Events::SetFarClipDistance, m_originalFarClipDistance);
+        AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Disable);
+
+        AZ::TickBus::Handler::BusDisconnect();
+    }
+
+    void XRRPIExampleComponent::CreateModels()
+    {
+        GetMeshFeatureProcessor();
+
+        // statue
+        {
+            AZ::Data::Asset<AZ::RPI::MaterialAsset> materialAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::MaterialAsset>("objects/hermanubis/hermanubis_stone.azmaterial", AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>(ATOMSAMPLEVIEWER_TRAIT_SSR_SAMPLE_HERMANUBIS_MODEL_NAME, AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Transform transform = AZ::Transform::CreateIdentity();
+            transform.SetTranslation(0.0f, 0.0f, -0.05f);
+
+            m_statueMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ modelAsset }, AZ::RPI::Material::FindOrCreate(materialAsset));
+            GetMeshFeatureProcessor()->SetTransform(m_statueMeshHandle, transform);
+        }
+
+        // cube
+        {
+            AZ::Data::Asset<AZ::RPI::MaterialAsset> materialAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::MaterialAsset>("materials/ssrexample/cube.azmaterial", AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>("objects/cube.azmodel", AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Transform transform = AZ::Transform::CreateIdentity();
+            transform.SetTranslation(-4.5f, 0.0f, 0.49f);
+
+            m_boxMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ modelAsset }, AZ::RPI::Material::FindOrCreate(materialAsset));
+            GetMeshFeatureProcessor()->SetTransform(m_boxMeshHandle, transform);
+        }
+
+        // shader ball
+        {
+            AZ::Data::Asset<AZ::RPI::MaterialAsset> materialAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::MaterialAsset>("Materials/Presets/PBR/default_grid.azmaterial", AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>("objects/ShaderBall_simple.azmodel", AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Transform transform = AZ::Transform::CreateIdentity();
+            transform *= AZ::Transform::CreateRotationZ(AZ::Constants::Pi);
+            transform.SetTranslation(4.5f, 0.0f, 0.89f);
+
+            m_shaderBallMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ modelAsset }, AZ::RPI::Material::FindOrCreate(materialAsset));
+            GetMeshFeatureProcessor()->SetTransform(m_shaderBallMeshHandle, transform);
+        }
+
+        // controller meshes
+        {
+            AZ::Data::Asset<AZ::RPI::MaterialAsset> materialAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::MaterialAsset>("Materials/XR/XR_Hand_Controller_ControlerMAT.azmaterial", AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>("objects/left_hand_controller.azmodel", AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Transform transform = AZ::Transform::CreateIdentity();
+
+            m_leftControllerMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ modelAsset }, AZ::RPI::Material::FindOrCreate(materialAsset));
+            m_rightControllerMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ modelAsset }, AZ::RPI::Material::FindOrCreate(materialAsset));
+            GetMeshFeatureProcessor()->SetTransform(m_leftControllerMeshHandle, transform);
+            GetMeshFeatureProcessor()->SetTransform(m_rightControllerMeshHandle, transform);
+        }
+    }
+
+    void XRRPIExampleComponent::CreateGroundPlane()
+    {
+        AZ::Render::MeshFeatureProcessorInterface* meshFeatureProcessor = GetMeshFeatureProcessor();
+        if (m_groundMeshHandle.IsValid())
+        {
+            meshFeatureProcessor->ReleaseMesh(m_groundMeshHandle);
+        }
+
+        // load material
+        AZStd::string materialName;
+        switch (m_groundPlaneMaterial)
+        {
+        case 0:
+            materialName = "materials/ssrexample/groundplanechrome.azmaterial";
+            break;
+        case 1:
+            materialName = "materials/ssrexample/groundplanealuminum.azmaterial";
+            break;
+        case 2:
+            materialName = "materials/presets/pbr/default_grid.azmaterial";
+            break;
+        default:
+            materialName = "materials/ssrexample/groundplanemirror.azmaterial";
+            break;
+        }
+
+        AZ::Data::AssetId groundMaterialAssetId = AZ::RPI::AssetUtils::GetAssetIdForProductPath(materialName.c_str(), AZ::RPI::AssetUtils::TraceLevel::Error);
+        m_groundMaterialAsset.Create(groundMaterialAssetId);
+
+        // load mesh
+        AZ::Data::Asset<AZ::RPI::ModelAsset> planeModel = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>("objects/plane.azmodel", AZ::RPI::AssetUtils::TraceLevel::Error);
+        m_groundMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ planeModel }, AZ::RPI::Material::FindOrCreate(m_groundMaterialAsset));
+
+        AZ::Transform transform = AZ::Transform::CreateIdentity();
+        const AZ::Vector3 nonUniformScale(15.0f, 15.0f, 1.0f);
+        GetMeshFeatureProcessor()->SetTransform(m_groundMeshHandle, transform, nonUniformScale);
+    }
+
+    void XRRPIExampleComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint timePoint)
+    {
+        if (m_resetCamera)
+        {
+            AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Reset);
+            AZ::TransformBus::Event(GetCameraEntityId(), &AZ::TransformBus::Events::SetWorldTranslation, AZ::Vector3(7.5f, -10.5f, 3.0f));
+            AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Enable, azrtti_typeid<AZ::Debug::NoClipControllerComponent>());
+            AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetHeading, AZ::DegToRad(22.5f));
+            AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetPitch, AZ::DegToRad(-10.0f));
+            m_resetCamera = false;
+        }
+
+        if (m_xrSystem && m_xrSystem->ShouldRender())
+        {
+            AZ::RPI::PoseData frontPoseData;
+            AZ::RHI::ResultCode resultCode = m_xrSystem->GetViewLocalPose(frontPoseData);
+            for (AZ::u32 i = 0; i < m_numXrViews; i++)
+            {
+                //Pose data for the controller
+                AZ::RPI::PoseData controllerPose;
+                resultCode = m_xrSystem->GetControllerPose(i, controllerPose);
+
+                if(resultCode == AZ::RHI::ResultCode::Success && !controllerPose.m_orientation.IsZero())
+                {
+                    AZ::Vector3 camPosition;
+                    AZ::TransformBus::EventResult(camPosition, GetCameraEntityId(), &AZ::TransformBus::Events::GetWorldTranslation);
+                    AZ::Vector3 controllerPositionOffset = controllerPose.m_position * ControllerOffsetScale;
+                    AZ::Vector3 newControllerPos = camPosition + AZ::Vector3(controllerPositionOffset.GetX(), -controllerPositionOffset.GetZ(), controllerPositionOffset.GetY());
+
+                    // Go from y up to z up as a right handed coord system
+                    AZ::Quaternion controllerOrientation = controllerPose.m_orientation;
+                    controllerOrientation.SetX(controllerPose.m_orientation.GetX());
+                    controllerOrientation.SetY(-controllerPose.m_orientation.GetZ());
+                    controllerOrientation.SetZ(controllerPose.m_orientation.GetY());
+
+                    //Apply a Rotation of 90 deg around X axis in order to orient the model to face away from you as default pose
+                    AZ::Transform controllerTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
+                                controllerOrientation * AZ::Quaternion::CreateRotationX(-AZ::Constants::Pi / 2), AZ::Vector3(newControllerPos.GetX(), newControllerPos.GetY(),newControllerPos.GetZ()));
+
+                    AZ::Render::MeshFeatureProcessorInterface::MeshHandle* controllerMeshhandle = &m_leftControllerMeshHandle;
+                    if (i == 1)
+                    {
+                        controllerMeshhandle = &m_rightControllerMeshHandle;
+                    }
+                    GetMeshFeatureProcessor()->SetTransform(*controllerMeshhandle, controllerTransform, AZ::Vector3(m_xrSystem->GetControllerScale(i)));
+                }
+            }
+
+            //Update Camera movement (left, right forward, back) based on left JoyStick controller
+            float m_xJoyStickValue = m_xrSystem->GetXJoyStickState(0);
+            float m_yJoyStickValue = m_xrSystem->GetYJoyStickState(0);
+            AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetCameraStateForward, m_yJoyStickValue);
+            AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetCameraStateBack, m_yJoyStickValue);
+            AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetCameraStateLeft, m_xJoyStickValue);
+            AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetCameraStateRight, m_xJoyStickValue);
+
+            //Update Camera movement (Up, Down) based on X,Y button presses on the left controller
+            float yButtonState = m_xrSystem->GetYButtonState();
+            float xButtonState = m_xrSystem->GetXButtonState();
+            AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetCameraStateUp, yButtonState);
+            AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetCameraStateDown, xButtonState);
+
+            // Switch to updating the view using right joystick controller if the Trigger button on the right controller is pressed
+            m_rightTriggerButtonPressed = (m_xrSystem->GetTriggerState(1) > 0.1f);
+            if (m_rightTriggerButtonPressed)
+            {
+                //Update Camera view based on right JoyStick controller
+                float m_xRightJoyStickValue = m_xrSystem->GetXJoyStickState(1);
+                float m_yRightJoyStickValue = m_xrSystem->GetYJoyStickState(1);
+                float heading, pitch = 0.0f;
+                AZ::Debug::NoClipControllerRequestBus::EventResult(heading, GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::GetHeading);
+                AZ::Debug::NoClipControllerRequestBus::EventResult(pitch, GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::GetPitch);
+                heading -= m_xRightJoyStickValue * PixelToDegree * ViewOrientationScale;
+                pitch += m_yRightJoyStickValue * PixelToDegree * ViewOrientationScale;
+                pitch = AZStd::max(pitch, -AZ::Constants::HalfPi);
+                pitch = AZStd::min(pitch, AZ::Constants::HalfPi);
+                AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetHeading, heading);
+                AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetPitch, pitch);
+            }
+            else
+            {
+                //Convert to O3de's coordinate system and update the camera orientation for the correct eye view
+                AZ::Quaternion viewLocalPoseOrientation = frontPoseData.m_orientation;
+                viewLocalPoseOrientation.SetX(-frontPoseData.m_orientation.GetX());
+                viewLocalPoseOrientation.SetY(frontPoseData.m_orientation.GetZ());
+                viewLocalPoseOrientation.SetZ(-frontPoseData.m_orientation.GetY());
+                for (AZ::u32 i = 0; i < m_numXrViews; i++)
+                {
+                    Camera::CameraRequestBus::Event(GetCameraEntityId(), &Camera::CameraRequestBus::Events::SetXRViewQuaternion, viewLocalPoseOrientation, i);
+                }
+            }
+
+            // Switch to the next lighting preset using the B-button
+            if (m_xrSystem->GetBButtonState() > 0.0f)
+            {
+                if (!m_bButtonPressed)
+                {
+                    m_bButtonPressed = true;
+                    IterateToNextLightingPreset();
+                }
+            }
+            else
+            {
+                m_bButtonPressed = false;
+            }
+
+            // Switch to the next ground floor using the A-button
+            if (m_xrSystem->GetAButtonState() > 0.0f)
+            {
+                if (!m_aButtonPressed)
+                {
+                    m_aButtonPressed = true;
+                    m_groundPlaneMaterial = (m_groundPlaneMaterial + 1) % 4;
+                    CreateGroundPlane();
+                }
+            }
+            else
+            {
+                m_aButtonPressed = false;
+            }
+        }
+    }
+}

+ 86 - 0
Gem/Code/Source/XRRPIExampleComponent.h

@@ -0,0 +1,86 @@
+/*
+ * 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 <Atom/Component/DebugCamera/CameraComponent.h>
+#include <Atom/Feature/Mesh/MeshFeatureProcessorInterface.h>
+#include <Atom/Feature/ImGui/ImGuiUtils.h>
+#include <CommonSampleComponentBase.h>
+#include <Utils/ImGuiSidebar.h>
+#include <Utils/Utils.h>
+
+
+namespace AtomSampleViewer
+{
+    //!
+    //! This component creates a simple scene that tests VR using a special multi-view VR pipeline. We setup two pipelines, one for each eye and use stereoscopic view for this pipeline.
+    //! This sample supports Quest 2 controllers to fly around the world. It also has support to use button presses for specific functionality 
+    //! in the scene. The schema for each controller is below
+    //! Left controller
+    //!         Joystick - Camera movement, Button X - Camera Up (View space y-axis), Button Y - Camera Down (View space Y axis), Squeeze - Scales Controller model     
+     //! Right controller
+    //!         Joystick - View Orientation if Trigger button is pressed, otherwise it will use device for view tracking, 
+    //!         Button B - Iterate through lighting preset, Button B - Iterate through ground plane material, Squeeze - Scales Controller model     
+    //!
+    class XRRPIExampleComponent final
+        : public CommonSampleComponentBase
+        , public AZ::TickBus::Handler
+    {
+    public:
+        AZ_COMPONENT(AtomSampleViewer::XRRPIExampleComponent, "{3122B48E-2553-4568-8B8B-532C105CB83B}", CommonSampleComponentBase);
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        XRRPIExampleComponent() = default;
+        ~XRRPIExampleComponent() override = default;
+
+        void Activate() override;
+        void Deactivate() override;
+
+    private:
+        AZ_DISABLE_COPY_MOVE(XRRPIExampleComponent);
+
+        void CreateModels();
+        void CreateGroundPlane();
+        
+        // AZ::TickBus::Handler
+        void OnTick(float deltaTime, AZ::ScriptTimePoint timePoint) override;
+
+        void OnModelReady(AZ::Data::Instance<AZ::RPI::Model> model);
+
+        // meshes
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_statueMeshHandle;
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_boxMeshHandle;
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_shaderBallMeshHandle;
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_groundMeshHandle;
+        AZ::Data::Asset<AZ::RPI::MaterialAsset> m_groundMaterialAsset;
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_leftControllerMeshHandle;
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_rightControllerMeshHandle;
+
+        // ground plane default material setting index
+        int m_groundPlaneMaterial = 2;
+
+        // IBL and skybox
+        Utils::DefaultIBL m_defaultIbl;
+        AZ::Data::Asset<AZ::RPI::StreamingImageAsset> m_skyboxImageAsset;
+
+        bool m_resetCamera = true;
+        float m_originalFarClipDistance = 0.0f;
+
+        bool m_xButtonPressed = false;
+        bool m_yButtonPressed = false;
+        bool m_aButtonPressed = false;
+        bool m_bButtonPressed = false;
+        bool m_rightTriggerButtonPressed = false;
+
+        AZ::RPI::XRRenderingInterface* m_xrSystem = nullptr;
+        AZ::u32 m_numXrViews = 0;
+    };
+} // namespace AtomSampleViewer

+ 4 - 0
Gem/Code/atomsampleviewergem_private_files.cmake

@@ -85,6 +85,8 @@ set(FILES
     Source/RHI/RayTracingExampleComponent.h
     Source/RHI/MatrixAlignmentTestExampleComponent.cpp
     Source/RHI/MatrixAlignmentTestExampleComponent.h
+    Source/RHI/XRExampleComponent.cpp
+    Source/RHI/XRExampleComponent.h
     Source/Performance/HighInstanceExampleComponent.cpp
     Source/Performance/HighInstanceExampleComponent.h
     Source/Performance/100KDrawable_SingleView_ExampleComponent.cpp
@@ -194,4 +196,6 @@ set(FILES
     Source/Utils/Utils.h
     Source/Utils/ImGuiProgressList.cpp
     Source/Utils/ImGuiProgressList.h
+    Source/XRRPIExampleComponent.cpp
+    Source/XRRPIExampleComponent.h
 )

+ 2 - 0
Gem/Code/enabled_gems.cmake

@@ -24,4 +24,6 @@ set(ENABLED_GEMS
     UiBasics
     StreamerProfiler
     DiffuseProbeGrid
+    XR
+    OpenXRVk
 )

+ 26 - 0
Materials/XR/XR_Hand_Controller_ControlerMAT.material

@@ -0,0 +1,26 @@
+{
+    "materialType": "Materials/Types/StandardPBR.materialtype",
+    "materialTypeVersion": 5,
+    "propertyValues": {
+        "baseColor.color": [
+            0.800000011920929,
+            0.800000011920929,
+            0.800000011920929,
+            1.0
+        ],
+        "baseColor.textureBlendMode": "Lerp",
+        "baseColor.textureMap": "Materials/XR/Textures/ControlerMAT_BaseColor.png",
+        "emissive.color": [
+            0.0,
+            0.9666590094566345,
+            1.0,
+            1.0
+        ],
+        "emissive.enable": true,
+        "emissive.textureMap": "Materials/XR/Textures/ControlerMAT_Emissive.png",
+        "irradiance.irradianceColorSource": "BaseColor",
+        "opacity.factor": 1.0,
+        "roughness.textureMap": "Materials/XR/Textures/ControlerMAT_Roughness.png",
+        "specularF0.factor": 0.10000000149011612
+    }
+}

+ 3 - 0
Materials/XR/textures/ControlerMAT_BaseColor.png

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

+ 3 - 0
Materials/XR/textures/ControlerMAT_Emissive.png

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

+ 3 - 0
Materials/XR/textures/ControlerMAT_Roughness.png

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

+ 3 - 0
Objects/Left_Hand_Controller.fbx

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

+ 3 - 0
Objects/Right_Hand_Controller.fbx

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

+ 8 - 0
Passes/ASV/PassTemplates.azasset

@@ -36,6 +36,14 @@
                 "Name": "RHISamplePipelineTemplate",
                 "Path": "Passes/RHISamplePipeline.pass"
             },
+            {
+                "Name": "RHISamplePipelineXRLeftTemplate",
+                "Path": "Passes/RHISamplePipelineXRLeft.pass"
+            },
+            {
+                "Name": "RHISamplePipelineXRRightTemplate",
+                "Path": "Passes/RHISamplePipelineXRRight.pass"
+            },
             {
                 "Name": "SsaoPipeline",
                 "Path": "Passes/SsaoPipeline.pass"

+ 71 - 0
Passes/RHISamplePipelineXRLeft.pass

@@ -0,0 +1,71 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "RHISamplePipelineXRLeftTemplate",
+            "PassClass": "ParentPass",
+            "Slots": [
+                {
+                    "Name": "PipelineOutput",
+                    "SlotType": "InputOutput"
+                }
+            ],
+            "PassData": {
+                "$type": "PassData",
+                "PipelineGlobalConnections": [
+                    {
+                        "GlobalName": "PipelineOutput",
+                        "Slot": "PipelineOutput"
+                    }
+                ]
+            },
+            "PassRequests": [
+                {
+                    "Name": "RHISamplePass",
+                    "TemplateName": "RHISamplePassTemplate",
+                    "Enabled": true
+                },
+                {
+                    "Name": "ImGuiPass",
+                    "TemplateName": "ImGuiPassTemplate",
+                    "Enabled": true,
+                    "Connections": [
+                        {
+                            "LocalSlot": "InputOutput",
+                            "AttachmentRef": {
+                                "Pass": "RHISamplePass",
+                                "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"
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}

+ 71 - 0
Passes/RHISamplePipelineXRRight.pass

@@ -0,0 +1,71 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "RHISamplePipelineXRRightTemplate",
+            "PassClass": "ParentPass",
+            "Slots": [
+                {
+                    "Name": "PipelineOutput",
+                    "SlotType": "InputOutput"
+                }
+            ],
+            "PassData": {
+                "$type": "PassData",
+                "PipelineGlobalConnections": [
+                    {
+                        "GlobalName": "PipelineOutput",
+                        "Slot": "PipelineOutput"
+                    }
+                ]
+            },
+            "PassRequests": [
+                {
+                    "Name": "RHISamplePass",
+                    "TemplateName": "RHISamplePassTemplate",
+                    "Enabled": true
+                },
+                {
+                    "Name": "ImGuiPass",
+                    "TemplateName": "ImGuiPassTemplate",
+                    "Enabled": true,
+                    "Connections": [
+                        {
+                            "LocalSlot": "InputOutput",
+                            "AttachmentRef": {
+                                "Pass": "RHISamplePass",
+                                "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"
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}

+ 49 - 0
Shaders/RHI/OpenXrSample.azsl

@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <Atom/Features/SrgSemantics.azsli>
+
+ShaderResourceGroup OpenXrSrg : SRG_PerObject
+{
+    row_major float4x4 m_worldMatrix;
+    row_major float4x4 m_viewProjMatrix;
+}
+
+struct VSInput
+{
+    float3 m_position : POSITION;
+    float4 m_color : COLOR0;
+};
+
+struct VSOutput
+{
+    float4 m_position : SV_Position;
+    float4 m_color : COLOR0;
+};
+
+VSOutput MainVS(VSInput vsInput)
+{
+    VSOutput OUT;
+    
+    OUT.m_position = mul(OpenXrSrg::m_worldMatrix, float4(vsInput.m_position, 1.0));
+    OUT.m_position = mul(OpenXrSrg::m_viewProjMatrix, OUT.m_position);
+    OUT.m_color = vsInput.m_color;
+    return OUT;
+}
+
+struct PSOutput
+{
+    float4 m_color : SV_Target0;
+};
+
+PSOutput MainPS(VSOutput vsOutput)
+{
+    PSOutput OUT;
+    OUT.m_color = vsOutput.m_color;
+    return OUT;
+}

+ 23 - 0
Shaders/RHI/OpenXrSample.shader

@@ -0,0 +1,23 @@
+{
+    "Source" : "OpenXrSample.azsl",
+
+    "DepthStencilState" : { 
+        "Depth" : { "Enable" : false, "CompareFunc" : "Less" }
+    },
+    "DrawList" : "forward",
+
+    "ProgramSettings":
+    {
+      "EntryPoints":
+      [
+        {
+          "name": "MainVS",
+          "type": "Vertex"
+        },
+        {
+          "name": "MainPS",
+          "type": "Fragment"
+        }
+      ]
+    }
+}

+ 3 - 0
Textures/ControlerMAT_BaseColor.png

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

+ 3 - 0
Textures/ControlerMAT_Emissive.png

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

+ 3 - 0
Textures/ControlerMAT_Roughness.png

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

+ 11 - 1
project.json

@@ -13,5 +13,15 @@
     ],
     "icon_path": "preview.png",
     "engine": "o3de",
-    "external_subdirectories": []
+    "external_subdirectories": [],
+    "gem_names": [
+        {
+            "name": "OpenXRVk",
+            "optional": true
+        },
+        {
+            "name": "XR",
+            "optional": true
+        }
+    ]
 }