#include "EXGLNativeContext.h" #include "EXPlatformUtils.h" namespace expo { namespace gl_cpp { constexpr const char *OnJSRuntimeDestroyPropertyName = "__EXGLOnJsRuntimeDestroy"; void EXGLContext::prepareContext(jsi::Runtime &runtime, std::function flushMethod) { this->flushOnGLThread = flushMethod; try { this->initialGlesContext = prepareOpenGLESContext(); createWebGLRenderer(runtime, this, this->initialGlesContext, runtime.global()); tryRegisterOnJSRuntimeDestroy(runtime); maybeResolveWorkletContext(runtime); } catch (const std::runtime_error &err) { EXGLSysLog("Failed to setup EXGLContext [%s]", err.what()); } } void EXGLContext::maybeResolveWorkletContext(jsi::Runtime &runtime) { jsi::Value workletRuntimeValue = runtime.global().getProperty(runtime, "_WORKLET_RUNTIME"); if (!workletRuntimeValue.isObject()) { return; } jsi::Object workletRuntimeObject = workletRuntimeValue.getObject(runtime); if (!workletRuntimeObject.isArrayBuffer(runtime)) { return; } size_t pointerSize = sizeof(void *); jsi::ArrayBuffer workletRuntimeArrayBuffer = workletRuntimeObject.getArrayBuffer(runtime); if (workletRuntimeArrayBuffer.size(runtime) != pointerSize) { return; } uintptr_t rawWorkletRuntimePointer = *reinterpret_cast(workletRuntimeArrayBuffer.data(runtime)); jsi::Runtime *workletRuntime = reinterpret_cast(rawWorkletRuntimePointer); this->maybeWorkletRuntime = workletRuntime; } void EXGLContext::prepareWorkletContext() { if (maybeWorkletRuntime == nullptr) { return; } jsi::Runtime &runtime = *this->maybeWorkletRuntime; createWebGLRenderer( runtime, this, initialGlesContext, runtime.global().getPropertyAsObject(runtime, "global")); tryRegisterOnJSRuntimeDestroy(runtime); } void EXGLContext::endNextBatch() noexcept { std::lock_guard lock(backlogMutex); backlog.push_back(std::move(nextBatch)); nextBatch = std::vector(); nextBatch.reserve(16); // default batch size } // [JS thread] Add an Op to the 'next' batch -- the arguments are any form of // constructor arguments for Op void EXGLContext::addToNextBatch(Op &&op) noexcept { nextBatch.push_back(std::move(op)); } // [JS thread] Add a blocking operation to the 'next' batch -- waits for the // queued function to run before returning void EXGLContext::addBlockingToNextBatch(Op &&op) { std::packaged_task task(std::move(op)); auto future = task.get_future(); addToNextBatch([&] { task(); }); endNextBatch(); flushOnGLThread(); future.wait(); } // [JS thread] Enqueue a function and return an EXGL object that will get mapped // to the function's return value when it is called on the GL thread. jsi::Value EXGLContext::addFutureToNextBatch( jsi::Runtime &runtime, std::function &&op) noexcept { auto exglObjId = createObject(); addToNextBatch([=] { assert(objects.find(exglObjId) == objects.end()); mapObject(exglObjId, op()); }); return static_cast(exglObjId); } // [GL thread] Do all the remaining work we can do on the GL thread void EXGLContext::flush(void) { // Keep a copy and clear backlog to minimize lock time std::vector copy; { std::lock_guard lock(backlogMutex); std::swap(backlog, copy); } for (const auto &batch : copy) { for (const auto &op : batch) { op(); } } } EXGLObjectId EXGLContext::createObject(void) noexcept { return nextObjectId++; } void EXGLContext::destroyObject(EXGLObjectId exglObjId) noexcept { objects.erase(exglObjId); } void EXGLContext::mapObject(EXGLObjectId exglObjId, GLuint glObj) noexcept { objects[exglObjId] = glObj; } GLuint EXGLContext::lookupObject(EXGLObjectId exglObjId) noexcept { auto iter = objects.find(exglObjId); return iter == objects.end() ? 0 : iter->second; } void EXGLContext::tryRegisterOnJSRuntimeDestroy(jsi::Runtime &runtime) { auto global = runtime.global(); if (global.getProperty(runtime, OnJSRuntimeDestroyPropertyName).isObject()) { return; } // Property `__EXGLOnJsRuntimeDestroy` of the global object will be released when entire // `jsi::Runtime` is being destroyed and that will trigger destructor of // `InvalidateCacheOnDestroy` class which will invalidate JSI PropNameID cache. global.setProperty( runtime, OnJSRuntimeDestroyPropertyName, jsi::Object::createFromHostObject( runtime, std::make_shared(runtime))); } glesContext EXGLContext::prepareOpenGLESContext() { glesContext result; // Clear everything to initial values addBlockingToNextBatch([&] { std::string version = reinterpret_cast(glGetString(GL_VERSION)); double glesVersion = strtod(version.substr(10).c_str(), 0); this->supportsWebGL2 = glesVersion >= 3.0; glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); // This should not be called on headless contexts as they don't have default framebuffer. // On headless context, status is undefined. if (status != GL_FRAMEBUFFER_UNDEFINED) { glClearColor(0, 0, 0, 0); glClearDepthf(1); glClearStencil(0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); int32_t viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); result.viewportWidth = viewport[2]; result.viewportHeight = viewport[3]; } else { // Set up an initial viewport for headless context. // These values are the same as newly created WebGL context has, // however they should be changed by the user anyway. glViewport(0, 0, 300, 150); result.viewportWidth = 300; result.viewportHeight = 150; } }); return result; } void EXGLContext::maybeReadAndCacheSupportedExtensions() { if (supportedExtensions.size() == 0) { addBlockingToNextBatch([&] { GLint numExtensions = 0; glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions); for (auto i = 0; i < numExtensions; i++) { std::string extensionName(reinterpret_cast(glGetStringi(GL_EXTENSIONS, i))); // OpenGL ES prefixes extension names with `GL_`, need to trim this. if (extensionName.substr(0, 3) == "GL_") { extensionName.erase(0, 3); } if (extensionName != "OES_vertex_array_object") { supportedExtensions.insert(extensionName); } } }); supportedExtensions.insert("OES_texture_float_linear"); supportedExtensions.insert("OES_texture_half_float_linear"); // OpenGL ES 3.0 supports these out of the box. if (supportsWebGL2) { supportedExtensions.insert("WEBGL_compressed_texture_astc"); supportedExtensions.insert("WEBGL_compressed_texture_etc"); } #ifdef __APPLE__ // All iOS devices support PVRTC compression format. supportedExtensions.insert("WEBGL_compressed_texture_pvrtc"); #endif } } } // namespace gl_cpp } // namespace expo