1 #pragma once
2 
3 #include "EXGLNativeApi.h"
4 
5 #ifdef __ANDROID__
6 #include <GLES3/gl3.h>
7 #include <GLES3/gl3ext.h>
8 #endif
9 #ifdef __APPLE__
10 #include <OpenGLES/EAGL.h>
11 #include <OpenGLES/ES3/gl.h>
12 #include <OpenGLES/ES3/glext.h>
13 #endif
14 
15 #include "EXTypedArrayApi.h"
16 
17 #include <exception>
18 #include <future>
19 #include <set>
20 #include <sstream>
21 #include <unordered_map>
22 #include <vector>
23 
24 #include <jsi/jsi.h>
25 
26 #include "EXJsiUtils.h"
27 #include "EXPlatformUtils.h"
28 #include "EXWebGLRenderer.h"
29 
30 // Constants in WebGL that aren't in OpenGL ES
31 // https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Constants
32 
33 #define GL_UNPACK_FLIP_Y_WEBGL 0x9240
34 #define GL_UNPACK_PREMULTIPLY_ALPHA_WEBGL 0x9241
35 #define GL_CONTEXT_LOST_WEBGL 0x9242
36 #define GL_UNPACK_COLORSPACE_CONVERSION_WEBGL 0x9243
37 #define GL_BROWSER_DEFAULT_WEBGL 0x9244
38 #define GL_MAX_CLIENT_WAIT_TIMEOUT_WEBGL 0x9247
39 
40 #define GL_STENCIL_INDEX 0x1901
41 #define GL_DEPTH_STENCIL 0x84F9
42 #define GL_DEPTH_STENCIL_ATTACHMENT 0x821A
43 
44 namespace expo {
45 namespace gl_cpp {
46 
47 class EXGLContext {
48   using Op = std::function<void(void)>;
49   using Batch = std::vector<Op>;
50 
51  public:
EXGLContext(EXGLContextId ctxId)52   EXGLContext(EXGLContextId ctxId) : ctxId(ctxId) {}
53   void prepareContext(jsi::Runtime &runtime, std::function<void(void)> flushMethod);
54   void maybeResolveWorkletContext(jsi::Runtime &runtime);
55   void prepareWorkletContext();
56 
57   // --- Queue handling --------------------------------------------------------
58 
59   // There are two threads: the input thread (henceforth "JS thread") feeds new GL
60   // work, the output thread (henceforth "GL thread", typically UI thread on iOS,
61   // GL thread on Android) reads GL work and performs it
62 
63   // Ops are combined into batches:
64   //   1. A batch is always executed entirely in one go on the GL thread
65   //   2. The last add to a batch always precedes the first remove
66   // #2 means that it's good to use an std::vector<...> for this
67 
68   // [JS thread] Send the current 'next' batch to GL and make a new 'next' batch
69   void endNextBatch() noexcept;
70   // [JS thread] Add an Op to the 'next' batch
71   void addToNextBatch(Op &&op) noexcept;
72   // [JS thread] Add a blocking operation to the 'next' batch -- waits for the
73   // queued function to run before returning
74   void addBlockingToNextBatch(Op &&op);
75 
76   // [JS thread] Enqueue a function and return an EXGL object that will get mapped
77   // to the function's return value when it is called on the GL thread.
78   //
79   // We call these 'futures': a return value from a GL method call that is simply
80   // fed to other GL method calls. The value is never inspected in JS. This
81   // allows us to continue queueing method calls when a method call with a
82   // 'future' return value is encountered: its value won't immediately matter
83   // and is only needed when method calls after it ask for the value, and those
84   // are queued for even later.
85   //
86   // To make it work lookupObject can be called only on GL thread
87   //
88   jsi::Value addFutureToNextBatch(
89       jsi::Runtime &runtime,
90       std::function<unsigned int(void)> &&op) noexcept;
91 
92   // [GL thread] Do all the remaining work we can do on the GL thread
93   // triggered by call to flushOnGLThread
94   void flush(void);
95 
96   // --- Object mapping --------------------------------------------------------
97 
98   // We err on the side of performance and hope that a global incrementing atomic
99   // unsigned int is enough for object ids. On 'creating' an object we simply
100   // 'reserve' the id by incrementing the atomic counter. Since the mapping is only
101   // set and read on the GL thread, this prevents us from having to maintain a
102   // mutex on the mapping.
103 
104   EXGLObjectId createObject(void) noexcept;
105   void destroyObject(EXGLObjectId exglObjId) noexcept;
106   void mapObject(EXGLObjectId exglObjId, GLuint glObj) noexcept;
107   GLuint lookupObject(EXGLObjectId exglObjId) noexcept;
108 
109   void tryRegisterOnJSRuntimeDestroy(jsi::Runtime &runtime);
110   glesContext prepareOpenGLESContext();
111   void maybeReadAndCacheSupportedExtensions();
112 
113  private:
114   // Queue
115   Batch nextBatch;
116   std::vector<Batch> backlog;
117   std::mutex backlogMutex;
118 
119  public:
120   EXGLContextId ctxId;
121   // Worklet runtime is stored here only to avoid it passing through Java/Obj-C.
122   // It should only be used in prepareContext and prepareWorkletContext.
123   jsi::Runtime *maybeWorkletRuntime = nullptr;
124   glesContext initialGlesContext;
125 
126   // Object mapping
127   std::unordered_map<EXGLObjectId, GLuint> objects;
128   std::atomic_uint nextObjectId = 1;
129 
130   bool supportsWebGL2 = false;
131   std::set<const std::string> supportedExtensions;
132 
133   // function that calls flush on GL thread - on Android it is passed by JNI
134   std::function<void(void)> flushOnGLThread = [&] {};
135 
136   // OpenGLES state
137   bool needsRedraw = false;
138   GLint defaultFramebuffer = 0;
139   bool unpackFLipY = false;
140 };
141 
142 } // namespace gl_cpp
143 } // namespace expo
144