1 #include "EXGLNativeContext.h"
2 #include "EXPlatformUtils.h"
3 
4 namespace expo {
5 namespace gl_cpp {
6 
7 constexpr const char *OnJSRuntimeDestroyPropertyName = "__EXGLOnJsRuntimeDestroy";
8 
prepareContext(jsi::Runtime & runtime,std::function<void (void)> flushMethod)9 void EXGLContext::prepareContext(jsi::Runtime &runtime, std::function<void(void)> flushMethod) {
10   this->flushOnGLThread = flushMethod;
11   try {
12     this->initialGlesContext = prepareOpenGLESContext();
13     createWebGLRenderer(runtime, this, this->initialGlesContext, runtime.global());
14     tryRegisterOnJSRuntimeDestroy(runtime);
15 
16     maybeResolveWorkletContext(runtime);
17   } catch (const std::runtime_error &err) {
18     EXGLSysLog("Failed to setup EXGLContext [%s]", err.what());
19   }
20 }
21 
maybeResolveWorkletContext(jsi::Runtime & runtime)22 void EXGLContext::maybeResolveWorkletContext(jsi::Runtime &runtime) {
23   jsi::Value workletRuntimeValue = runtime.global().getProperty(runtime, "_WORKLET_RUNTIME");
24   if (!workletRuntimeValue.isObject()) {
25     return;
26   }
27   jsi::Object workletRuntimeObject = workletRuntimeValue.getObject(runtime);
28   if (!workletRuntimeObject.isArrayBuffer(runtime)) {
29     return;
30   }
31   size_t pointerSize = sizeof(void *);
32   jsi::ArrayBuffer workletRuntimeArrayBuffer = workletRuntimeObject.getArrayBuffer(runtime);
33   if (workletRuntimeArrayBuffer.size(runtime) != pointerSize) {
34     return;
35   }
36   uintptr_t rawWorkletRuntimePointer =
37       *reinterpret_cast<uintptr_t *>(workletRuntimeArrayBuffer.data(runtime));
38   jsi::Runtime *workletRuntime = reinterpret_cast<jsi::Runtime *>(rawWorkletRuntimePointer);
39   this->maybeWorkletRuntime = workletRuntime;
40 }
41 
prepareWorkletContext()42 void EXGLContext::prepareWorkletContext() {
43   if (maybeWorkletRuntime == nullptr) {
44     return;
45   }
46   jsi::Runtime &runtime = *this->maybeWorkletRuntime;
47   createWebGLRenderer(
48       runtime, this, initialGlesContext, runtime.global().getPropertyAsObject(runtime, "global"));
49   tryRegisterOnJSRuntimeDestroy(runtime);
50 }
51 
endNextBatch()52 void EXGLContext::endNextBatch() noexcept {
53   std::lock_guard<std::mutex> lock(backlogMutex);
54   backlog.push_back(std::move(nextBatch));
55   nextBatch = std::vector<Op>();
56   nextBatch.reserve(16); // default batch size
57 }
58 
59 // [JS thread] Add an Op to the 'next' batch -- the arguments are any form of
60 // constructor arguments for Op
addToNextBatch(Op && op)61 void EXGLContext::addToNextBatch(Op &&op) noexcept {
62   nextBatch.push_back(std::move(op));
63 }
64 
65 // [JS thread] Add a blocking operation to the 'next' batch -- waits for the
66 // queued function to run before returning
addBlockingToNextBatch(Op && op)67 void EXGLContext::addBlockingToNextBatch(Op &&op) {
68   std::packaged_task<void(void)> task(std::move(op));
69   auto future = task.get_future();
70   addToNextBatch([&] { task(); });
71   endNextBatch();
72   flushOnGLThread();
73   future.wait();
74 }
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.
addFutureToNextBatch(jsi::Runtime & runtime,std::function<unsigned int (void)> && op)78 jsi::Value EXGLContext::addFutureToNextBatch(
79     jsi::Runtime &runtime,
80     std::function<unsigned int(void)> &&op) noexcept {
81   auto exglObjId = createObject();
82   addToNextBatch([=] {
83     assert(objects.find(exglObjId) == objects.end());
84     mapObject(exglObjId, op());
85   });
86   return static_cast<double>(exglObjId);
87 }
88 
89 // [GL thread] Do all the remaining work we can do on the GL thread
flush(void)90 void EXGLContext::flush(void) {
91   // Keep a copy and clear backlog to minimize lock time
92   std::vector<Batch> copy;
93   {
94     std::lock_guard<std::mutex> lock(backlogMutex);
95     std::swap(backlog, copy);
96   }
97   for (const auto &batch : copy) {
98     for (const auto &op : batch) {
99       op();
100     }
101   }
102 }
103 
createObject(void)104 EXGLObjectId EXGLContext::createObject(void) noexcept {
105   return nextObjectId++;
106 }
107 
destroyObject(EXGLObjectId exglObjId)108 void EXGLContext::destroyObject(EXGLObjectId exglObjId) noexcept {
109   objects.erase(exglObjId);
110 }
111 
mapObject(EXGLObjectId exglObjId,GLuint glObj)112 void EXGLContext::mapObject(EXGLObjectId exglObjId, GLuint glObj) noexcept {
113   objects[exglObjId] = glObj;
114 }
115 
lookupObject(EXGLObjectId exglObjId)116 GLuint EXGLContext::lookupObject(EXGLObjectId exglObjId) noexcept {
117   auto iter = objects.find(exglObjId);
118   return iter == objects.end() ? 0 : iter->second;
119 }
120 
tryRegisterOnJSRuntimeDestroy(jsi::Runtime & runtime)121 void EXGLContext::tryRegisterOnJSRuntimeDestroy(jsi::Runtime &runtime) {
122   auto global = runtime.global();
123 
124   if (global.getProperty(runtime, OnJSRuntimeDestroyPropertyName).isObject()) {
125     return;
126   }
127   // Property `__EXGLOnJsRuntimeDestroy` of the global object will be released when entire
128   // `jsi::Runtime` is being destroyed and that will trigger destructor of
129   // `InvalidateCacheOnDestroy` class which will invalidate JSI PropNameID cache.
130   global.setProperty(
131       runtime,
132       OnJSRuntimeDestroyPropertyName,
133       jsi::Object::createFromHostObject(
134           runtime, std::make_shared<InvalidateCacheOnDestroy>(runtime)));
135 }
136 
prepareOpenGLESContext()137 glesContext EXGLContext::prepareOpenGLESContext() {
138   glesContext result;
139   // Clear everything to initial values
140   addBlockingToNextBatch([&] {
141     std::string version = reinterpret_cast<const char *>(glGetString(GL_VERSION));
142     double glesVersion = strtod(version.substr(10).c_str(), 0);
143     this->supportsWebGL2 = glesVersion >= 3.0;
144 
145     glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);
146     GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
147 
148     // This should not be called on headless contexts as they don't have default framebuffer.
149     // On headless context, status is undefined.
150     if (status != GL_FRAMEBUFFER_UNDEFINED) {
151       glClearColor(0, 0, 0, 0);
152       glClearDepthf(1);
153       glClearStencil(0);
154       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
155       int32_t viewport[4];
156       glGetIntegerv(GL_VIEWPORT, viewport);
157       result.viewportWidth = viewport[2];
158       result.viewportHeight = viewport[3];
159     } else {
160       // Set up an initial viewport for headless context.
161       // These values are the same as newly created WebGL context has,
162       // however they should be changed by the user anyway.
163       glViewport(0, 0, 300, 150);
164       result.viewportWidth = 300;
165       result.viewportHeight = 150;
166     }
167   });
168   return result;
169 }
170 
maybeReadAndCacheSupportedExtensions()171 void EXGLContext::maybeReadAndCacheSupportedExtensions() {
172   if (supportedExtensions.size() == 0) {
173     addBlockingToNextBatch([&] {
174       GLint numExtensions = 0;
175       glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions);
176 
177       for (auto i = 0; i < numExtensions; i++) {
178         std::string extensionName(reinterpret_cast<const char *>(glGetStringi(GL_EXTENSIONS, i)));
179 
180         // OpenGL ES prefixes extension names with `GL_`, need to trim this.
181         if (extensionName.substr(0, 3) == "GL_") {
182           extensionName.erase(0, 3);
183         }
184         if (extensionName != "OES_vertex_array_object") {
185           supportedExtensions.insert(extensionName);
186         }
187       }
188     });
189 
190     supportedExtensions.insert("OES_texture_float_linear");
191     supportedExtensions.insert("OES_texture_half_float_linear");
192 
193     // OpenGL ES 3.0 supports these out of the box.
194     if (supportsWebGL2) {
195       supportedExtensions.insert("WEBGL_compressed_texture_astc");
196       supportedExtensions.insert("WEBGL_compressed_texture_etc");
197     }
198 
199 #ifdef __APPLE__
200     // All iOS devices support PVRTC compression format.
201     supportedExtensions.insert("WEBGL_compressed_texture_pvrtc");
202 #endif
203   }
204 }
205 
206 } // namespace gl_cpp
207 } // namespace expo
208