XRCameraSubsystem.cs 50.7 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025
using System;
using System.Collections.Generic;
using Unity.Collections;

namespace UnityEngine.XR.ARSubsystems
{
    /// <summary>
    /// Provides access to a device's camera.
    /// </summary>
    /// <remarks>
    /// The <c>XRCameraSubsystem</c> links a Unity <c>Camera</c> to a device camera for video overlay (pass-thru
    /// rendering). It also allows developers to query for environmental light estimation, when available.
    /// </remarks>
    public abstract class XRCameraSubsystem : XRSubsystem<XRCameraSubsystemDescriptor>
    {
        /// <summary>
        /// The provider created by the implementation that contains the required camera functionality.
        /// </summary>
        /// <value>
        /// The provider created by the implementation that contains the required camera functionality.
        /// </value>
        Provider m_Provider;

        /// <summary>
        /// Construct the <c>XRCameraSubsystem</c>.
        /// </summary>
        public XRCameraSubsystem()
        {
            m_Provider = CreateProvider();
            Debug.Assert(m_Provider != null, "camera functionality provider cannot be null");
        }

        /// <summary>
        /// Interface for providing camera functionality for the implementation.
        /// </summary>
        protected class Provider
        {
            /// <summary>
            /// Property to be implemented by the provder to get the material used by <c>XRCameraSubsystem</c> to
            /// render the camera texture.
            /// </summary>
            /// <returns>
            /// The material to render the camera texture.
            /// </returns>
            public virtual Material cameraMaterial => null;

            /// <summary>
            /// Property to be implemented by the provider to determine whether camera permission has been granted.
            /// </summary>
            /// <value>
            /// <c>true</c> if camera permission has been granted. Otherwise, <c>false</c>.
            /// </value>
            public virtual bool permissionGranted => false;

            /// <summary>
            /// Whether or not culling should be inverted during rendering. Some front-facing
            /// camera modes may require this.
            /// </summary>
            public virtual bool invertCulling => false;

            /// <summary>
            /// Method to be implemented by provider to start the camera for the subsystem.
            /// </summary>
            public virtual void Start() { }

            /// <summary>
            /// Method to be implemented by provider to stop the camera for the subsystem.
            /// </summary>
            public virtual void Stop() { }

            /// <summary>
            /// Method to be implemented by provider to destroy the camera for the subsystem.
            /// </summary>
            public virtual void Destroy() { }

            /// <summary>
            /// Method to be implemented by provider to get the camera frame for the subsystem.
            /// </summary>
            /// <param name="cameraParams">The current Unity <c>Camera</c> parameters.</param>
            /// <param name="cameraFrame">The current camera frame returned by the method.</param>
            /// <returns>
            /// <c>true</c> if the method successfully got a frame. Otherwise, <c>false</c>.
            /// </returns>
            public virtual bool TryGetFrame(
                XRCameraParams cameraParams,
                out XRCameraFrame cameraFrame)
            {
                cameraFrame = default(XRCameraFrame);
                return false;
            }

            /// <summary>
            /// Property to be implemented by the provider to get or set the focus mode for the camera.
            /// </summary>
            public virtual CameraFocusMode cameraFocusMode
            {
                get => CameraFocusMode.Fixed;
                set { }
            }

            /// <summary>
            /// Method to be implemented by the provider to set the light estimation mode.
            /// </summary>
            /// <param name="lightEstimationMode">The light estimation mode to set.</param>
            /// <returns>
            /// <c>true</c> if the method successfully set the light estimation mode. Otherwise, <c>false</c>.
            /// </returns>
            public virtual bool TrySetLightEstimationMode(LightEstimationMode lightEstimationMode) => false;

            /// <summary>
            /// Method to be implemented by the provider to get the camera intrinisics information.
            /// </summary>
            /// <param name="cameraIntrinsics">The camera intrinsics information returned from the method.</param>
            /// <returns>
            /// <c>true</c> if the method successfully gets the camera intrinsics information. Otherwise, <c>false</c>.
            /// </returns>
            public virtual bool TryGetIntrinsics(
                out XRCameraIntrinsics cameraIntrinsics)
            {
                cameraIntrinsics = default(XRCameraIntrinsics);
                return false;
            }

            /// <summary>
            /// Method to be implemented by the provider to query the supported camera configurations.
            /// </summary>
            /// <param name="defaultCameraConfiguration">A default value used to fill the returned array before copying
            /// in real values. This ensures future additions to this struct are backwards compatible.</param>
            /// <param name="allocator">The allocation strategy to use for the returned data.</param>
            /// <returns>
            /// The supported camera configurations.
            /// </returns>
            public virtual NativeArray<XRCameraConfiguration> GetConfigurations(XRCameraConfiguration defaultCameraConfiguration,
                                                                                Allocator allocator)
            {
                return new NativeArray<XRCameraConfiguration>(0, allocator);
            }

