public OVRSandwichComposition(GameObject parentObject, Camera mainCamera, OVRManager.CameraDevice cameraDevice, bool useDynamicLighting, OVRManager.DepthQuality depthQuality)
        : base(cameraDevice, useDynamicLighting, depthQuality)
    {
        frameRealtime = Time.realtimeSinceStartup;

        historyRecordCount = OVRManager.instance.sandwichCompositionBufferedFrames;
        if (historyRecordCount < 1)
        {
            Debug.LogWarning("Invalid sandwichCompositionBufferedFrames in OVRManager. It should be at least 1");
            historyRecordCount = 1;
        }
        if (historyRecordCount > 16)
        {
            Debug.LogWarning("The value of sandwichCompositionBufferedFrames in OVRManager is too big. It would consume a lot of memory. It has been override to 16");
            historyRecordCount = 16;
        }
        historyRecordArray = new HistoryRecord[historyRecordCount];
        for (int i = 0; i < historyRecordCount; ++i)
        {
            historyRecordArray[i] = new HistoryRecord();
        }
        historyRecordCursorIndex = 0;

        GameObject fgObject = new GameObject("MRSandwichForegroundCamera");

        fgObject.transform.parent = parentObject.transform;
        fgCamera                 = fgObject.AddComponent <Camera>();
        fgCamera.depth           = 200;
        fgCamera.clearFlags      = CameraClearFlags.SolidColor;
        fgCamera.backgroundColor = Color.clear;
        fgCamera.cullingMask     = mainCamera.cullingMask & (~OVRManager.instance.extraHiddenLayers);
        fgCamera.nearClipPlane   = mainCamera.nearClipPlane;
        fgCamera.farClipPlane    = mainCamera.farClipPlane;

        GameObject bgObject = new GameObject("MRSandwichBackgroundCamera");

        bgObject.transform.parent = parentObject.transform;
        bgCamera                 = bgObject.AddComponent <Camera>();
        bgCamera.depth           = 100;
        bgCamera.clearFlags      = mainCamera.clearFlags;
        bgCamera.backgroundColor = mainCamera.backgroundColor;
        bgCamera.cullingMask     = mainCamera.cullingMask & (~OVRManager.instance.extraHiddenLayers);
        bgCamera.nearClipPlane   = mainCamera.nearClipPlane;
        bgCamera.farClipPlane    = mainCamera.farClipPlane;

        // Create cameraProxyPlane for clipping
        Debug.Assert(cameraProxyPlane == null);
        cameraProxyPlane                  = GameObject.CreatePrimitive(PrimitiveType.Quad);
        cameraProxyPlane.name             = "MRProxyClipPlane";
        cameraProxyPlane.transform.parent = parentObject.transform;
        cameraProxyPlane.GetComponent <Collider>().enabled = false;
        cameraProxyPlane.GetComponent <MeshRenderer>().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
        Material clipMaterial = new Material(Shader.Find("Oculus/OVRMRClipPlane"));

        cameraProxyPlane.GetComponent <MeshRenderer>().material = clipMaterial;
        clipMaterial.SetColor("_Color", Color.clear);
        clipMaterial.SetFloat("_Visible", 0.0f);
        cameraProxyPlane.transform.localScale = new Vector3(1000, 1000, 1000);
        cameraProxyPlane.SetActive(true);
        OVRMRForegroundCameraManager foregroundCameraManager = fgCamera.gameObject.AddComponent <OVRMRForegroundCameraManager>();

        foregroundCameraManager.clipPlaneGameObj = cameraProxyPlane;

        GameObject compositionCameraObject = new GameObject("MRSandwichCaptureCamera");

        compositionCameraObject.transform.parent = parentObject.transform;
        compositionCamera = compositionCameraObject.AddComponent <Camera>();
        compositionCamera.stereoTargetEye = StereoTargetEyeMask.None;
        compositionCamera.depth           = float.MaxValue;
        compositionCamera.rect            = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
        compositionCamera.clearFlags      = CameraClearFlags.Depth;
        compositionCamera.backgroundColor = mainCamera.backgroundColor;
        compositionCamera.cullingMask     = 1 << cameraFramePlaneLayer;
        compositionCamera.nearClipPlane   = mainCamera.nearClipPlane;
        compositionCamera.farClipPlane    = mainCamera.farClipPlane;

        if (!hasCameraDeviceOpened)
        {
            Debug.LogError("Unable to open camera device " + cameraDevice);
        }
        else
        {
            Debug.Log("SandwichComposition activated : useDynamicLighting " + (useDynamicLighting ? "ON" : "OFF"));
            CreateCameraFramePlaneObject(parentObject, compositionCamera, useDynamicLighting);
            cameraFramePlaneObject.layer = cameraFramePlaneLayer;
            RefreshRenderTextures(mainCamera);
            compositionManager           = compositionCamera.gameObject.AddComponent <OVRSandwichCompositionManager>();
            compositionManager.fgTexture = historyRecordArray[historyRecordCursorIndex].fgRenderTexture;
            compositionManager.bgTexture = historyRecordArray[historyRecordCursorIndex].bgRenderTexture;
        }
    }
    public override void Update(Camera mainCamera)
    {
        if (!hasCameraDeviceOpened)
        {
            return;
        }

        frameRealtime = Time.realtimeSinceStartup;

        ++historyRecordCursorIndex;
        if (historyRecordCursorIndex >= historyRecordCount)
        {
            historyRecordCursorIndex = 0;
        }

        if (!OVRPlugin.SetHandNodePoseStateLatency(OVRManager.instance.handPoseStateLatency))
        {
            Debug.LogWarning("HandPoseStateLatency is invalid. Expect a value between 0.0 to 0.5, get " + OVRManager.instance.handPoseStateLatency);
        }

        RefreshRenderTextures(mainCamera);

        bgCamera.clearFlags      = mainCamera.clearFlags;
        bgCamera.backgroundColor = mainCamera.backgroundColor;
        bgCamera.cullingMask     = mainCamera.cullingMask & (~OVRManager.instance.extraHiddenLayers);

        fgCamera.cullingMask = mainCamera.cullingMask & (~OVRManager.instance.extraHiddenLayers);

        if (OVRMixedReality.useFakeExternalCamera || OVRPlugin.GetExternalCameraCount() == 0)
        {
            OVRPose worldSpacePose    = new OVRPose();
            OVRPose trackingSpacePose = new OVRPose();
            trackingSpacePose.position    = OVRMixedReality.fakeCameraPositon;
            trackingSpacePose.orientation = OVRMixedReality.fakeCameraRotation;
            worldSpacePose = OVRExtensions.ToWorldSpacePose(trackingSpacePose);

            RefreshCameraPoses(OVRMixedReality.fakeCameraFov, OVRMixedReality.fakeCameraAspect, worldSpacePose);
        }
        else
        {
            OVRPlugin.CameraExtrinsics extrinsics;
            OVRPlugin.CameraIntrinsics intrinsics;

            // So far, only support 1 camera for MR and always use camera index 0
            if (OVRPlugin.GetMixedRealityCameraInfo(0, out extrinsics, out intrinsics))
            {
                OVRPose worldSpacePose = ComputeCameraWorldSpacePose(extrinsics);

                float fovY   = Mathf.Atan(intrinsics.FOVPort.UpTan) * Mathf.Rad2Deg * 2;
                float aspect = intrinsics.FOVPort.LeftTan / intrinsics.FOVPort.UpTan;

                RefreshCameraPoses(fovY, aspect, worldSpacePose);
            }
            else
            {
                Debug.LogWarning("Failed to get external camera information");
            }
        }

        compositionCamera.GetComponent <OVRCameraFrameCompositionManager>().boundaryMeshMaskTexture = historyRecordArray[historyRecordCursorIndex].boundaryMeshMaskTexture;
        HistoryRecord record = GetHistoryRecordForComposition();

        UpdateCameraFramePlaneObject(mainCamera, compositionCamera, record.boundaryMeshMaskTexture);
        OVRSandwichCompositionManager compositionManager = compositionCamera.gameObject.GetComponent <OVRSandwichCompositionManager>();

        compositionManager.fgTexture = record.fgRenderTexture;
        compositionManager.bgTexture = record.bgRenderTexture;

        cameraProxyPlane.transform.position = fgCamera.transform.position + fgCamera.transform.forward * cameraFramePlaneDistance;
        cameraProxyPlane.transform.LookAt(cameraProxyPlane.transform.position + fgCamera.transform.forward);
    }