Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fix GCAlloc #985

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 25 additions & 28 deletions Runtime/Scripts/Context.cs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<IntPtr> tracks;

BatchData data;

public Batch()
{
ResizeCapacity(1);
tracks = new NativeArray<IntPtr>(1, Allocator.Persistent);
}

~Batch()
Expand All @@ -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<IntPtr>(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
{
Expand Down Expand Up @@ -181,6 +177,7 @@ public void Dispose()

// Release buffers on the rendering thread
batch.Submit(true);
batch.Dispose();

NativeMethods.ContextDestroy(id);
self = IntPtr.Zero;
Expand Down
16 changes: 14 additions & 2 deletions Runtime/Scripts/Internal/ExecutableUnitySynchronizationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<WorkRequest> m_AsyncWorkQueue;
readonly List<WorkRequest> m_CurrentFrameWork = new List<WorkRequest>(k_AwqInitialCapacity);
Expand All @@ -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.");
Expand All @@ -42,7 +54,7 @@ internal ExecutableUnitySynchronizationContext(SynchronizationContext context)
m_AsyncWorkQueue = new List<WorkRequest>();

// 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<WorkRequest> queue, int mainThreadID)
Expand Down Expand Up @@ -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
Expand Down
27 changes: 19 additions & 8 deletions Runtime/Scripts/VideoStreamTrack.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using UnityEngine;
Expand Down Expand Up @@ -30,8 +30,8 @@ public class VideoStreamTrack : MediaStreamTrack
/// </summary>
public static bool NeedReceivedVideoFlipVertically { get; set; } = true;

internal static ConcurrentDictionary<IntPtr, WeakReference<VideoStreamTrack>> s_tracks =
new ConcurrentDictionary<IntPtr, WeakReference<VideoStreamTrack>>();
internal static Dictionary<IntPtr, WeakReference<VideoStreamTrack>> s_tracks =
new Dictionary<IntPtr, WeakReference<VideoStreamTrack>>();

internal enum VideoStreamTrackAction
{
Expand Down Expand Up @@ -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<VideoStreamTrack>(this)))
throw new InvalidOperationException();
lock (s_tracks)
{
if (s_tracks.ContainsKey(self))
throw new InvalidOperationException();
s_tracks.Add(self, new WeakReference<VideoStreamTrack>(this));
}

m_dataptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(VideoStreamTrackData)));
Marshal.StructureToPtr(m_data, m_dataptr, false);
Expand All @@ -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<VideoStreamTrack>(this)))
throw new InvalidOperationException();
lock (s_tracks)
{
if (s_tracks.ContainsKey(self))
throw new InvalidOperationException();
s_tracks.Add(self, new WeakReference<VideoStreamTrack>(this));
}

m_dataptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(VideoStreamTrackData)));
Marshal.StructureToPtr(m_data, m_dataptr, false);
Expand Down Expand Up @@ -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();
}
Expand Down
24 changes: 12 additions & 12 deletions Runtime/Scripts/WebRTC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
14 changes: 4 additions & 10 deletions Tests/Runtime/NativeAPITest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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();

Expand Down