            /// <summary>
            /// Property to be implemented by the provider to query/set the current camera configuration.
            /// </summary>
            /// <value>
            /// The current camera configuration if it exists. Otherise, <c>null</c>.
            /// </value>
            /// <exception cref="System.NotSupportedException">Thrown when setting the current configuration if the
            /// implementation does not support camera configurations.</exception>
            /// <exception cref="System.ArgumentException">Thrown when setting the current configuration if the given
            /// configuration is not a valid, supported camera configuration.</exception>
            /// <exception cref="System.InvalidOperationException">Thrown when setting the current configuration if the
            /// implementation is unable to set the current camera configuration.</exception>
            public virtual XRCameraConfiguration? currentConfiguration
            {
                get => null;
                set => throw new NotSupportedException("setting current camera configuration is not supported by this implementation");
            }

            /// <summary>
            /// Get the <see cref="XRTextureDescriptor"/>s associated with the current
            /// <see cref="XRCameraFrame"/>.
            /// </summary>
            /// <returns>The current texture descriptors.</returns>
            /// <param name="defaultDescriptor">A default value which should
            /// be used to fill the returned array before copying in the
            /// real values. This ensures future additions to this struct
            /// are backwards compatible.</param>
            /// <param name="allocator">The allocator to use when creating
            /// the returned <c>NativeArray</c>.</param>
            public virtual NativeArray<XRTextureDescriptor> GetTextureDescriptors(
                XRTextureDescriptor defaultDescriptor,
                Allocator allocator)
            {
                return new NativeArray<XRTextureDescriptor>(0, allocator);
            }

            /// <summary>
            /// Method to be implemented by the provider to get the enabled and disabled shader keywords for the
            /// material.
            /// </summary>
            /// <param name="enabledKeywords">The keywords to enable for the material.</param>
            /// <param name="disabledKeywords">The keywords to disable for the material.</param>
            public virtual void GetMaterialKeywords(out List<string> enabledKeywords, out List<string> disabledKeywords)
            {
                enabledKeywords = null;
                disabledKeywords = null;
            }

            /// <summary>
            /// Method to be implemented by the provider to query for the latest native camera image.
            /// </summary>
            /// <param name="cameraImageCinfo">The metadata required to construct a <see cref="XRCameraImage"/></param>
            /// <returns>
            /// <c>true</c> if the camera image is acquired. Otherwise, <c>false</c>.
            /// </returns>
            /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera
            /// image.</exception>
            public virtual bool TryAcquireLatestImage(out CameraImageCinfo cameraImageCinfo)
            {
                throw new NotSupportedException("getting camera image is not supported by this implementation");
            }

            /// <summary>
            /// Method to be implemented by the provider to get the status of an existing asynchronous conversion
            /// request.
            /// </summary>
            /// <param name="requestId">The unique identifier associated with a request.</param>
            /// <returns>The state of the request.</returns>
            /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera
            /// image.</exception>
            /// <seealso cref="ConvertAsync(int, XRCameraImageConversionParams)"/>
            public virtual AsyncCameraImageConversionStatus GetAsyncRequestStatus(int requestId)
            {
                throw new NotSupportedException("camera image conversion is not supported by this implementation");
            }

            /// <summary>
            /// Method to be implemented by the provider to dispose an existing native image identified by
            /// <paramref name="nativeHandle"/>.
            /// </summary>
            /// <param name="nativeHandle">A unique identifier for this camera image.</param>
            /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera
            /// image.</exception>
            /// <seealso cref="TryAcquireLatestImage"/>
            public virtual void DisposeImage(int nativeHandle)
            {
                throw new NotSupportedException("camera image conversion is not supported by this implementation");
            }

            /// <summary>
            /// Method to be implemented by the provider to dispose an existing async conversion request.
            /// </summary>
            /// <param name="requestId">A unique identifier for the request.</param>
            /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera
            /// image.</exception>
            /// <seealso cref="ConvertAsync(int, XRCameraImageConversionParams)"/>
            public virtual void DisposeAsyncRequest(int requestId)
            {
                throw new NotSupportedException("camera image conversion is not supported by this implementation");
            }

            /// <summary>
            /// Method to be implemented by the provider to get information about an image plane from a native image
            /// handle by index.
            /// </summary>
            /// <param name="nativeHandle">A unique identifier for this camera image.</param>
            /// <param name="planeIndex">The index of the plane to get.</param>
            /// <param name="planeCinfo">The returned camera plane information if successful.</param>
            /// <returns>
            /// <c>true</c> if the image plane was acquired. Otherwise, <c>false</c>.
            /// </returns>
            /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera
            /// image.</exception>
            /// <seealso cref="TryAcquireLatestImage"/>
            public virtual bool TryGetPlane(
                int nativeHandle,
                int planeIndex,
                out CameraImagePlaneCinfo planeCinfo)
            {
                throw new NotSupportedException("camera image conversion is not supported by this implementation");
            }

