// XRSystem is where information about XR views and passes are read from 2 exclusive sources: // - XRDisplaySubsystem from the XR SDK // - custom XR layout (only internal for now) using System; using System.Collections.Generic; #if DEVELOPMENT_BUILD || UNITY_EDITOR using System.Diagnostics; #endif #if ENABLE_VR && ENABLE_XR_MODULE using UnityEngine.XR; #endif namespace UnityEngine.Rendering.HighDefinition { internal partial class XRSystem { // Valid empty pass when a camera is not using XR internal readonly XRPass emptyPass = new XRPass(); // Store active passes and avoid allocating memory every frames List<(Camera, XRPass)> framePasses = new List<(Camera, XRPass)>(); // XRTODO: expose and document public API for custom layout internal delegate bool CustomLayout(XRLayout layout); private static CustomLayout customLayout = null; static internal void SetCustomLayout(CustomLayout cb) => customLayout = cb; #if ENABLE_VR && ENABLE_XR_MODULE // XR SDK display interface static List displayList = new List(); XRDisplaySubsystem display = null; // Internal resources used by XR rendering Material occlusionMeshMaterial = null; Material mirrorViewMaterial = null; MaterialPropertyBlock mirrorViewMaterialProperty = new MaterialPropertyBlock(); #endif #if DEVELOPMENT_BUILD || UNITY_EDITOR internal static bool dumpDebugInfo = false; internal static List passDebugInfos = new List(8); internal static string ReadPassDebugInfo(int i) => passDebugInfos[i]; #endif internal XRSystem(RenderPipelineResources.ShaderResources shaders) { #if ENABLE_VR && ENABLE_XR_MODULE RefreshXrSdk(); if (shaders != null) { occlusionMeshMaterial = CoreUtils.CreateEngineMaterial(shaders.xrOcclusionMeshPS); mirrorViewMaterial = CoreUtils.CreateEngineMaterial(shaders.xrMirrorViewPS); } #endif // XRTODO: replace by dynamic render graph TextureXR.maxViews = Math.Max(TextureXR.slices, GetMaxViews()); } #if ENABLE_VR && ENABLE_XR_MODULE // With XR SDK: disable legacy VR system before rendering first frame [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] internal static void XRSystemInit() { if (GraphicsSettings.currentRenderPipeline == null) return; #if UNITY_2020_2_OR_NEWER SubsystemManager.GetSubsystems(displayList); #else SubsystemManager.GetInstances(displayList); #endif for (int i = 0; i < displayList.Count; i++) { displayList[i].disableLegacyRenderer = true; displayList[i].sRGB = true; displayList[i].textureLayout = XRDisplaySubsystem.TextureLayout.Texture2DArray; } } #endif // Compute the maximum number of views (slices) to allocate for texture arrays internal int GetMaxViews() { int maxViews = 1; #if ENABLE_VR && ENABLE_XR_MODULE if (display != null) { // XRTODO : replace by API from XR SDK, assume we have 2 slices until then maxViews = 2; } #endif if (XRGraphicsAutomatedTests.enabled) maxViews = Math.Max(maxViews, 2); return maxViews; } #if UNITY_2021_1_OR_NEWER internal List<(Camera, XRPass)> SetupFrame(List cameras, bool singlePassAllowed, bool singlePassTestModeActive) #else internal List<(Camera, XRPass)> SetupFrame(Camera[] cameras, bool singlePassAllowed, bool singlePassTestModeActive) #endif { bool xrActive = RefreshXrSdk(); if (framePasses.Count > 0) { Debug.LogWarning("XRSystem.ReleaseFrame() was not called!"); ReleaseFrame(); } #if DEVELOPMENT_BUILD || UNITY_EDITOR bool singlePassTestMode = (singlePassTestModeActive || XRGraphicsAutomatedTests.running) && XRGraphicsAutomatedTests.enabled; #endif foreach (var camera in cameras) { if (camera == null) continue; // Enable XR layout only for gameview camera bool xrSupported = camera.cameraType == CameraType.Game && camera.targetTexture == null && HDUtils.TryGetAdditionalCameraDataOrDefault(camera).xrRendering; #if DEVELOPMENT_BUILD || UNITY_EDITOR if (singlePassTestMode && LayoutSinglePassTestMode(new XRLayout() { camera = camera, xrSystem = this })) { // single-pass test layout in used } else #endif if (customLayout != null && customLayout(new XRLayout() { camera = camera, xrSystem = this })) { // custom layout in used } #if ENABLE_VR && ENABLE_XR_MODULE else if (xrActive && xrSupported) { // Disable vsync on the main display when rendering to a XR device QualitySettings.vSyncCount = 0; if (display != null) { display.zNear = camera.nearClipPlane; display.zFar = camera.farClipPlane; } CreateLayoutFromXrSdk(camera, singlePassAllowed); } #endif else { AddPassToFrame(camera, emptyPass); } } CaptureDebugInfo(); return framePasses; } internal void ReleaseFrame() { foreach ((Camera _, XRPass xrPass) in framePasses) { if (xrPass != emptyPass) XRPass.Release(xrPass); } framePasses.Clear(); } bool RefreshXrSdk() { #if ENABLE_VR && ENABLE_XR_MODULE #if UNITY_2020_2_OR_NEWER SubsystemManager.GetSubsystems(displayList); #else SubsystemManager.GetInstances(displayList); #endif if (displayList.Count > 0) { if (displayList.Count > 1) throw new NotImplementedException("Only 1 XR display is supported."); display = displayList[0]; display.disableLegacyRenderer = true; display.textureLayout = XRDisplaySubsystem.TextureLayout.Texture2DArray; return display.running; } else { display = null; } #endif return false; } void CreateLayoutFromXrSdk(Camera camera, bool singlePassAllowed) { #if ENABLE_VR && ENABLE_XR_MODULE bool CanUseSinglePass(XRDisplaySubsystem.XRRenderPass renderPass) { if (renderPass.renderTargetDesc.dimension != TextureDimension.Tex2DArray) return false; if (renderPass.GetRenderParameterCount() != 2 || renderPass.renderTargetDesc.volumeDepth != 2) return false; renderPass.GetRenderParameter(camera, 0, out var renderParam0); renderPass.GetRenderParameter(camera, 1, out var renderParam1); if (renderParam0.textureArraySlice != 0 || renderParam1.textureArraySlice != 1) return false; if (renderParam0.viewport != renderParam1.viewport) return false; return true; } for (int renderPassIndex = 0; renderPassIndex < display.GetRenderPassCount(); ++renderPassIndex) { display.GetRenderPass(renderPassIndex, out var renderPass); display.GetCullingParameters(camera, renderPass.cullingPassIndex, out var cullingParams); // Disable legacy stereo culling path cullingParams.cullingOptions &= ~CullingOptions.Stereo; if (singlePassAllowed && CanUseSinglePass(renderPass)) { var xrPass = XRPass.Create(renderPass, multipassId: framePasses.Count, cullingParams, occlusionMeshMaterial); for (int renderParamIndex = 0; renderParamIndex < renderPass.GetRenderParameterCount(); ++renderParamIndex) { renderPass.GetRenderParameter(camera, renderParamIndex, out var renderParam); xrPass.AddView(renderPass, renderParam); } AddPassToFrame(camera, xrPass); } else { for (int renderParamIndex = 0; renderParamIndex < renderPass.GetRenderParameterCount(); ++renderParamIndex) { renderPass.GetRenderParameter(camera, renderParamIndex, out var renderParam); var xrPass = XRPass.Create(renderPass, multipassId: framePasses.Count, cullingParams, occlusionMeshMaterial); xrPass.AddView(renderPass, renderParam); AddPassToFrame(camera, xrPass); } } } #endif } internal void Cleanup() { customLayout = null; #if ENABLE_VR && ENABLE_XR_MODULE CoreUtils.Destroy(occlusionMeshMaterial); CoreUtils.Destroy(mirrorViewMaterial); #endif } internal void AddPassToFrame(Camera camera, XRPass xrPass) { xrPass.UpdateOcclusionMesh(); framePasses.Add((camera, xrPass)); } internal void RenderMirrorView(CommandBuffer cmd) { #if ENABLE_VR && ENABLE_XR_MODULE if (display == null || !display.running) return; using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.XRMirrorView))) { cmd.SetRenderTarget(BuiltinRenderTextureType.CameraTarget); int mirrorBlitMode = display.GetPreferredMirrorBlitMode(); if (display.GetMirrorViewBlitDesc(null, out var blitDesc, mirrorBlitMode)) { if (blitDesc.nativeBlitAvailable) { display.AddGraphicsThreadMirrorViewBlit(cmd, blitDesc.nativeBlitInvalidStates, mirrorBlitMode); } else { for (int i = 0; i < blitDesc.blitParamsCount; ++i) { blitDesc.GetBlitParameter(i, out var blitParam); Vector4 scaleBias = new Vector4(blitParam.srcRect.width, blitParam.srcRect.height, blitParam.srcRect.x, blitParam.srcRect.y); Vector4 scaleBiasRT = new Vector4(blitParam.destRect.width, blitParam.destRect.height, blitParam.destRect.x, blitParam.destRect.y); mirrorViewMaterialProperty.SetTexture(HDShaderIDs._BlitTexture, blitParam.srcTex); mirrorViewMaterialProperty.SetVector(HDShaderIDs._BlitScaleBias, scaleBias); mirrorViewMaterialProperty.SetVector(HDShaderIDs._BlitScaleBiasRt, scaleBiasRT); mirrorViewMaterialProperty.SetInt(HDShaderIDs._BlitTexArraySlice, blitParam.srcTexArraySlice); int shaderPass = (blitParam.srcTex.dimension == TextureDimension.Tex2DArray) ? 1 : 0; cmd.DrawProcedural(Matrix4x4.identity, mirrorViewMaterial, shaderPass, MeshTopology.Quads, 4, 1, mirrorViewMaterialProperty); } } } else { cmd.ClearRenderTarget(true, true, Color.black); } } #endif } void CaptureDebugInfo() { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (dumpDebugInfo) { passDebugInfos.Clear(); for (int passIndex = 0; passIndex < framePasses.Count; passIndex++) { var pass = framePasses[passIndex].Item2; for (int viewIndex = 0; viewIndex < pass.viewCount; viewIndex++) { var viewport = pass.GetViewport(viewIndex); passDebugInfos.Add(string.Format(" Pass {0} Cull {1} View {2} Slice {3} : {4} x {5}", pass.multipassId, pass.cullingPassId, viewIndex, pass.GetTextureArraySlice(viewIndex), viewport.width, viewport.height)); } } } while (passDebugInfos.Count < passDebugInfos.Capacity) passDebugInfos.Add("inactive"); #endif } #if DEVELOPMENT_BUILD || UNITY_EDITOR bool LayoutSinglePassTestMode(XRLayout frameLayout) { Camera camera = frameLayout.camera; if (camera != null && camera.cameraType == CameraType.Game && camera.TryGetCullingParameters(false, out var cullingParams)) { cullingParams.stereoProjectionMatrix = camera.projectionMatrix; cullingParams.stereoViewMatrix = camera.worldToCameraMatrix; var passInfo = new XRPassCreateInfo { multipassId = 0, cullingPassId = 0, cullingParameters = cullingParams, renderTarget = camera.targetTexture, customMirrorView = null }; var viewInfo2 = new XRViewCreateInfo { projMatrix = camera.projectionMatrix, viewMatrix = camera.worldToCameraMatrix, viewport = new Rect(camera.pixelRect.x, camera.pixelRect.y, camera.pixelWidth, camera.pixelHeight), textureArraySlice = -1 }; // Change the first view so that it's a different viewpoint and projection to detect more issues var viewInfo1 = viewInfo2; var planes = viewInfo1.projMatrix.decomposeProjection; planes.left *= 0.44f; planes.right *= 0.88f; planes.top *= 0.11f; planes.bottom *= 0.33f; viewInfo1.projMatrix = Matrix4x4.Frustum(planes); viewInfo1.viewMatrix *= Matrix4x4.Translate(new Vector3(.34f, 0.25f, -0.08f)); // single-pass 2x rendering { XRPass pass = frameLayout.CreatePass(passInfo); frameLayout.AddViewToPass(viewInfo1, pass); frameLayout.AddViewToPass(viewInfo2, pass); } // valid layout return true; } return false; } #endif } }