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 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 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 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 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 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 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. 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 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 104 EXGLObjectId EXGLContext::createObject(void) noexcept { 105 return nextObjectId++; 106 } 107 108 void EXGLContext::destroyObject(EXGLObjectId exglObjId) noexcept { 109 objects.erase(exglObjId); 110 } 111 112 void EXGLContext::mapObject(EXGLObjectId exglObjId, GLuint glObj) noexcept { 113 objects[exglObjId] = glObj; 114 } 115 116 GLuint EXGLContext::lookupObject(EXGLObjectId exglObjId) noexcept { 117 auto iter = objects.find(exglObjId); 118 return iter == objects.end() ? 0 : iter->second; 119 } 120 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 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 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