aboutsummaryrefslogtreecommitdiff
path: root/sdk/android/api/org/webrtc/EglThread.java
blob: 73323d59c89d55671d727ae4c71511e693578851 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
/*
 *  Copyright 2022 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

package org.webrtc;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import org.webrtc.EglBase.EglConnection;

/** EGL graphics thread that allows multiple clients to share the same underlying EGLContext. */
public class EglThread implements RenderSynchronizer.Listener {
  /** Callback for externally managed reference count. */
  public interface ReleaseMonitor {
    /**
     * Called by EglThread when a client releases its reference. Returns true when there are no more
     * references and resources should be released.
     */
    boolean onRelease(EglThread eglThread);
  }

  /** Interface for clients to schedule rendering updates that will run synchronized. */
  public interface RenderUpdate {

    /**
     * Called by EglThread when the rendering window is open. `runsInline` is true when the update
     * is executed directly while the client schedules the update.
     */
    void update(boolean runsInline);
  }

  public static EglThread create(
      @Nullable ReleaseMonitor releaseMonitor,
      @Nullable final EglBase.Context sharedContext,
      final int[] configAttributes,
      @Nullable RenderSynchronizer renderSynchronizer) {
    final HandlerThread renderThread = new HandlerThread("EglThread");
    renderThread.start();
    HandlerWithExceptionCallbacks handler =
        new HandlerWithExceptionCallbacks(renderThread.getLooper());

    // Not creating the EGLContext on the thread it will be used on seems to cause issues with
    // creating window surfaces on certain devices. So keep the same legacy behavior as EglRenderer
    // and create the context on the render thread.
    EglConnection eglConnection = ThreadUtils.invokeAtFrontUninterruptibly(handler, () -> {
      // If sharedContext is null, then texture frames are disabled. This is typically for old
      // devices that might not be fully spec compliant, so force EGL 1.0 since EGL 1.4 has
      // caused trouble on some weird devices.
      if (sharedContext == null) {
        return EglConnection.createEgl10(configAttributes);
      } else {
        return EglConnection.create(sharedContext, configAttributes);
      }
    });

    return new EglThread(
        releaseMonitor != null ? releaseMonitor : eglThread -> true,
        handler,
        eglConnection,
        renderSynchronizer);
  }

  public static EglThread create(
      @Nullable ReleaseMonitor releaseMonitor,
      @Nullable final EglBase.Context sharedContext,
      final int[] configAttributes) {
    return create(releaseMonitor, sharedContext, configAttributes, /* renderSynchronizer= */ null);
  }

  /**
   * Handler that triggers callbacks when an uncaught exception happens when handling a message.
   */
  private static class HandlerWithExceptionCallbacks extends Handler {
    private final Object callbackLock = new Object();
    @GuardedBy("callbackLock") private final List<Runnable> exceptionCallbacks = new ArrayList<>();

    public HandlerWithExceptionCallbacks(Looper looper) {
      super(looper);
    }

    @Override
    public void dispatchMessage(Message msg) {
      try {
        super.dispatchMessage(msg);
      } catch (Exception e) {
        Logging.e("EglThread", "Exception on EglThread", e);
        synchronized (callbackLock) {
          for (Runnable callback : exceptionCallbacks) {
            callback.run();
          }
        }
        throw e;
      }
    }

    public void addExceptionCallback(Runnable callback) {
      synchronized (callbackLock) {
        exceptionCallbacks.add(callback);
      }
    }

    public void removeExceptionCallback(Runnable callback) {
      synchronized (callbackLock) {
        exceptionCallbacks.remove(callback);
      }
    }
  }

  private final ReleaseMonitor releaseMonitor;
  private final HandlerWithExceptionCallbacks handler;
  private final EglConnection eglConnection;
  private final RenderSynchronizer renderSynchronizer;
  private final List<RenderUpdate> pendingRenderUpdates = new ArrayList<>();
  private boolean renderWindowOpen = true;

  private EglThread(
      ReleaseMonitor releaseMonitor,
      HandlerWithExceptionCallbacks handler,
      EglConnection eglConnection,
      RenderSynchronizer renderSynchronizer) {
    this.releaseMonitor = releaseMonitor;
    this.handler = handler;
    this.eglConnection = eglConnection;
    this.renderSynchronizer = renderSynchronizer;
    if (renderSynchronizer != null) {
      renderSynchronizer.registerListener(this);
    }
  }

  public void release() {
    if (!releaseMonitor.onRelease(this)) {
      // Thread is still in use, do not release yet.
      return;
    }

    if (renderSynchronizer != null) {
      renderSynchronizer.removeListener(this);
    }

    handler.post(eglConnection::release);
    handler.getLooper().quitSafely();
  }

  /**
   * Creates an EglBase instance with the EglThread's EglConnection. This method can be called on
   * any thread, but the returned EglBase instance should only be used on this EglThread's Handler.
   */
  public EglBase createEglBaseWithSharedConnection() {
    return EglBase.create(eglConnection);
  }

  /**
   * Returns the Handler to interact with Gl/EGL on. Callers need to make sure that their own
   * EglBase is current on the handler before running any graphics operations since the EglThread
   * can be shared by multiple clients.
   */
  public Handler getHandler() {
    return handler;
  }

  /**
   * Adds a callback that will be called on the EGL thread if there is an exception on the thread.
   */
  public void addExceptionCallback(Runnable callback) {
    handler.addExceptionCallback(callback);
  }

  /**
   * Removes a previously added exception callback.
   */
  public void removeExceptionCallback(Runnable callback) {
    handler.removeExceptionCallback(callback);
  }

  /**
   * Schedules a render update (like swapBuffers) to be run in sync with other updates on the next
   * open render window. If the render window is currently open the update will run immediately.
   * This method must be called on the EglThread during a render pass.
   */
  public void scheduleRenderUpdate(RenderUpdate update) {
    if (renderWindowOpen) {
      update.update(/* runsInline = */true);
    } else {
      pendingRenderUpdates.add(update);
    }
  }

  @Override
  public void onRenderWindowOpen() {
    handler.post(
        () -> {
          renderWindowOpen = true;
          for (RenderUpdate update : pendingRenderUpdates) {
            update.update(/* runsInline = */false);
          }
          pendingRenderUpdates.clear();
        });
  }

  @Override
  public void onRenderWindowClose() {
    handler.post(() -> renderWindowOpen = false);
  }
}