            /// <summary>
            /// Method to be implemented by the provider to determine whether a native image handle returned by
            /// <see cref="TryAcquireLatestImage"/> is currently valid. An image may become invalid if it has been
            /// disposed.
            /// </summary>
            /// <remarks>
            /// If a handle is valid, <see cref="TryConvert"/> and <see cref="TryGetConvertedDataSize"/> should not fail.
            /// </remarks>
            /// <param name="nativeHandle">A unique identifier for the camera image in question.</param>
            /// <returns><c>true</c>, if it is a valid handle. Otherwise, <c>false</c>.</returns>
            /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera
            /// image.</exception>
            /// <seealso cref="DisposeImage"/>
            public virtual bool NativeHandleValid(
                int nativeHandle)
            {
                throw new NotSupportedException("camera image conversion is not supported by this implementation");
            }

            /// <summary>
            /// Method to be implemented by the provider to get the number of bytes required to store an image with the
            /// given dimensions and <c>TextureFormat</c>.
            /// </summary>
            /// <param name="nativeHandle">A unique identifier for the camera image to convert.</param>
            /// <param name="dimensions">The dimensions of the output image.</param>
            /// <param name="format">The <c>TextureFormat</c> for the image.</param>
            /// <param name="size">The number of bytes required to store the converted image.</param>
            /// <returns><c>true</c> if the output <paramref name="size"/> was set.</returns>
            /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera
            /// image.</exception>
            public virtual bool TryGetConvertedDataSize(
                int nativeHandle,
                Vector2Int dimensions,
                TextureFormat format,
                out int size)
            {
                throw new NotSupportedException("camera image conversion is not supported by this implementation");
            }

            /// <summary>
            /// Method to be implemented by the provider to convert the image with handle
            /// <paramref name="nativeHandle"/> using the provided <paramref cref="conversionParams"/>.
            /// </summary>
            /// <param name="nativeHandle">A unique identifier for the camera image to convert.</param>
            /// <param name="conversionParams">The parameters to use during the conversion.</param>
            /// <param name="destinationBuffer">A buffer to write the converted image to.</param>
            /// <param name="bufferLength">The number of bytes available in the buffer.</param>
            /// <returns>
            /// <c>true</c> if the image was converted and stored in <paramref name="destinationBuffer"/>.
            /// </returns>
            /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera
            /// image.</exception>
            public virtual bool TryConvert(
                int nativeHandle,
                XRCameraImageConversionParams conversionParams,
                IntPtr destinationBuffer,
                int bufferLength)
            {
                throw new NotSupportedException("camera image conversion is not supported by this implementation");
            }

            /// <summary>
            /// Method to be implemented by the provider to create an asynchronous request to convert a camera image,
            /// similar to <see cref="TryConvert"/> except the conversion should happen on a thread other than the
            /// calling (main) thread.
            /// </summary>
            /// <param name="nativeHandle">A unique identifier for the camera image to convert.</param>
            /// <param name="conversionParams">The parameters to use during the conversion.</param>
            /// <returns>A unique identifier for this request.</returns>
            /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera
            /// image.</exception>
            public virtual int ConvertAsync(
                int nativeHandle,
                XRCameraImageConversionParams conversionParams)
            {
                throw new NotSupportedException("camera image conversion is not supported by this implementation");
            }

            /// <summary>
            /// Method to be implemented by the provider to get a pointer to the image data from a completed
            /// asynchronous request. This method should only succeed if <see cref="GetAsyncRequestStatus"/> returns
            /// <see cref="AsyncCameraImageConversionStatus.Ready"/>.
            /// </summary>
            /// <param name="requestId">The unique identifier associated with a request.</param>
            /// <param name="dataPtr">A pointer to the native buffer containing the data.</param>
            /// <param name="dataLength">The number of bytes in <paramref name="dataPtr"/>.</param>
            /// <returns><c>true</c> if <paramref name="dataPtr"/> and <paramref name="dataLength"/> were set and point
            ///  to the image data.</returns>
            /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera
            /// image.</exception>
            public virtual bool TryGetAsyncRequestData(int requestId, out IntPtr dataPtr, out int dataLength)
            {
                throw new NotSupportedException("camera image conversion is not supported by this implementation");
            }

            /// <summary>
            /// Method to be implemented by the provider to similar to
            /// <see cref="ConvertAsync(int, XRCameraImageConversionParams)"/> but takes a delegate to invoke when the
            /// request is complete, rather than returning a request id.
            /// </summary>
            /// <remarks>
            /// If the first parameter to <paramref name="callback"/> is
            /// <see cref="AsyncCameraImageConversionStatus.Ready"/> then the <c>dataPtr</c> parameter must be valid
            /// for the duration of the invocation. The data may be destroyed immediately upon return. The
            /// <paramref name="context"/> parameter must be passed back to the <paramref name="callback"/>.
            /// </remarks>
            /// <param name="nativeHandle">A unique identifier for the camera image to convert.</param>
            /// <param name="conversionParams">The parameters to use during the conversion.</param>
            /// <param name="callback">A delegate which must be invoked when the request is complete, whether the
            /// conversion was successfully or not.</param>
            /// <param name="context">A native pointer which must be passed back unaltered to
            /// <paramref name="callback"/>.</param>
            /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera
            /// image.</exception>
            public virtual void ConvertAsync(
                int nativeHandle,
                XRCameraImageConversionParams conversionParams,
                OnImageRequestCompleteDelegate callback,
                IntPtr context)
            {
                throw new NotSupportedException("camera image conversion is not supported by this implementation");
            }

