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