using System; using Unity.Jobs; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; namespace UnityEngine.XR.ARSubsystems { /// /// A reference image library that can be constructed and modified at runtime. /// /// /// This differs from an , which can only be constructed in the Editor and is /// immutable at runtime. /// /// > [!IMPORTANT] /// > Implementors: providers must implement this class for their provider if /// > is `true` to provide the functionality /// > to support runtime mutable libraries. /// > /// > This is not something consumers of the namespace should implement. /// /// public abstract class MutableRuntimeReferenceImageLibrary : RuntimeReferenceImageLibrary { /// /// This method should schedule a [Unity Job](xref:Unity.Jobs.IJob) which adds an image to this reference image /// library. /// /// The raw image bytes in . Assume the bytes will be valid /// until the job completes. /// The width and height of the image, in pixels. /// The format of . The format has already been validated with /// . /// The 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. /// Input dependencies for the add image job. /// A [JobHandle](xref:Unity.Jobs.JobHandle) which can be used /// to chain together multiple tasks or to query for completion. /// protected abstract JobHandle ScheduleAddImageJobImpl( NativeSlice imageBytes, Vector2Int sizeInPixels, TextureFormat format, XRReferenceImage referenceImage, JobHandle inputDeps); /// /// Derived classes should call this to create an to be returned by /// its implementation of . /// /// A handle to the job state. This should be unique to this job state. /// The [JobHandle](xref:Unity.Jobs.JobHandle) associated with the add job state. /// Returns a new that contains information about the state of /// an add image job. /// Thrown if is true and /// is `IntPtr.Zero`. 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); } /// /// Get the status of an . /// /// /// > [!IMPORTANT] /// > Implementors: If is `true`, then you must also implement this method. /// /// The state whose status should be retrieved. /// Returns the of an existing /// . /// Thrown if the has a non-zero /// (non-zero means the concrete class should have overriden this method). /// protected internal virtual AddReferenceImageJobStatus GetAddReferenceImageJobStatus(AddReferenceImageJobState state) { if (state.AsIntPtr() == IntPtr.Zero) { return state.jobHandle.IsCompleted ? AddReferenceImageJobStatus.Success : AddReferenceImageJobStatus.Pending; } throw new NotImplementedException(); } /// /// (Read Only) True if this supports the validation of images /// when added via . /// /// /// > [!IMPORTANT] /// > Implementors: If this is `true`, then your implementation must also override: /// > - /// > - /// public virtual bool supportsValidation => false; /// /// This method should schedule a [Unity Job](xref:Unity.Jobs.IJob) which adds an image to this reference image /// library. /// /// The raw image bytes in . You can assume the bytes are /// valid until the job completes. /// The width and height of the image, in pixels. /// The format of . The format has already been validated with /// . /// The 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. /// A [JobHandle](xref:Unity.Jobs.JobHandle) that represents input dependencies for the add /// image job. /// Returns an which contains the state of the asynchronous /// image addition. /// Thrown by this base class implementation. If /// is `true`, then this method should be implemented by the derived class. /// protected virtual AddReferenceImageJobState ScheduleAddImageWithValidationJobImpl( NativeSlice imageBytes, Vector2Int sizeInPixels, TextureFormat format, XRReferenceImage referenceImage, JobHandle inputDeps) => throw new NotImplementedException(); /// /// Asynchronously adds an image to this library. /// /// /// 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 /// 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 /// ). This can be useful, for example, if is the /// output of another job. If you are adding multiple images to the library, it is not necessary to pass a /// previous 's `JobHandle` as the input dependency to the next /// . /// /// The 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 /// . /// /// The raw image bytes in . /// The width and height of the image, in pixels. /// The format of . Test for and enumerate supported formats /// with , , and /// . /// The 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 /// must be set to zero (). A new guid is /// automatically generated for the new image. /// (Optional) Input dependencies for the add image job. /// Returns an that can be used to query the status of the job. /// The can be used to determine whether the image was successfully /// added. Invalid images will be not be added to the reference image library. /// Thrown if does not contain any /// bytes. /// Thrown if 's /// is not . /// Thrown if 's /// is `null`. /// Thrown if 's /// is `true` but /// . contains values less than or equal /// to zero. /// Thrown if the is not supported. /// You can query for support using . /// Thrown if contains /// values less than or equal to zero. public AddReferenceImageJobState ScheduleAddImageWithValidationJob( NativeSlice 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 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."); } /// /// The number of texture formats that are supported for image addition. /// public abstract int supportedTextureFormatCount { get; } /// /// Returns the supported texture format at . Useful for enumerating the supported texture formats for image addition. /// /// The index of the format to retrieve. /// The supported format at . 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); } /// /// Derived methods should return the [TextureFormat](xref:UnityEngine.TextureFormat) at the given . /// has already been validated to be within [0..]. /// /// The index of the format to retrieve. /// The supported format at . protected abstract TextureFormat GetSupportedTextureFormatAtImpl(int index); /// /// Determines whether the given is supported. /// /// The [TextureFormat](xref:UnityEngine.TextureFormat) to test. /// true if is supported for image addition; otherwise, false. public bool IsTextureFormatSupported(TextureFormat format) { int n = supportedTextureFormatCount; for (int i = 0; i < n; ++i) { if (GetSupportedTextureFormatAtImpl(i) == format) return true; } return false; } /// /// Gets an enumerator for this collection of reference images. This allows this image library to act as a collection in a foreach statement. /// The is a struct, so no garbage is generated. /// /// An enumerator that can be used in a foreach statement. 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); } /// /// An enumerator to be used in a foreach statement. /// public struct Enumerator : IEquatable { internal Enumerator(MutableRuntimeReferenceImageLibrary lib) { m_Library = lib; m_Index = -1; } MutableRuntimeReferenceImageLibrary m_Library; int m_Index; /// /// Moves to the next element in the collection. /// /// true if is valid after this call. public bool MoveNext() => ++m_Index < m_Library.count; /// /// The current . /// public XRReferenceImage Current => m_Library[m_Index]; /// /// Disposes of the enumerator. This method does nothing. /// public void Dispose() {} /// /// Generates a hash code suitable for use in a `Dictionary` or `HashSet`. /// /// A hash code of this Enumerator. public override int GetHashCode() { unchecked { var hash = ReferenceEquals(m_Library, null) ? 0 : m_Library.GetHashCode(); return hash * 486187739 + m_Index.GetHashCode(); } } /// /// Compares for equality. /// /// The object to compare against. /// true if is of type and is true. public override bool Equals(object obj) => (obj is Enumerator) && Equals((Enumerator)obj); /// /// Compares for equality. /// /// The other enumerator to compare against. /// true if the other enumerator is equal to this one. public bool Equals(Enumerator other) { return ReferenceEquals(m_Library, other.m_Library) && (m_Index == other.m_Index); } /// /// Compares for equality. /// /// The left-hand side of the comparison. /// The right-hand side of the comparison. /// The same as . public static bool operator ==(Enumerator lhs, Enumerator rhs) => lhs.Equals(rhs); /// /// Compares for inequality. /// /// The left-hand side of the comparison. /// The right-hand side of the comparison. /// The negation of . public static bool operator !=(Enumerator lhs, Enumerator rhs) => !lhs.Equals(rhs); } } }