using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using Unity.Jobs; using UnityEngine; using unity.libwebp.Interop; using unity.libwebp; namespace WebP.Experiment.Animation { /// /// WebP decoding process which optimized by using threads. /// public static class WebPDecodeJob { public static Task> StartJob(IntPtr decoder, int count) { Debug.Log($"[WebPDecodeJob] Starting multi-threading webp decode"); var managedBytes = new ConcurrentDictionary(); var task = new WebPDecodeTask(decoder, managedBytes); var taskRef = GCHandle.Alloc(task); var job = new WebPDecoderJob { Task = taskRef }; var handler = job.Schedule(count, 1); var handled = false; while (!handled) { handled = handler.IsCompleted; } handler.Complete(); taskRef.Free(); if (managedBytes.Count <= 0 || managedBytes.Values.Count <= 0) return Task.FromResult(new List<(byte[], int)>()); var sorted = managedBytes.OrderBy(kvp => kvp.Key).ToList().ConvertAll(kvp => kvp.Value); return Task.FromResult(sorted); } } internal interface IWebPDecodeTask { void Excute(int index); } internal unsafe class WebPDecodeTask : IWebPDecodeTask { // have to use Concurrent structures to prevent concurrent race exceptions private ConcurrentDictionary _managedBytes; private WebPAnimDecoder* _decoder; public WebPDecodeTask(IntPtr decoder, ConcurrentDictionary managedBytes) { _managedBytes = managedBytes; _decoder = (WebPAnimDecoder * )decoder; } /// /// The actual decode process which make the magic happen /// public unsafe void Excute(int index) { // get the demuxer (which contains almost all the information about the WebP file) var demuxer = NativeLibwebpdemux.WebPAnimDecoderGetDemuxer(_decoder); WebPIterator iter = new WebPIterator(); // use the demuxer and WebPIterator to extract one frame from the WebP data var success = NativeLibwebpdemux.WebPDemuxGetFrame(demuxer, index + 1, &iter); if (success == 0) { Debug.LogError($"[WebPDecodeTask] Decode frame data {index} failed"); return; } // use native memory marshal to minimize the momory consumption from native memory to csharp managed memory var size = (int)iter.fragment.size; var bytes = new byte[size]; Marshal.Copy((IntPtr)iter.fragment.bytes, bytes, 0, size); // parse the memory data (structured bitmap data) to texture bytes, which can be used to parse into Texture var loadedBytes = Texture2DExt.LoadRGBAFromWebP(bytes, ref iter.width, ref iter.height, false, out var error, null); if (error != Error.Success) { Debug.LogError($"[WebPDecodeTask] Decode texture bytes {index} failed"); return; } // sequential added to the concurrent dictionary // if not using concurrent dict, exceptions may happen _managedBytes?.TryAdd(index, (loadedBytes, iter.duration)); // release the iterator pointer NativeLibwebpdemux.WebPDemuxReleaseIterator(&iter); } } /// /// Convinience for task usage /// internal struct WebPDecoderJob : IJobParallelFor { public GCHandle Task; public void Execute(int index) { var task = (IWebPDecodeTask)Task.Target; task?.Excute(index); } } }