using System.Threading; using Unity.Burst; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; using UnityEngine; using UnityEngine.Assertions; #if TFLITE_UNITASK_ENABLED using Cysharp.Threading.Tasks; #endif // TFLITE_UNITASK_ENABLED namespace TensorFlowLite { /// /// TextureToNativeTensor for float32 (float) input type /// public sealed class TextureToNativeTensorFloat32 : TextureToNativeTensor { public TextureToNativeTensorFloat32(Options options) : base(UnsafeUtility.SizeOf(), options) { } } /// /// TextureToNativeTensor for uint8 (byte) input type /// /// Note: /// Run compute shader with Float32 then convert to UInt8(byte) in C# /// Because ComputeBuffer doesn't support UInt8 type /// public sealed class TextureToNativeTensorUInt8 : TextureToNativeTensor { const int kJOB_BATCH_SIZE = 64; private NativeArray tensorUInt8; public TextureToNativeTensorUInt8(Options options) : base(UnsafeUtility.SizeOf(), options) { int length = options.width * options.height * options.channels; tensorUInt8 = new NativeArray(length, Allocator.Persistent); Assert.AreEqual(tensor.Length / 4, tensorUInt8.Length, $"Length {tensor.Length} != {tensorUInt8.Length}"); } public override void Dispose() { base.Dispose(); tensorUInt8.Dispose(); } public override NativeArray Transform(Texture input, in Matrix4x4 t) { NativeArray tensor = base.Transform(input, t); // Reinterpret (byte * 4) as float NativeSlice tensorF32 = tensor.Slice().SliceConvert(); // Cast Float32 to Uint8 using Burst var job = new CastFloat32toUInt8Job() { input = tensorF32, output = tensorUInt8, }; job.Schedule(tensorF32.Length, kJOB_BATCH_SIZE).Complete(); return tensorUInt8; } #if TFLITE_UNITASK_ENABLED public override async UniTask> TransformAsync(Texture input, Matrix4x4 t, CancellationToken cancellationToken) { NativeArray tensor = await base.TransformAsync(input, t, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); // Reinterpret (byte * 4) as float NativeSlice tensorF32 = tensor.Slice().SliceConvert(); // Cast Float32 to Uint8 using Burst var job = new CastFloat32toUInt8Job() { input = tensorF32, output = tensorUInt8, }; // Wait for the job to complete async var handle = job.Schedule(tensorF32.Length, kJOB_BATCH_SIZE); runningJobs.Add(handle); await handle; cancellationToken.ThrowIfCancellationRequested(); runningJobs.Remove(handle); return tensorUInt8; } #endif // TFLITE_UNITASK_ENABLED [BurstCompile] struct CastFloat32toUInt8Job : IJobParallelFor { [ReadOnly] public NativeSlice input; [WriteOnly] public NativeArray output; public void Execute(int index) { output[index] = (byte)(input[index] * 255f); } } } /// /// TextureToNativeTensor for int32 (int) input type /// public sealed class TextureToNativeTensorInt32 : TextureToNativeTensor { const int kJOB_BATCH_SIZE = 64; private NativeArray tensorInt32; public TextureToNativeTensorInt32(Options options) : base(UnsafeUtility.SizeOf(), options) { int length = options.width * options.height * options.channels; int stride = UnsafeUtility.SizeOf(); tensorInt32 = new NativeArray(length * stride, Allocator.Persistent); Assert.AreEqual(tensor.Length, tensorInt32.Length, $"Length {tensor.Length} != {tensorInt32.Length}"); } public override void Dispose() { base.Dispose(); tensorInt32.Dispose(); } public override NativeArray Transform(Texture input, in Matrix4x4 t) { NativeArray tensor = base.Transform(input, t); // Reinterpret (byte * 4) as float NativeSlice sliceF32 = tensor.Slice().SliceConvert(); // Reinterpret (byte * 4) as int NativeSlice sliceI32 = tensorInt32.Slice().SliceConvert(); // Cast Float32 to Int32 using Burst var job = new CastFloat32toInt32Job() { input = sliceF32, output = sliceI32, }; job.Schedule(sliceF32.Length, kJOB_BATCH_SIZE).Complete(); return tensorInt32; } #if TFLITE_UNITASK_ENABLED public override async UniTask> TransformAsync(Texture input, Matrix4x4 t, CancellationToken cancellationToken) { NativeArray tensor = await base.TransformAsync(input, t, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); // Reinterpret (byte * 4) as float NativeSlice sliceF32 = tensor.Slice().SliceConvert(); // Reinterpret (byte * 4) as int NativeSlice sliceI32 = tensorInt32.Slice().SliceConvert(); // Cast Float32 to Uint8 using Burst var job = new CastFloat32toInt32Job() { input = sliceF32, output = sliceI32, }; // Wait for the job to complete async var handle = job.Schedule(sliceF32.Length, kJOB_BATCH_SIZE); runningJobs.Add(handle); await handle; cancellationToken.ThrowIfCancellationRequested(); runningJobs.Remove(handle); return tensorInt32; } #endif // TFLITE_UNITASK_ENABLED /// /// Cast f32 to uint8 using Burst Job /// [BurstCompile] internal struct CastFloat32toInt32Job : IJobParallelFor { [ReadOnly] public NativeSlice input; [WriteOnly] public NativeSlice output; public void Execute(int index) { output[index] = (int)(input[index] * 255f); } } } }