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