using System; using System.Collections.Generic; using UnityEngine.Rendering; #if ENABLE_VR && ENABLE_XR_MODULE using UnityEngine.XR; #endif namespace UnityEngine.Experimental.Rendering { /// /// Used by render pipelines to communicate with XR SDK. /// public static class XRSystem { // Keep track of only one XR layout static XRLayout s_Layout = new XRLayout(); // Delegate allocations of XRPass to the render pipeline static Func s_PassAllocator = null; #if ENABLE_VR && ENABLE_XR_MODULE static List s_DisplayList = new List(); static XRDisplaySubsystem s_Display; /// /// Returns the active XR display. /// static public XRDisplaySubsystem GetActiveDisplay() { return s_Display; } #endif // MSAA level (number of samples per pixel) shared by all XR displays static MSAASamples s_MSAASamples = MSAASamples.None; // Internal resources used by XR rendering static Material s_OcclusionMeshMaterial; static Material s_MirrorViewMaterial; // Ability to override the default XR layout static Action s_LayoutOverride = null; /// /// Returns true if a XR device is connected and running. /// static public bool displayActive { #if ENABLE_VR && ENABLE_XR_MODULE get => (s_Display != null) ? s_Display.running : false; #else get => false; #endif } /// /// Valid empty pass when a camera is not using XR. /// public static readonly XRPass emptyPass = new XRPass(); /// /// If true, the system will try to create a layout compatible with single-pass rendering. /// static public bool singlePassAllowed { get; set; } = true; /// /// Cached value of SystemInfo.foveatedRenderingCaps. /// static public FoveatedRenderingCaps foveatedRenderingCaps { get; set; } /// /// If true, the system will log some information about the layout to the console. /// static public bool dumpDebugInfo { get; set; } = false; /// /// Use this method to assign the shaders that will be used to render occlusion mesh for each XRPass and the final mirror view. /// /// /// /// public static void Initialize(Func passAllocator, Shader occlusionMeshPS, Shader mirrorViewPS) { if (passAllocator == null) throw new ArgumentNullException("passCreator"); s_PassAllocator = passAllocator; RefreshDeviceInfo(); foveatedRenderingCaps = SystemInfo.foveatedRenderingCaps; if (occlusionMeshPS != null && s_OcclusionMeshMaterial == null) s_OcclusionMeshMaterial = CoreUtils.CreateEngineMaterial(occlusionMeshPS); if (mirrorViewPS != null && s_MirrorViewMaterial == null) s_MirrorViewMaterial = CoreUtils.CreateEngineMaterial(mirrorViewPS); if (XRGraphicsAutomatedTests.enabled) SetLayoutOverride(XRGraphicsAutomatedTests.OverrideLayout); } /// /// Used by the render pipeline to communicate to the XR device how many samples are used by MSAA. /// /// public static void SetDisplayMSAASamples(MSAASamples msaaSamples) { if (s_MSAASamples == msaaSamples) return; s_MSAASamples = msaaSamples; #if ENABLE_VR && ENABLE_XR_MODULE SubsystemManager.GetInstances(s_DisplayList); foreach (var display in s_DisplayList) display.SetMSAALevel((int)s_MSAASamples); #endif } /// /// Returns the number of samples (MSAA) currently configured on the XR device. /// /// public static MSAASamples GetDisplayMSAASamples() { return s_MSAASamples; } /// /// Used by the render pipeline to scale the render target on the XR device. /// /// A value of 1.0f represents 100% of the original resolution. public static void SetRenderScale(float renderScale) { #if ENABLE_VR && ENABLE_XR_MODULE SubsystemManager.GetInstances(s_DisplayList); foreach (var display in s_DisplayList) display.scaleOfAllRenderTargets = renderScale; #endif } /// /// Used by the render pipeline to initiate a new rendering frame through a XR layout. /// /// public static XRLayout NewLayout() { RefreshDeviceInfo(); if (s_Layout.GetActivePasses().Count > 0) { Debug.LogWarning("Render Pipeline error : the XR layout still contains active passes. Executing XRSystem.EndLayout() right now."); EndLayout(); } return s_Layout; } /// /// Used by the render pipeline to complete the XR layout at the end of the frame. /// public static void EndLayout() { if (dumpDebugInfo) s_Layout.LogDebugInfo(); s_Layout.Clear(); } /// /// Used by the render pipeline to render the mirror view to the gameview, as configured by the XR device. /// /// /// public static void RenderMirrorView(CommandBuffer cmd, Camera camera) { #if ENABLE_VR && ENABLE_XR_MODULE XRMirrorView.RenderMirrorView(cmd, camera, s_MirrorViewMaterial, s_Display); #endif } /// /// Free up resources used by the system. /// public static void Dispose() { if (s_OcclusionMeshMaterial != null) { CoreUtils.Destroy(s_OcclusionMeshMaterial); s_OcclusionMeshMaterial = null; } if (s_MirrorViewMaterial != null) { CoreUtils.Destroy(s_MirrorViewMaterial); s_MirrorViewMaterial = null; } } // Used by the render pipeline to communicate to the XR device the range of the depth buffer. internal static void SetDisplayZRange(float zNear, float zFar) { #if ENABLE_VR && ENABLE_XR_MODULE if (s_Display != null) { s_Display.zNear = zNear; s_Display.zFar = zFar; } #endif } // XRTODO : expose as public API static void SetLayoutOverride(Action action) { s_LayoutOverride = action; } // Disable legacy VR system before rendering first frame [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] static void XRSystemInit() { if (GraphicsSettings.currentRenderPipeline != null) { RefreshDeviceInfo(); } } static void RefreshDeviceInfo() { #if ENABLE_VR && ENABLE_XR_MODULE SubsystemManager.GetInstances(s_DisplayList); if (s_DisplayList.Count > 0) { if (s_DisplayList.Count > 1) throw new NotImplementedException("Only one XR display is supported!"); s_Display = s_DisplayList[0]; s_Display.disableLegacyRenderer = true; s_Display.sRGB = QualitySettings.activeColorSpace == ColorSpace.Linear; // XRTODO : discuss this code and UI implications s_Display.textureLayout = XRDisplaySubsystem.TextureLayout.Texture2DArray; // XRTODO : replace by API from XR SDK, assume we have 2 views max for now TextureXR.maxViews = Math.Max(TextureXR.slices, 2); } else { s_Display = null; } #endif } // Setup the layout to use multi-pass or single-pass based on the runtime caps internal static void CreateDefaultLayout(Camera camera) { #if ENABLE_VR && ENABLE_XR_MODULE if (s_Display == null) throw new NullReferenceException(nameof(s_Display)); for (int renderPassIndex = 0; renderPassIndex < s_Display.GetRenderPassCount(); ++renderPassIndex) { s_Display.GetRenderPass(renderPassIndex, out var renderPass); s_Display.GetCullingParameters(camera, renderPass.cullingPassIndex, out var cullingParams); if (CanUseSinglePass(camera, renderPass)) { var xrPass = s_PassAllocator(BuildPass(renderPass, cullingParams)); for (int renderParamIndex = 0; renderParamIndex < renderPass.GetRenderParameterCount(); ++renderParamIndex) { renderPass.GetRenderParameter(camera, renderParamIndex, out var renderParam); xrPass.AddView(BuildView(renderPass, renderParam)); } s_Layout.AddPass(camera, xrPass); } else { for (int renderParamIndex = 0; renderParamIndex < renderPass.GetRenderParameterCount(); ++renderParamIndex) { renderPass.GetRenderParameter(camera, renderParamIndex, out var renderParam); var xrPass = s_PassAllocator(BuildPass(renderPass, cullingParams)); xrPass.AddView(BuildView(renderPass, renderParam)); s_Layout.AddPass(camera, xrPass); } } } if (s_LayoutOverride != null) s_LayoutOverride.Invoke(s_Layout, camera); #endif } // Update the parameters of one pass with a different camera internal static void ReconfigurePass(XRPass xrPass, Camera camera) { #if ENABLE_VR && ENABLE_XR_MODULE if (xrPass.enabled && s_Display != null) { s_Display.GetRenderPass(xrPass.multipassId, out var renderPass); Debug.Assert(xrPass.singlePassEnabled || renderPass.GetRenderParameterCount() == 1); s_Display.GetCullingParameters(camera, renderPass.cullingPassIndex, out var cullingParams); xrPass.AssignCullingParams(renderPass.cullingPassIndex, cullingParams); for (int renderParamIndex = 0; renderParamIndex < renderPass.GetRenderParameterCount(); ++renderParamIndex) { renderPass.GetRenderParameter(camera, renderParamIndex, out var renderParam); xrPass.AssignView(renderParamIndex, BuildView(renderPass, renderParam)); } if (s_LayoutOverride != null) s_LayoutOverride.Invoke(s_Layout, camera); } #endif } #if ENABLE_VR && ENABLE_XR_MODULE static bool CanUseSinglePass(Camera camera, XRDisplaySubsystem.XRRenderPass renderPass) { if (!singlePassAllowed) return false; 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; } static XRView BuildView(XRDisplaySubsystem.XRRenderPass renderPass, XRDisplaySubsystem.XRRenderParameter renderParameter) { // Convert viewport from normalized to screen space Rect viewport = renderParameter.viewport; viewport.x *= renderPass.renderTargetDesc.width; viewport.width *= renderPass.renderTargetDesc.width; viewport.y *= renderPass.renderTargetDesc.height; viewport.height *= renderPass.renderTargetDesc.height; // XRTODO : remove this line and use XRSettings.useOcclusionMesh instead when it's fixed Mesh occlusionMesh = XRGraphicsAutomatedTests.running ? null : renderParameter.occlusionMesh; return new XRView(renderParameter.projection, renderParameter.view, viewport, occlusionMesh, renderParameter.textureArraySlice); } static XRPassCreateInfo BuildPass(XRDisplaySubsystem.XRRenderPass xrRenderPass, ScriptableCullingParameters cullingParameters) { // We can't use descriptor directly because y-flip is forced // XRTODO : fix root problem RenderTextureDescriptor xrDesc = xrRenderPass.renderTargetDesc; RenderTextureDescriptor rtDesc = new RenderTextureDescriptor(xrDesc.width, xrDesc.height, xrDesc.colorFormat, xrDesc.depthBufferBits, xrDesc.mipCount); rtDesc.dimension = xrRenderPass.renderTargetDesc.dimension; rtDesc.volumeDepth = xrRenderPass.renderTargetDesc.volumeDepth; rtDesc.vrUsage = xrRenderPass.renderTargetDesc.vrUsage; rtDesc.sRGB = xrRenderPass.renderTargetDesc.sRGB; XRPassCreateInfo passInfo = new XRPassCreateInfo { renderTarget = xrRenderPass.renderTarget, renderTargetDesc = rtDesc, cullingParameters = cullingParameters, occlusionMeshMaterial = s_OcclusionMeshMaterial, foveatedRenderingInfo = xrRenderPass.foveatedRenderingInfo, multipassId = s_Layout.GetActivePasses().Count, cullingPassId = xrRenderPass.cullingPassIndex, copyDepth = xrRenderPass.shouldFillOutDepth, xrSdkRenderPass = xrRenderPass }; return passInfo; } #endif } }