            /// <summary>
            /// Create the camera material from the given camera shader name.
            /// </summary>
            /// <param name="cameraShaderName">The name of the camera shader.</param>
            /// <returns>
            /// The created camera material shader.
            /// </returns>
            /// <exception cref="System.InvalidOperationException">Thrown if the shader cannot be found or if a
            /// material cannot be created for the shader.</exception>
            protected Material CreateCameraMaterial(string cameraShaderName)
            {
                var shader = Shader.Find(cameraShaderName);
                if (shader == null)
                {
                    throw new InvalidOperationException($"Could not find shader named '{cameraShaderName}' required "
                                                        + $"for video overlay on camera subsystem.");
                }

                Material material = new Material(shader);
                if (material == null)
                {
                    throw new InvalidOperationException($"Could not create a material for shader named "
                                                        + $"'{cameraShaderName}' required for video overlay on camera "
                                                        + $"subsystem.");
                }

                return material;
            }
        }

        /// <summary>
        /// Get or set the focus mode for the camera.
        /// </summary>
        /// <value>
        /// The focus mode for the camera.
        /// </value>
        public CameraFocusMode focusMode
        {
            get => m_Provider.cameraFocusMode;
            set => m_Provider.cameraFocusMode = value;
        }

        /// <summary>
        /// Specifies the light estimation mode.
        /// </summary>
        /// <value>
        /// The light estimation mode.
        /// </value>
        public LightEstimationMode lightEstimationMode
        {
            get => m_LightEstimationMode;
            set
            {
                if ((m_LightEstimationMode != value) && m_Provider.TrySetLightEstimationMode(value))
                {
                    m_LightEstimationMode = value;
                }
            }
        }
        LightEstimationMode m_LightEstimationMode = LightEstimationMode.Disabled;

        /// <summary>
        /// Start the camera subsystem.
        /// </summary>
        protected sealed override void OnStart() => m_Provider.Start();

        /// <summary>
        /// Stop the camera subsystem.
        /// </summary>
        protected sealed override void OnStop() => m_Provider.Stop();

        /// <summary>
        /// Destroy the camera subsystem.
        /// </summary>
        protected sealed override void OnDestroyed() => m_Provider.Destroy();

        /// <summary>
        /// Gets the <see cref="XRTextureDescriptor"/>s associated with the
        /// current frame. The caller owns the returned <c>NativeArray</c>
        /// and is responsible for calling <c>Dispose</c> on it.
        /// </summary>
        /// <returns>An array of texture descriptors.</returns>
        /// <param name="allocator">The allocator to use when creating
        /// the returned <c>NativeArray</c>.</param>
        public NativeArray<XRTextureDescriptor> GetTextureDescriptors(
            Allocator allocator)
        {
            return m_Provider.GetTextureDescriptors(
                default(XRTextureDescriptor),
                allocator);
        }

        /// <summary>
        /// Get the material used by <c>XRCameraSubsystem</c> to render the camera texture.
        /// </summary>
        /// <value>
        /// The material to render the camera texture.
        /// </value>
        public Material cameraMaterial => m_Provider.cameraMaterial;

        /// <summary>
        /// Returns the camera intrinsics information.
        /// </summary>
        /// <param name="cameraIntrinsics">The camera intrinsics information returned from the method.</param>
        /// <returns>
        /// <c>true</c> if the method successfully gets the camera intrinsics information. Otherwise, <c>false</c>.
        /// </returns>
        public bool TryGetIntrinsics(out XRCameraIntrinsics cameraIntrinsics)
        {
            return m_Provider.TryGetIntrinsics(out cameraIntrinsics);
        }

        /// <summary>
        /// Queries for the supported camera configurations.
        /// </summary>
        /// <param name="allocator">The allocation strategy to use for the returned data.</param>
        /// <returns>
        /// The supported camera configurations.
        /// </returns>
        public NativeArray<XRCameraConfiguration> GetConfigurations(Allocator allocator)
        {
            return m_Provider.GetConfigurations(default(XRCameraConfiguration), allocator);
        }

        /// <summary>
        /// The current camera configuration.
        /// </summary>
        /// <value>
        /// The current camera configuration if it exists. Otherise, <c>null</c>.
        /// </value>
        /// <exception cref="System.NotSupportedException">Thrown when setting the current configuration if the
        /// implementation does not support camera configurations.</exception>
        /// <exception cref="System.ArgumentNullException">Thrown when setting the current configuration if the given
        /// configuration is <c>null</c>.</exception>
        /// <exception cref="System.ArgumentException">Thrown when setting the current configuration if the given
        /// configuration is not a supported camera configuration.</exception>
        /// <exception cref="System.InvalidOperationException">Thrown when setting the current configuration if the
        /// implementation is unable to set the current camera configuration.</exception>
        public virtual XRCameraConfiguration? currentConfiguration
        {
            get => m_Provider.currentConfiguration;
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value", "cannot set the camera configuration to null");
                }

