414 lines
15 KiB
C#

// 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<XRDisplaySubsystem> displayList = new List<XRDisplaySubsystem>();
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<string> passDebugInfos = new List<string>(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<Camera> 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
}
}