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