                m_Provider.currentConfiguration = value;
            }
        }

        /// <summary>
        /// Whether to invert the culling mode during rendering. Some front-facing
        /// camera modes may require this.
        /// </summary>
        public bool invertCulling => m_Provider.invertCulling;

        /// <summary>
        /// Method for the implementation to create the camera functionality provider.
        /// </summary>
        /// <returns>
        /// The camera functionality provider.
        /// </returns>
        protected abstract Provider CreateProvider();

        /// <summary>
        /// Get the latest frame from the provider.
        /// </summary>
        /// <param name="cameraParams">The Unity <c>Camera</c> parameters.</param>
        /// <param name="frame">The camera frame to be populated if the subsystem is running and successfully provides
        /// the latest camera frame.</param>
        /// <returns>
        /// <c>true</c> if the camera frame is successfully returned. Otherwise, <c>false</c>.
        /// </returns>
        public bool TryGetLatestFrame(
            XRCameraParams cameraParams,
            out XRCameraFrame frame)
        {
            if (running && m_Provider.TryGetFrame(cameraParams, out frame))
            {
                return true;
            }

            frame = default(XRCameraFrame);
            return false;
        }

        /// <summary>
        /// Determines whether camera permission has been granted.
        /// </summary>
        /// <value>
        /// <c>true</c> if camera permission has been granted. Otherwise, <c>false</c>.
        /// </value>
        public bool permissionGranted => m_Provider.permissionGranted;

        /// <summary>
        /// Get the enabled and disabled shader keywords for the material.
        /// </summary>
        /// <param name="enabledKeywords">The keywords to enable for the material.</param>
        /// <param name="disabledKeywords">The keywords to disable for the material.</param>
        public void GetMaterialKeywords(out List<string> enabledKeywords, out List<string> disabledKeywords)
            => m_Provider.GetMaterialKeywords(out enabledKeywords, out disabledKeywords);

        /// <summary>
        /// Attempt to get the latest camera image. This provides directly access to the raw pixel data, as well as
        /// utilities to convert to RGB and Grayscale formats.
        /// </summary>
        /// <remarks>
        /// The returned <see cref="XRCameraImage"/> must be disposed to avoid resource leaks.
        /// </remarks>
        /// <param name="cameraImage">A valid <see cref="XRCameraImage"/> if this method returns <c>true</c>.</param>
        /// <returns>
        /// <c>true</c> if the image was acquired. Otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera image.
        /// </exception>
        public bool TryGetLatestImage(out XRCameraImage cameraImage)
        {
            CameraImageCinfo cameraImageCinfo;
            if (m_Provider.TryAcquireLatestImage(out cameraImageCinfo))
            {
                cameraImage = new XRCameraImage(this, cameraImageCinfo.nativeHandle, cameraImageCinfo.dimensions,
                                                cameraImageCinfo.planeCount, cameraImageCinfo.timestamp,
                                                cameraImageCinfo.format);
                return true;
            }
            else
            {
                cameraImage = default(XRCameraImage);
                return false;
            }
        }

        /// <summary>
        /// Registers a camera subsystem implementation based on the given subsystem parameters.
        /// </summary>
        /// <param name="cameraSubsystemParams">The parameters defining the camera subsystem functionality implemented
        /// by the subsystem provider.</param>
        /// <returns>
        /// <c>true</c> if the subsystem implementation is registered. Otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="System.ArgumentException">Thrown when the values specified in the
        /// <see cref="XRCameraSubsystemCinfo"/> parameter are invalid. Typically, this will occur
        /// <list type="bullet">
        /// <item>
        /// <description>if <see cref="XRCameraSubsystemCinfo.id"/> is <c>null</c> or empty</description>
        /// </item>
        /// <item>
        /// <description>if <see cref="XRCameraSubsystemCinfo.implementationType"/> is <c>null</c></description>
        /// </item>
        /// <item>
        /// <description>if <see cref="XRCameraSubsystemCinfo.implementationType"/> does not derive from the
        /// <see cref="XRCameraSubsystem"/> class
        /// </description>
        /// </item>
        /// </list>
        /// </exception>
        public static bool Register(XRCameraSubsystemCinfo cameraSubsystemParams)
        {
            XRCameraSubsystemDescriptor cameraSubsystemDescriptor = XRCameraSubsystemDescriptor.Create(cameraSubsystemParams);
            return SubsystemRegistration.CreateDescriptor(cameraSubsystemDescriptor);
        }

        /// <summary>
        /// Get the status of an existing asynchronous conversion request.
        /// </summary>
        /// <param name="requestId">The unique identifier associated with a request.</param>
        /// <returns>The state of the request.</returns>
        /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera image.
        /// </exception>
        /// <seealso cref="ConvertAsync(int, XRCameraImageConversionParams)"/>
        internal AsyncCameraImageConversionStatus GetAsyncRequestStatus(int requestId)
        {
            return m_Provider.GetAsyncRequestStatus(requestId);
        }

        /// <summary>
        /// Dispose an existing native image identified by <paramref name="nativeHandle"/>.
        /// </summary>
        /// <param name="nativeHandle">A unique identifier for this camera image.</param>
        /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera image.
        /// </exception>
        /// <seealso cref="Provider.TryAcquireLatestImage"/>
        internal void DisposeImage(int nativeHandle) => m_Provider.DisposeImage(nativeHandle);

        /// <summary>
        /// Dispose an existing async conversion request.
        /// </summary>
        /// <param name="requestId">A unique identifier for the request.</param>
        /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera image.
        /// </exception>
        /// <seealso cref="Provider.ConvertAsync(int, XRCameraImageConversionParams)"/>
        internal void DisposeAsyncRequest(int requestId) => m_Provider.DisposeAsyncRequest(requestId);

        /// <summary>
        /// Attempt to get information about an image plane from a native image by index.
        /// </summary>
        /// <param name="nativeHandle">A unique identifier for this camera image.</param>
        /// <param name="planeIndex">The index of the plane to get.</param>
        /// <param name="planeCinfo">The returned camera plane information if successful.</param>
        /// <returns>
        /// <c>true</c> if the image plane is successfully acquired. Otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera image.
        /// </exception>
        /// <seealso cref="Provider.TryAcquireLatestImage"/>
        internal bool TryGetPlane(
            int nativeHandle,
            int planeIndex,
            out CameraImagePlaneCinfo planeCinfo)
        {
            return m_Provider.TryGetPlane(nativeHandle, planeIndex, out planeCinfo);
        }

        /// <summary>
        /// Determine whether a native image handle returned by <see cref="Provider.TryAcquireLatestImage"/> is
        /// currently valid. An image may become invalid if it has been disposed.
        /// </summary>
        /// <remarks>
        /// If a handle is valid, <see cref="TryConvert"/> and <see cref="TryGetConvertedDataSize"/> should not fail.
        /// </remarks>
        /// <param name="nativeHandle">A unique identifier for the camera image in question.</param>
        /// <returns><c>true</c> if it is a valid handle, <c>false</c> otherwise.</returns>
        /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera image.
        /// </exception>
        /// <seealso cref="DisposeImage"/>
        internal bool NativeHandleValid(int nativeHandle) => m_Provider.NativeHandleValid(nativeHandle);

        /// <summary>
        /// Get the number of bytes required to store an image with the given dimensions and <c>TextureFormat</c>.
        /// </summary>
        /// <param name="nativeHandle">A unique identifier for the camera image to convert.</param>
        /// <param name="dimensions">The dimensions of the output image.</param>
        /// <param name="format">The <c>TextureFormat</c> for the image.</param>
        /// <param name="size">The number of bytes required to store the converted image.</param>
        /// <returns><c>true</c> if the output <paramref name="size"/> was set.</returns>
        /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera image.
        /// </exception>
        internal bool TryGetConvertedDataSize(
            int nativeHandle,
            Vector2Int dimensions,
            TextureFormat format,
            out int size)
        {
            return m_Provider.TryGetConvertedDataSize(nativeHandle, dimensions, format, out size);
        }

        /// <summary>
        /// Convert the image with handle <paramref name="nativeHandle"/> using the provided
        /// <see cref="XRCameraImageConversionParams"/>.
        /// </summary>
        /// <param name="nativeHandle">A unique identifier for the camera image to convert.</param>
        /// <param name="conversionParams">The parameters to use during the conversion.</param>
        /// <param name="destinationBuffer">A buffer to write the converted image to.</param>
        /// <param name="bufferLength">The number of bytes available in the buffer.</param>
        /// <returns>
        /// <c>true</c> if the image was converted and stored in <paramref name="destinationBuffer"/>.
        /// </returns>
        /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera image.
        /// </exception>
        internal bool TryConvert(
            int nativeHandle,
            XRCameraImageConversionParams conversionParams,
            IntPtr destinationBuffer,
            int bufferLength)
        {
            return m_Provider.TryConvert(nativeHandle, conversionParams, destinationBuffer, bufferLength);
        }

        /// <summary>
        /// Create an asynchronous request to convert a camera image, similar to <see cref="TryConvert"/> except the
        /// conversion should happen on a thread other than the calling (main) thread.
        /// </summary>
        /// <param name="nativeHandle">A unique identifier for the camera image to convert.</param>
        /// <param name="conversionParams">The parameters to use during the conversion.</param>
        /// <returns>A unique identifier for this request.</returns>
        /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera image.
        /// </exception>
        internal int ConvertAsync(
            int nativeHandle,
            XRCameraImageConversionParams conversionParams)
        {
            return m_Provider.ConvertAsync(nativeHandle, conversionParams);
        }

        /// <summary>
        /// Get a pointer to the image data from a completed asynchronous request. This method should only succeed if
        /// <see cref="GetAsyncRequestStatus"/> returns <see cref="AsyncCameraImageConversionStatus.Ready"/>.
        /// </summary>
        /// <param name="requestId">The unique identifier associated with a request.</param>
        /// <param name="dataPtr">A pointer to the native buffer containing the data.</param>
        /// <param name="dataLength">The number of bytes in <paramref name="dataPtr"/>.</param>
        /// <returns><c>true</c> if <paramref name="dataPtr"/> and <paramref name="dataLength"/> were set and point to
        /// the image data.</returns>
        /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera image.
        /// </exception>
        internal bool TryGetAsyncRequestData(int requestId, out IntPtr dataPtr, out int dataLength)
        {
            return m_Provider.TryGetAsyncRequestData(requestId, out dataPtr, out dataLength);
        }

        /// <summary>
        /// Callback from native code for when the asychronous conversion is complete.
        /// </summary>
        /// <param name="status">The status of the conversion operation.</param>
        /// <param name="conversionParams">The parameters for the conversion.</param>
        /// <param name="dataPtr">The native pointer to the converted data.</param>
        /// <param name="dataLength">The memory size of the converted data.</param>
        /// <param name="context">The native context for the conversion operation.</param>
        protected internal delegate void OnImageRequestCompleteDelegate(
            AsyncCameraImageConversionStatus status,
            XRCameraImageConversionParams conversionParams,
            IntPtr dataPtr,
            int dataLength,
            IntPtr context);

        /// <summary>
        /// Similar to <see cref="ConvertAsync(int, XRCameraImageConversionParams)"/> but takes a delegate to invoke
        /// when the request is complete, rather than returning a request id.
        /// </summary>
        /// <remarks>
        /// If the first parameter to <paramref name="callback"/> is
        /// <see cref="AsyncCameraImageConversionStatus.Ready"/> then the <c>dataPtr</c> parameter must be valid for
        /// the duration of the invocation. The data may be destroyed immediately upon return. The
        /// <paramref name="context"/> parameter must be passed back to the <paramref name="callback"/>.
        /// </remarks>
        /// <param name="nativeHandle">A unique identifier for the camera image to convert.</param>
        /// <param name="conversionParams">The parameters to use during the conversion.</param>
        /// <param name="callback">A delegate which must be invoked when the request is complete, whether the
        /// conversion was successfully or not.</param>
        /// <param name="context">A native pointer which must be passed back unaltered to <paramref name="callback"/>.
        /// </param>
        /// <exception cref="System.NotSupportedException">Thrown if the implementation does not support camera image.
        /// </exception>
        internal void ConvertAsync(
            int nativeHandle,
            XRCameraImageConversionParams conversionParams,
            OnImageRequestCompleteDelegate callback,
            IntPtr context)
        {
            m_Provider.ConvertAsync(nativeHandle, conversionParams, callback, context);
        }

        /// <summary>
        /// Container for native camera image construction metadata.
        /// </summary>
        protected struct CameraImageCinfo : IEquatable<CameraImageCinfo>
        {
            /// <summary>
            /// The handle representing the camera image on the native level.
            /// </summary>
            /// <value>
            /// The handle representing the camera image on the native level.
            /// </value>
            public int nativeHandle => m_NativeHandle;
            int m_NativeHandle;

            /// <summary>
            /// The dimensions of the camera image.
            /// </summary>
            /// <value>
            /// The dimensions of the camera image.
            /// </value>
            public Vector2Int dimensions => m_Dimensions;
            Vector2Int m_Dimensions;

            /// <summary>
            /// The number of video planes in the camera image.
            /// </summary>
            /// <value>
            /// The number of video planes in the camera image.
            /// </value>
            public int planeCount => m_PlaneCount;
            int m_PlaneCount;

            /// <summary>
            /// The timestamp for when the camera image was captured.
            /// </summary>
            /// <value>
            /// The timestamp for when the camera image was captured.
            /// </value>
            public double timestamp => m_Timestamp;
            double m_Timestamp;

            /// <summary>
            /// The format of the camera image.
            /// </summary>
            /// <value>
            /// The format of the camera image.
            /// </value>
            public CameraImageFormat format => m_Format;
            CameraImageFormat m_Format;

            /// <summary>
            /// Constructs the camera image cinfo.
            /// </summary>
            /// <param name="nativeHandle">The handle representing the camera image on the native level.</param>
            /// <param name="dimensions">The dimensions of the camera image.</param>
            /// <param name="planeCount">The number of video planes in the camera image.</param>
            /// <param name="timestamp">The timestamp for when the camera image was captured.</param>
            /// <param name="format">The format of the camera image.</param>
            public CameraImageCinfo(int nativeHandle, Vector2Int dimensions, int planeCount, double timestamp,
                                    CameraImageFormat format)
            {
                this.m_NativeHandle = nativeHandle;
                this.m_Dimensions = dimensions;
                this.m_PlaneCount = planeCount;
                this.m_Timestamp = timestamp;
                this.m_Format = format;
            }

            public bool Equals(CameraImageCinfo other)
            {
                return (nativeHandle.Equals(other.nativeHandle) && dimensions.Equals(other.dimensions)
                        && planeCount.Equals(other.planeCount) && timestamp.Equals(other.timestamp)
                        && format.Equals(other.format));
            }

            public override bool Equals(System.Object obj)
            {
                return ReferenceEquals(this, obj) || ((obj is CameraImageCinfo) && Equals((CameraImageCinfo)obj));
            }

            public static bool operator ==(CameraImageCinfo lhs, CameraImageCinfo rhs) => lhs.Equals(rhs);

            public static bool operator !=(CameraImageCinfo lhs, CameraImageCinfo rhs) => !(lhs == rhs);

            public override int GetHashCode()
            {
                int hashCode = 486187739;
                unchecked
                {
                    hashCode = (hashCode * 486187739) + nativeHandle.GetHashCode();
                    hashCode = (hashCode * 486187739) + dimensions.GetHashCode();
                    hashCode = (hashCode * 486187739) + planeCount.GetHashCode();
                    hashCode = (hashCode * 486187739) + timestamp.GetHashCode();
                    hashCode = (hashCode * 486187739) + ((int)format).GetHashCode();
                }
                return hashCode;
            }

            public override string ToString()
            {
                return string.Format("nativeHandle: {0} dimensions:{1} planes:{2} timestamp:{3} format:{4}",
                                     nativeHandle.ToString(), dimensions.ToString(), planeCount.ToString(),
                                     timestamp.ToString(), format.ToString());
            }
        }

        /// <summary>
        /// Container for the metadata describing access to the raw camera image plane data.
        /// </summary>
        protected internal struct CameraImagePlaneCinfo : IEquatable<CameraImagePlaneCinfo>
        {
            /// <summary>
            /// The pointer to the raw native image data.
            /// </summary>
            /// <value>
            /// The pointer to the raw native image data.
            /// </value>
            public IntPtr dataPtr => m_DataPtr;
            IntPtr m_DataPtr;

            /// <summary>
            /// The length of the native image data.
            /// </summary>
            /// <value>
            /// The length of the native image data.
            /// </value>
            public int dataLength => m_DataLength;
            int m_DataLength;

            /// <summary>
            /// The stride for iterating through the rows of the native image data.
            /// </summary>
            /// <value>
            /// The stride for iterating through the rows of the native image data.
            /// </value>
            public int rowStride => m_RowStride;
            int m_RowStride;

            /// <summary>
            /// The stride for iterating through the pixels of the native image data.
            /// </summary>
            /// <value>
            /// The stride for iterating through the pixels of the native image data.
            /// </value>
            public int pixelStride => m_PixelStride;
            int m_PixelStride;

            /// <summary>
            /// Constructs the camera image plane cinfo.
            /// </summary>
            /// <param name="dataPtr">The pointer to the raw native image data.</param>
            /// <param name="dataLength">The length of the native image data.</param>
            /// <param name="rowStride">The stride for iterating through the rows of the native image data.</param>
            /// <param name="pixelStride">The stride for iterating through the pixels of the native image data.</param>
            public CameraImagePlaneCinfo(IntPtr dataPtr, int dataLength, int rowStride, int pixelStride)
            {
                this.m_DataPtr = dataPtr;
                this.m_DataLength = dataLength;
                this.m_RowStride = rowStride;
                this.m_PixelStride = pixelStride;
            }

            public bool Equals(CameraImagePlaneCinfo other)
            {
                return (dataPtr.Equals(other.dataPtr) && dataLength.Equals(other.dataLength)
                        && rowStride.Equals(other.rowStride) && pixelStride.Equals(other.pixelStride));
            }

            public override bool Equals(System.Object obj)
            {
                return ReferenceEquals(this, obj) || ((obj is CameraImagePlaneCinfo) && Equals((CameraImagePlaneCinfo)obj));
            }

            public static bool operator ==(CameraImagePlaneCinfo lhs, CameraImagePlaneCinfo rhs) => lhs.Equals(rhs);

            public static bool operator !=(CameraImagePlaneCinfo lhs, CameraImagePlaneCinfo rhs) => !(lhs == rhs);

            public override int GetHashCode()
            {
                int hashCode = 486187739;
                unchecked
                {
                    hashCode = (hashCode * 486187739) + dataPtr.GetHashCode();
                    hashCode = (hashCode * 486187739) + dataLength.GetHashCode();
                    hashCode = (hashCode * 486187739) + rowStride.GetHashCode();
                    hashCode = (hashCode * 486187739) + pixelStride.GetHashCode();
                }
                return hashCode;
            }

            public override string ToString()
            {
                return string.Format("dataPtr: {0} length:{1} rowStride:{2} pixelStride:{3}", dataPtr.ToString(),
                                     dataLength.ToString(), rowStride.ToString(), pixelStride.ToString());
            }
        }
    }
}