364 lines
20 KiB
C#
364 lines
20 KiB
C#
using System;
|
|
using Unity.Jobs;
|
|
using Unity.Collections;
|
|
using Unity.Collections.LowLevel.Unsafe;
|
|
|
|
namespace UnityEngine.XR.ARSubsystems
|
|
{
|
|
/// <summary>
|
|
/// A reference image library that can be constructed and modified at runtime.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This differs from an <see cref="XRReferenceImageLibrary"/>, which can only be constructed in the Editor and is
|
|
/// immutable at runtime.
|
|
///
|
|
/// > [!IMPORTANT]
|
|
/// > Implementors: <see cref="XRImageTrackingSubsystem"/> providers must implement this class for their provider if
|
|
/// > <see cref="XRImageTrackingSubsystemDescriptor.supportsMutableLibrary"/> is `true` to provide the functionality
|
|
/// > to support runtime mutable libraries.
|
|
/// >
|
|
/// > This is not something consumers of the <see cref="UnityEngine.XR.ARSubsystems"/> namespace should implement.
|
|
/// </remarks>
|
|
/// <seealso cref="XRImageTrackingSubsystem.CreateRuntimeLibrary(XRReferenceImageLibrary)"/>
|
|
public abstract class MutableRuntimeReferenceImageLibrary : RuntimeReferenceImageLibrary
|
|
{
|
|
/// <summary>
|
|
/// This method should schedule a [Unity Job](xref:Unity.Jobs.IJob) which adds an image to this reference image
|
|
/// library.
|
|
/// </summary>
|
|
/// <param name="imageBytes">The raw image bytes in <paramref name="format"/>. Assume the bytes will be valid
|
|
/// until the job completes.</param>
|
|
/// <param name="sizeInPixels">The width and height of the image, in pixels.</param>
|
|
/// <param name="format">The format of <paramref name="imageBytes"/>. The format has already been validated with
|
|
/// <see cref="IsTextureFormatSupported(TextureFormat)"/>.</param>
|
|
/// <param name="referenceImage">The <see cref="XRReferenceImage"/> data associated with the image to add to the
|
|
/// library. This includes information like physical dimensions, associated
|
|
/// [Texture2D](xref:UnityEngine.Texture2D) (optional), and string name.</param>
|
|
/// <param name="inputDeps">Input dependencies for the add image job.</param>
|
|
/// <returns>A [JobHandle](xref:Unity.Jobs.JobHandle) which can be used
|
|
/// to chain together multiple tasks or to query for completion.</returns>
|
|
/// <seealso cref="ScheduleAddImageWithValidationJobImpl(NativeSlice{byte}, Vector2Int, TextureFormat, XRReferenceImage, JobHandle)"/>
|
|
protected abstract JobHandle ScheduleAddImageJobImpl(
|
|
NativeSlice<byte> imageBytes,
|
|
Vector2Int sizeInPixels,
|
|
TextureFormat format,
|
|
XRReferenceImage referenceImage,
|
|
JobHandle inputDeps);
|
|
|
|
/// <summary>
|
|
/// Derived classes should call this to create an <see cref="AddReferenceImageJobState"/> to be returned by
|
|
/// its implementation of <see cref="ScheduleAddImageWithValidationJobImpl"/>.
|
|
/// </summary>
|
|
/// <param name="handle">A handle to the job state. This should be unique to this job state.</param>
|
|
/// <param name="jobHandle">The [JobHandle](xref:Unity.Jobs.JobHandle) associated with the add job state.</param>
|
|
/// <returns>Returns a new <see cref="AddReferenceImageJobState"/> that contains information about the state of
|
|
/// an add image job.</returns>
|
|
/// <exception cref="System.ArgumentException">Thrown if <see cref="supportsValidation"/> is true and
|
|
/// <paramref name="handle"/> is `IntPtr.Zero`.</exception>
|
|
protected AddReferenceImageJobState CreateAddJobState(IntPtr handle, JobHandle jobHandle)
|
|
{
|
|
if (supportsValidation && handle == IntPtr.Zero)
|
|
throw new ArgumentException($"{nameof(handle)} must be non-zero if {nameof(supportsValidation)} is true.", nameof(handle));
|
|
|
|
return new AddReferenceImageJobState(handle, jobHandle, this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the status of an <see cref="AddReferenceImageJobState"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// > [!IMPORTANT]
|
|
/// > Implementors: If <see cref="supportsValidation"/> is `true`, then you must also implement this method.
|
|
/// </remarks>
|
|
/// <param name="state">The state whose status should be retrieved.</param>
|
|
/// <returns>Returns the <see cref="AddReferenceImageJobStatus"/> of an existing
|
|
/// <see cref="AddReferenceImageJobState"/>.</returns>
|
|
/// <exception cref="System.NotImplementedException">Thrown if the <paramref name="state"/> has a non-zero
|
|
/// <see cref="System.IntPtr"/> (non-zero means the concrete class should have overriden this method).
|
|
/// </exception>
|
|
protected internal virtual AddReferenceImageJobStatus GetAddReferenceImageJobStatus(AddReferenceImageJobState state)
|
|
{
|
|
if (state.AsIntPtr() == IntPtr.Zero)
|
|
{
|
|
return state.jobHandle.IsCompleted
|
|
? AddReferenceImageJobStatus.Success
|
|
: AddReferenceImageJobStatus.Pending;
|
|
}
|
|
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// (Read Only) <c>True</c> if this <see cref="MutableRuntimeReferenceImageLibrary"/> supports the validation of images
|
|
/// when added via <see cref="ScheduleAddImageWithValidationJob"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// > [!IMPORTANT]
|
|
/// > Implementors: If this is `true`, then your implementation must also override:
|
|
/// > - <see cref="ScheduleAddImageWithValidationJobImpl"/>
|
|
/// > - <see cref="GetAddReferenceImageJobStatus"/>
|
|
/// </remarks>
|
|
public virtual bool supportsValidation => false;
|
|
|
|
/// <summary>
|
|
/// This method should schedule a [Unity Job](xref:Unity.Jobs.IJob) which adds an image to this reference image
|
|
/// library.
|
|
/// </summary>
|
|
/// <param name="imageBytes">The raw image bytes in <paramref name="format"/>. You can assume the bytes are
|
|
/// valid until the job completes.</param>
|
|
/// <param name="sizeInPixels">The width and height of the image, in pixels.</param>
|
|
/// <param name="format">The format of <paramref name="imageBytes"/>. The format has already been validated with
|
|
/// <see cref="IsTextureFormatSupported(TextureFormat)"/>.</param>
|
|
/// <param name="referenceImage">The <see cref="XRReferenceImage"/> data associated with the image to add to the
|
|
/// library. This includes information like physical dimensions, associated
|
|
/// [Texture2D](xref:UnityEngine.Texture2D) (optional), and string name.</param>
|
|
/// <param name="inputDeps">A [JobHandle](xref:Unity.Jobs.JobHandle) that represents input dependencies for the add
|
|
/// image job.</param>
|
|
/// <returns>Returns an <see cref="AddReferenceImageJobState"/> which contains the state of the asynchronous
|
|
/// image addition.</returns>
|
|
/// <exception cref="System.NotImplementedException">Thrown by this base class implementation. If
|
|
/// <see cref="supportsValidation"/> is `true`, then this method should be implemented by the derived class.
|
|
/// </exception>
|
|
protected virtual AddReferenceImageJobState ScheduleAddImageWithValidationJobImpl(
|
|
NativeSlice<byte> imageBytes,
|
|
Vector2Int sizeInPixels,
|
|
TextureFormat format,
|
|
XRReferenceImage referenceImage,
|
|
JobHandle inputDeps) => throw new NotImplementedException();
|
|
|
|
/// <summary>
|
|
/// Asynchronously adds an image to this library.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Image addition can take some time (for example, several frames) due to the processing that must occur to
|
|
/// insert the image into the library. This is done using the [Unity Job System]
|
|
/// (https://docs.unity3d.com/Manual/JobSystem.html). The returned <see cref="AddReferenceImageJobState"/>
|
|
/// allows your to query for the status of the job, and, if the job completed,
|
|
/// whether it was successful. The job state includes the [JobHandle](xref:Unity.Jobs.JobHandle) which can be
|
|
/// used to chain together multiple tasks.
|
|
///
|
|
/// This job, like all [Unity jobs](https://docs.unity3d.com/Manual/JobSystem.html), can have dependencies (using the
|
|
/// <paramref name="inputDeps"/>). This can be useful, for example, if <paramref name="imageBytes"/> is the
|
|
/// output of another job. If you are adding multiple images to the library, it is not necessary to pass a
|
|
/// previous <see cref="ScheduleAddImageWithValidationJob"/>'s `JobHandle` as the input dependency to the next
|
|
/// <see cref="ScheduleAddImageWithValidationJob"/>.
|
|
///
|
|
/// The <paramref name="imageBytes"/> must be valid until this job completes. The caller is responsible for
|
|
/// managing its memory. You can use the resulting `JobHandle` to schedule a job that deallocates the
|
|
/// <paramref name="imageBytes"/>.
|
|
/// </remarks>
|
|
/// <param name="imageBytes">The raw image bytes in <paramref name="format"/>.</param>
|
|
/// <param name="sizeInPixels">The width and height of the image, in pixels.</param>
|
|
/// <param name="format">The format of <paramref name="imageBytes"/>. Test for and enumerate supported formats
|
|
/// with <see cref="supportedTextureFormatCount"/>, <see cref="GetSupportedTextureFormatAt(int)"/>, and
|
|
/// <see cref="IsTextureFormatSupported(TextureFormat)"/>.</param>
|
|
/// <param name="referenceImage">The <see cref="XRReferenceImage"/> data associated with the image to add to the
|
|
/// library. This includes information like physical dimensions, associated
|
|
/// [Texture2D](xref:UnityEngine.Texture2D) (optional), and string name. The
|
|
/// <see cref="XRReferenceImage.guid"/> must be set to zero (<see cref="System.Guid.Empty"/>). A new guid is
|
|
/// automatically generated for the new image.</param>
|
|
/// <param name="inputDeps">(Optional) Input dependencies for the add image job.</param>
|
|
/// <returns>Returns an <see cref="AddReferenceImageJobState"/> that can be used to query the status of the job.
|
|
/// The <see cref="AddReferenceImageJobState"/> can be used to determine whether the image was successfully
|
|
/// added. Invalid images will be not be added to the reference image library.</returns>
|
|
/// <exception cref="System.ArgumentException">Thrown if <paramref name="imageBytes"/> does not contain any
|
|
/// bytes.</exception>
|
|
/// <exception cref="System.ArgumentException">Thrown if <paramref name="referenceImage"/>'s
|
|
/// <see cref="XRReferenceImage.guid"/> is not <see cref="System.Guid.Empty"/>.</exception>
|
|
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="referenceImage"/>'s
|
|
/// <see cref="XRReferenceImage.name"/> is `null`.</exception>
|
|
/// <exception cref="System.ArgumentOutOfRangeException">Thrown if <paramref name="referenceImage"/>'s
|
|
/// <see cref="XRReferenceImage.specifySize"/> is `true` but
|
|
/// <paramref name="referenceImage"/>.<see cref="XRReferenceImage.size"/> contains values less than or equal
|
|
/// to zero.</exception>
|
|
/// <exception cref="System.InvalidOperationException">Thrown if the <paramref name="format"/> is not supported.
|
|
/// You can query for support using <see cref="IsTextureFormatSupported"/>.</exception>
|
|
/// <exception cref="System.ArgumentOutOfRangeException">Thrown if <paramref name="sizeInPixels"/> contains
|
|
/// values less than or equal to zero.</exception>
|
|
public AddReferenceImageJobState ScheduleAddImageWithValidationJob(
|
|
NativeSlice<byte> imageBytes,
|
|
Vector2Int sizeInPixels,
|
|
TextureFormat format,
|
|
XRReferenceImage referenceImage,
|
|
JobHandle inputDeps = default)
|
|
{
|
|
ValidateAndThrow(imageBytes, sizeInPixels, format, ref referenceImage);
|
|
|
|
return supportsValidation
|
|
? ScheduleAddImageWithValidationJobImpl(imageBytes, sizeInPixels, format, referenceImage, inputDeps)
|
|
: CreateAddJobState(IntPtr.Zero, ScheduleAddImageJobImpl(imageBytes, sizeInPixels, format, referenceImage, inputDeps));
|
|
}
|
|
|
|
void ValidateAndThrow(NativeSlice<byte> imageBytes, Vector2Int sizeInPixels, TextureFormat format, ref XRReferenceImage referenceImage)
|
|
{
|
|
unsafe
|
|
{
|
|
if (imageBytes.GetUnsafePtr() == null)
|
|
throw new ArgumentException($"{nameof(imageBytes)} does not contain any bytes.", nameof(imageBytes));
|
|
}
|
|
|
|
if (!referenceImage.guid.Equals(Guid.Empty))
|
|
throw new ArgumentException($"{nameof(referenceImage)}.{nameof(referenceImage.guid)} must be empty (all zeroes).", $"{nameof(referenceImage)}.{nameof(referenceImage.guid)}");
|
|
|
|
// Generate and assign a new guid for the new image
|
|
referenceImage.m_SerializedGuid = GenerateNewGuid();
|
|
|
|
if (string.IsNullOrEmpty(referenceImage.name))
|
|
throw new ArgumentNullException($"{nameof(referenceImage)}.{nameof(referenceImage.name)}");
|
|
|
|
if (referenceImage.specifySize && referenceImage.size.x <= 0)
|
|
throw new ArgumentOutOfRangeException($"{nameof(referenceImage)}.{nameof(referenceImage.size)}", referenceImage.size.x, $"Invalid physical image dimensions.");
|
|
|
|
if (!IsTextureFormatSupported(format))
|
|
throw new InvalidOperationException($"The texture format {format} is not supported by the current image tracking subsystem.");
|
|
|
|
if (sizeInPixels.x <= 0)
|
|
throw new ArgumentOutOfRangeException($"{nameof(sizeInPixels)}.{nameof(sizeInPixels.x)}", sizeInPixels.x, "Image width must be greater than zero.");
|
|
|
|
if (sizeInPixels.y <= 0)
|
|
throw new ArgumentOutOfRangeException($"{nameof(sizeInPixels)}.{nameof(sizeInPixels.y)}", sizeInPixels.y, "Image height must be greater than zero.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// The number of texture formats that are supported for image addition.
|
|
/// </summary>
|
|
public abstract int supportedTextureFormatCount { get; }
|
|
|
|
/// <summary>
|
|
/// Returns the supported texture format at <paramref name="index"/>. Useful for enumerating the supported texture formats for image addition.
|
|
/// </summary>
|
|
/// <param name="index">The index of the format to retrieve.</param>
|
|
/// <returns>The supported format at <paramref name="index"/>.</returns>
|
|
public TextureFormat GetSupportedTextureFormatAt(int index)
|
|
{
|
|
if (index < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(index), index, $"{nameof(index)} must be greater than or equal to zero.");
|
|
|
|
if (index >= supportedTextureFormatCount)
|
|
throw new ArgumentOutOfRangeException(nameof(index), index, $"{nameof(index)} must be less than {nameof(supportedTextureFormatCount)} ({supportedTextureFormatCount}).");
|
|
|
|
return GetSupportedTextureFormatAtImpl(index);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Derived methods should return the [TextureFormat](xref:UnityEngine.TextureFormat) at the given <paramref name="index"/>.
|
|
/// <paramref name="index"/> has already been validated to be within [0..<see cref="supportedTextureFormatCount"/>].
|
|
/// </summary>
|
|
/// <param name="index">The index of the format to retrieve.</param>
|
|
/// <returns>The supported format at <paramref name="index"/>.</returns>
|
|
protected abstract TextureFormat GetSupportedTextureFormatAtImpl(int index);
|
|
|
|
/// <summary>
|
|
/// Determines whether the given <paramref name="format"/> is supported.
|
|
/// </summary>
|
|
/// <param name="format">The [TextureFormat](xref:UnityEngine.TextureFormat) to test.</param>
|
|
/// <returns><c>true</c> if <paramref name="format"/> is supported for image addition; otherwise, <c>false</c>.</returns>
|
|
public bool IsTextureFormatSupported(TextureFormat format)
|
|
{
|
|
int n = supportedTextureFormatCount;
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
if (GetSupportedTextureFormatAtImpl(i) == format)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets an enumerator for this collection of reference images. This allows this image library to act as a collection in a <c>foreach</c> statement.
|
|
/// The <see cref="Enumerator"/> is a <c>struct</c>, so no garbage is generated.
|
|
/// </summary>
|
|
/// <returns>An enumerator that can be used in a <c>foreach</c> statement.</returns>
|
|
public Enumerator GetEnumerator() => new Enumerator(this);
|
|
|
|
// Converts a System.Guid into two ulongs
|
|
static unsafe SerializableGuid GenerateNewGuid()
|
|
{
|
|
var newGuid = Guid.NewGuid();
|
|
var trackableId = *(TrackableId*)&newGuid;
|
|
return new SerializableGuid(trackableId.subId1, trackableId.subId2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// An enumerator to be used in a <c>foreach</c> statement.
|
|
/// </summary>
|
|
public struct Enumerator : IEquatable<Enumerator>
|
|
{
|
|
internal Enumerator(MutableRuntimeReferenceImageLibrary lib)
|
|
{
|
|
m_Library = lib;
|
|
m_Index = -1;
|
|
}
|
|
|
|
MutableRuntimeReferenceImageLibrary m_Library;
|
|
|
|
int m_Index;
|
|
|
|
/// <summary>
|
|
/// Moves to the next element in the collection.
|
|
/// </summary>
|
|
/// <returns><c>true</c> if <see cref="Current"/> is valid after this call.</returns>
|
|
public bool MoveNext() => ++m_Index < m_Library.count;
|
|
|
|
/// <summary>
|
|
/// The current <see cref="XRReferenceImage"/>.
|
|
/// </summary>
|
|
public XRReferenceImage Current => m_Library[m_Index];
|
|
|
|
/// <summary>
|
|
/// Disposes of the enumerator. This method does nothing.
|
|
/// </summary>
|
|
public void Dispose() {}
|
|
|
|
/// <summary>
|
|
/// Generates a hash code suitable for use in a `Dictionary` or `HashSet`.
|
|
/// </summary>
|
|
/// <returns>A hash code of this Enumerator.</returns>
|
|
public override int GetHashCode()
|
|
{
|
|
unchecked
|
|
{
|
|
var hash = ReferenceEquals(m_Library, null) ? 0 : m_Library.GetHashCode();
|
|
return hash * 486187739 + m_Index.GetHashCode();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compares for equality.
|
|
/// </summary>
|
|
/// <param name="obj">The <c>object</c> to compare against.</param>
|
|
/// <returns><c>true</c> if <paramref name="obj"/> is of type <see cref="Enumerator"/> and <see cref="Equals(Enumerator)"/> is <c>true</c>.</returns>
|
|
public override bool Equals(object obj) => (obj is Enumerator) && Equals((Enumerator)obj);
|
|
|
|
/// <summary>
|
|
/// Compares for equality.
|
|
/// </summary>
|
|
/// <param name="other">The other enumerator to compare against.</param>
|
|
/// <returns><c>true</c> if the other enumerator is equal to this one.</returns>
|
|
public bool Equals(Enumerator other)
|
|
{
|
|
return
|
|
ReferenceEquals(m_Library, other.m_Library) &&
|
|
(m_Index == other.m_Index);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compares for equality.
|
|
/// </summary>
|
|
/// <param name="lhs">The left-hand side of the comparison.</param>
|
|
/// <param name="rhs">The right-hand side of the comparison.</param>
|
|
/// <returns>The same as <see cref="Equals(Enumerator)"/>.</returns>
|
|
public static bool operator ==(Enumerator lhs, Enumerator rhs) => lhs.Equals(rhs);
|
|
|
|
/// <summary>
|
|
/// Compares for inequality.
|
|
/// </summary>
|
|
/// <param name="lhs">The left-hand side of the comparison.</param>
|
|
/// <param name="rhs">The right-hand side of the comparison.</param>
|
|
/// <returns>The negation of <see cref="Equals(Enumerator)"/>.</returns>
|
|
public static bool operator !=(Enumerator lhs, Enumerator rhs) => !lhs.Equals(rhs);
|
|
}
|
|
}
|
|
}
|