diff --git a/Runtime/Scripts/Context.cs b/Runtime/Scripts/Context.cs index 55c5ca637c..a4b141d0db 100644 --- a/Runtime/Scripts/Context.cs +++ b/Runtime/Scripts/Context.cs @@ -1,6 +1,8 @@ using System; using System.Runtime.InteropServices; using System.Threading; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; using UnityEngine; #if UNITY_EDITOR @@ -61,16 +63,16 @@ internal class Batch public struct BatchData { public int tracksCount; - [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] - public IntPtr[] tracks; + public IntPtr tracks; } - public BatchData data; - public IntPtr ptr; + public NativeArray tracks; + + BatchData data; public Batch() { - ResizeCapacity(1); + tracks = new NativeArray(1, Allocator.Persistent); } ~Batch() @@ -80,40 +82,34 @@ public Batch() public void Dispose() { - if (ptr != IntPtr.Zero) + if (tracks.IsCreated) { - Marshal.FreeHGlobal(ptr); - ptr = IntPtr.Zero; + tracks.Dispose(); + tracks = default; } } public void ResizeCapacity(int totalTracks) { - const int roundedCapacity = 32; - int totalCapacity = ((totalTracks + roundedCapacity) / roundedCapacity) * roundedCapacity; - - if (ptr != IntPtr.Zero && data.tracks.Length >= totalCapacity) - return; - - data.tracksCount = 0; - data.tracks = new IntPtr[totalCapacity]; - - int size = Marshal.SizeOf(typeof(BatchData)) + - Marshal.SizeOf(typeof(IntPtr)) * data.tracks.Length; + tracks.Dispose(); + tracks = new NativeArray(totalTracks, Allocator.Persistent); + } - if (ptr == IntPtr.Zero) - ptr = Marshal.AllocHGlobal(size); - else - ptr = Marshal.ReAllocHGlobal(ptr, (IntPtr)size); - Marshal.StructureToPtr(data, ptr, false); + public unsafe IntPtr GetPtr() + { + data.tracks = new IntPtr(tracks.GetUnsafePtr()); + data.tracksCount = tracks.Length; + fixed (void* ptr = &data) + { + return new IntPtr(ptr); + } } - public void Submit(bool flush = false) + public unsafe void Submit(bool flush = false) { - if (flush == false) + if (!flush) { - Marshal.StructureToPtr(data, ptr, false); - WebRTC.Context.BatchUpdate(ptr); + WebRTC.Context.BatchUpdate(GetPtr()); } else { @@ -181,6 +177,7 @@ public void Dispose() // Release buffers on the rendering thread batch.Submit(true); + batch.Dispose(); NativeMethods.ContextDestroy(id); self = IntPtr.Zero; diff --git a/Runtime/Scripts/Internal/ExecutableUnitySynchronizationContext.cs b/Runtime/Scripts/Internal/ExecutableUnitySynchronizationContext.cs index 4e73816328..3c60230b62 100644 --- a/Runtime/Scripts/Internal/ExecutableUnitySynchronizationContext.cs +++ b/Runtime/Scripts/Internal/ExecutableUnitySynchronizationContext.cs @@ -19,6 +19,8 @@ class ExecutableUnitySynchronizationContext : SynchronizationContext const int k_AwqInitialCapacity = 20; static SynchronizationContext s_MainThreadContext; + static object s_CallbackObject; + static SendOrPostCallback s_CallbackMethod; readonly List m_AsyncWorkQueue; readonly List m_CurrentFrameWork = new List(k_AwqInitialCapacity); @@ -32,6 +34,16 @@ internal ExecutableUnitySynchronizationContext(SynchronizationContext context) s_MainThreadContext = context; } + if (s_CallbackObject == null) + { + s_CallbackObject = new CallbackObject(ExecuteAndAppendNextExecute); + } + + if (s_CallbackMethod == null) + { + s_CallbackMethod = SendOrPostCallback; + } + if (s_MainThreadContext == null || s_MainThreadContext != context) { throw new InvalidOperationException("Unable to create executable synchronization context without a valid synchronization context."); @@ -42,7 +54,7 @@ internal ExecutableUnitySynchronizationContext(SynchronizationContext context) m_AsyncWorkQueue = new List(); // Queue up and Execute work request with the synchronization context. - s_MainThreadContext.Post(SendOrPostCallback, new CallbackObject(ExecuteAndAppendNextExecute)); + s_MainThreadContext.Post(s_CallbackMethod, s_CallbackObject); } ExecutableUnitySynchronizationContext(List queue, int mainThreadID) @@ -191,7 +203,7 @@ void ExecuteAndAppendNextExecute() // UnitySynchronizationContext works by performing work in batches so as not to stall the main thread // forever. Therefore it is safe to re-add ourselves to the delayed work queue. This is how we hook into // the main thread delayed tasks. - s_MainThreadContext.Post(SendOrPostCallback, new CallbackObject(ExecuteAndAppendNextExecute)); + s_MainThreadContext.Post(s_CallbackMethod, s_CallbackObject); } class CallbackObject diff --git a/Runtime/Scripts/VideoStreamTrack.cs b/Runtime/Scripts/VideoStreamTrack.cs index 8c9dd97009..6f391780df 100644 --- a/Runtime/Scripts/VideoStreamTrack.cs +++ b/Runtime/Scripts/VideoStreamTrack.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Concurrent; +using System.Collections.Generic; using System.ComponentModel; using System.Runtime.InteropServices; using UnityEngine; @@ -30,8 +30,8 @@ public class VideoStreamTrack : MediaStreamTrack /// public static bool NeedReceivedVideoFlipVertically { get; set; } = true; - internal static ConcurrentDictionary> s_tracks = - new ConcurrentDictionary>(); + internal static Dictionary> s_tracks = + new Dictionary>(); internal enum VideoStreamTrackAction { @@ -148,8 +148,12 @@ internal void UpdateTexture() public VideoStreamTrack(Texture texture, CopyTexture copyTexture = null) : base(CreateVideoTrack(texture, out var source)) { - if (!s_tracks.TryAdd(self, new WeakReference(this))) - throw new InvalidOperationException(); + lock (s_tracks) + { + if (s_tracks.ContainsKey(self)) + throw new InvalidOperationException(); + s_tracks.Add(self, new WeakReference(this)); + } m_dataptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(VideoStreamTrackData))); Marshal.StructureToPtr(m_data, m_dataptr, false); @@ -170,8 +174,12 @@ public VideoStreamTrack(Texture texture, CopyTexture copyTexture = null) internal VideoStreamTrack(IntPtr ptr) : base(CreateVideoTrack(ptr)) { - if (!s_tracks.TryAdd(self, new WeakReference(this))) - throw new InvalidOperationException(); + lock (s_tracks) + { + if (s_tracks.ContainsKey(self)) + throw new InvalidOperationException(); + s_tracks.Add(self, new WeakReference(this)); + } m_dataptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(VideoStreamTrackData))); Marshal.StructureToPtr(m_data, m_dataptr, false); @@ -205,7 +213,10 @@ public override void Dispose() }, 0.1f); } - s_tracks.TryRemove(self, out var value); + lock (s_tracks) + { + s_tracks.Remove(self); + } } base.Dispose(); } diff --git a/Runtime/Scripts/WebRTC.cs b/Runtime/Scripts/WebRTC.cs index b5228adad9..94798f0b2c 100644 --- a/Runtime/Scripts/WebRTC.cs +++ b/Runtime/Scripts/WebRTC.cs @@ -679,23 +679,23 @@ public static IEnumerator Update() RenderTexture.active = null; var batch = Context.batch; - batch.ResizeCapacity(VideoStreamTrack.s_tracks.Count); - int trackIndex = 0; - foreach (var reference in VideoStreamTrack.s_tracks.Values) + lock (VideoStreamTrack.s_tracks) { - if (!reference.TryGetTarget(out var track)) - continue; - - track.UpdateTexture(); - if (track.DataPtr != IntPtr.Zero) + batch.ResizeCapacity(VideoStreamTrack.s_tracks.Count); + foreach (var pair in VideoStreamTrack.s_tracks) { - batch.data.tracks[trackIndex] = track.DataPtr; - trackIndex++; + if (!pair.Value.TryGetTarget(out var track)) + continue; + + track.UpdateTexture(); + if (track.DataPtr != IntPtr.Zero) + { + batch.tracks[trackIndex] = track.DataPtr; + trackIndex++; + } } } - - batch.data.tracksCount = trackIndex; if (trackIndex > 0) batch.Submit(); diff --git a/Tests/Runtime/NativeAPITest.cs b/Tests/Runtime/NativeAPITest.cs index 3b49ff7cdf..9c7791383c 100644 --- a/Tests/Runtime/NativeAPITest.cs +++ b/Tests/Runtime/NativeAPITest.cs @@ -359,13 +359,11 @@ public IEnumerator CallVideoUpdateMethodsEncode() Batch batch = new Batch(); int trackIndex = 0; - batch.data.tracks[trackIndex] = ptr; - batch.data.tracksCount = ++trackIndex; + batch.tracks[trackIndex] = ptr; yield return new WaitForSeconds(1.0f); - Marshal.StructureToPtr(batch.data, batch.ptr, false); - VideoUpdateMethods.BatchUpdate(batchUpdateEvent, batchUpdateEventID, batch.ptr); + VideoUpdateMethods.BatchUpdate(batchUpdateEvent, batchUpdateEventID, batch.GetPtr()); VideoUpdateMethods.Flush(); yield return new WaitForSeconds(1.0f); @@ -414,15 +412,11 @@ public IEnumerator CallVideoUpdateMethodsUpdateRenderer() Batch batch = new Batch(); int trackIndex = 0; - batch.data.tracks[trackIndex] = ptr; - batch.data.tracksCount = ++trackIndex; + batch.tracks[trackIndex] = ptr; yield return new WaitForSeconds(1.0f); - Marshal.StructureToPtr(batch.data, batch.ptr, false); - VideoUpdateMethods.BatchUpdate(batchUpdateEvent, batchUpdateEventID, batch.ptr); - - // this method is not supported on Direct3D12 + VideoUpdateMethods.BatchUpdate(batchUpdateEvent, batchUpdateEventID, batch.GetPtr()); VideoUpdateMethods.UpdateRendererTexture(updateTextureEvent, receiveTexture, rendererId); VideoUpdateMethods.Flush();