1 #include "EXWebGLRenderer.h"
2 #include "EXGLNativeContext.h"
3 #include "EXGLContextManager.h"
4 #include "EXJsiUtils.h"
5 #include "EXWebGLMethods.h"
6 #include <jsi/jsi.h>
7 
8 namespace expo {
9 namespace gl_cpp {
10 
11 constexpr const char *EXGLContextsMapPropertyName = "__EXGLContexts";
12 
13 // There is no way to crate function that can be used as constructor
14 // using jsi api, so we need to set it up via eval, it will be called
15 // only once so perofmance impact should be minimal
16 constexpr const char *evalStubConstructors = R"(
17 WebGLRenderingContext = function() {};
18 WebGL2RenderingContext = function() {};
19 WebGLObject = function() {};
20 WebGLBuffer = function() {};
21 WebGLFramebuffer = function() {};
22 WebGLProgram = function() {};
23 WebGLRenderbuffer = function() {};
24 WebGLShader = function() {};
25 WebGLTexture = function() {};
26 WebGLUniformLocation = function() {};
27 WebGLActiveInfo = function() {};
28 WebGLShaderPrecisionFormat = function() {};
29 WebGLQuery = function() {};
30 WebGLSampler = function() {};
31 WebGLSync = function() {};
32 WebGLTransformFeedback = function() {};
33 WebGLVertexArrayObject = function() {};
34 )";
35 
36 void installConstants(jsi::Runtime &runtime, jsi::Object &gl);
37 void installWebGLMethods(jsi::Runtime &runtime, jsi::Object &gl);
38 void installWebGL2Methods(jsi::Runtime &runtime, jsi::Object &gl);
39 
40 void createWebGLRenderer(jsi::Runtime &runtime, EXGLContext *ctx, initGlesContext viewport, jsi::Object&& global) {
41   ensurePrototypes(runtime);
42   jsi::Object gl = ctx->supportsWebGL2
43     ? createWebGLObject(
44           runtime, EXWebGLClass::WebGL2RenderingContext, {static_cast<double>(ctx->ctxId)})
45           .asObject(runtime)
46     : createWebGLObject(
47           runtime, EXWebGLClass::WebGLRenderingContext, {static_cast<double>(ctx->ctxId)})
48           .asObject(runtime);
49 
50   gl.setProperty(runtime, "drawingBufferWidth", viewport.viewportWidth);
51   gl.setProperty(runtime, "drawingBufferHeight", viewport.viewportHeight);
52   gl.setProperty(runtime, "supportsWebGL2", ctx->supportsWebGL2);
53   gl.setProperty(runtime, "contextId", static_cast<double>(ctx->ctxId));
54 
55   // Legacy case for older SDKs in Expo Go
56   bool legacyJs = !runtime.global().getProperty(runtime, "__EXGLConstructorReady").isBool();
57   if (legacyJs) {
58     installConstants(runtime, gl);
59     ctx->supportsWebGL2 ? installWebGL2Methods(runtime, gl) : installWebGLMethods(runtime, gl);
60   }
61 
62   jsi::Value jsContextMap = global.getProperty(runtime, EXGLContextsMapPropertyName);
63   if (jsContextMap.isNull() || jsContextMap.isUndefined()) {
64     global.setProperty(runtime, EXGLContextsMapPropertyName, jsi::Object(runtime));
65   }
66   global.getProperty(runtime, EXGLContextsMapPropertyName)
67       .asObject(runtime)
68       .setProperty(runtime, jsi::PropNameID::forUtf8(runtime, std::to_string(ctx->ctxId)), gl);
69 }
70 
71 // We are assuming that eval was called before first object
72 // is created and all global.WebGL... stub functions already exist
73 jsi::Value createWebGLObject(
74     jsi::Runtime &runtime,
75     EXWebGLClass webglClass,
76     std::initializer_list<jsi::Value> &&args) {
77   jsi::Object webglObject = runtime.global()
78           .getProperty(runtime, jsi::PropNameID::forUtf8(runtime, getConstructorName(webglClass)))
79           .asObject(runtime)
80           .asFunction(runtime)
81           .callAsConstructor(runtime, {})
82           .asObject(runtime);
83   jsi::Value id = args.size() > 0 ? jsi::Value(runtime, *args.begin()) : jsi::Value::undefined();
84   webglObject.setProperty(runtime, "id", id);
85   return webglObject;
86 }
87 
88 std::string getConstructorName(EXWebGLClass value) {
89   switch (value) {
90     case EXWebGLClass::WebGLRenderingContext:
91       return "WebGLRenderingContext";
92     case EXWebGLClass::WebGL2RenderingContext:
93       return "WebGL2RenderingContext";
94     case EXWebGLClass::WebGLObject:
95       return "WebGLObject";
96     case EXWebGLClass::WebGLBuffer:
97       return "WebGLBuffer";
98     case EXWebGLClass::WebGLFramebuffer:
99       return "WebGLFramebuffer";
100     case EXWebGLClass::WebGLProgram:
101       return "WebGLProgram";
102     case EXWebGLClass::WebGLRenderbuffer:
103       return "WebGLRenderbuffer";
104     case EXWebGLClass::WebGLShader:
105       return "WebGLShader";
106     case EXWebGLClass::WebGLTexture:
107       return "WebGLTexture";
108     case EXWebGLClass::WebGLUniformLocation:
109       return "WebGLUniformLocation";
110     case EXWebGLClass::WebGLActiveInfo:
111       return "WebGLActiveInfo";
112     case EXWebGLClass::WebGLShaderPrecisionFormat:
113       return "WebGLShaderPrecisionFormat";
114     case EXWebGLClass::WebGLQuery:
115       return "WebGLQuery";
116     case EXWebGLClass::WebGLSampler:
117       return "WebGLSampler";
118     case EXWebGLClass::WebGLSync:
119       return "WebGLSync";
120     case EXWebGLClass::WebGLTransformFeedback:
121       return "WebGLTransformFeedback";
122     case EXWebGLClass::WebGLVertexArrayObject:
123       return "WebGLVertexArrayObject";
124   }
125 }
126 
127 void attachClass(
128     jsi::Runtime &runtime,
129     EXWebGLClass webglClass,
130     std::function<void(EXWebGLClass webglClass)> installPrototypes) {
131   jsi::PropNameID name = jsi::PropNameID::forUtf8(runtime, getConstructorName(webglClass));
132   installPrototypes(webglClass);
133 }
134 
135 // https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance#setting_teachers_prototype_and_constructor_reference
136 //
137 // Below implementation is equivalent of `class WebGLBuffer extends WebGLObject {}`
138 // where baseClass=global.WebGLObject and derivedProp="WebGLBuffer"
139 //
140 // WebGLBuffer.prototype = Object.create(WebGLObject.prototype);
141 // Object.defineProperty(WebGLBuffer.prototype, 'constructor', {
142 //   value: WebGLBuffer,
143 //   enumerable: false,
144 //   configurable: true,
145 //   writable: true });
146 void jsClassExtend(jsi::Runtime &runtime, jsi::Object &baseClass, jsi::PropNameID derivedProp) {
147   jsi::PropNameID prototype = jsi::PropNameID::forUtf8(runtime, "prototype");
148   jsi::Object objectClass = runtime.global().getPropertyAsObject(runtime, "Object");
149   jsi::Function createMethod = objectClass.getPropertyAsFunction(runtime, "create");
150   jsi::Function definePropertyMethod = objectClass.getPropertyAsFunction(runtime, "defineProperty");
151   jsi::Object derivedClass = runtime.global().getProperty(runtime, derivedProp).asObject(runtime);
152 
153   // WebGLBuffer.prototype = Object.create(WebGLObject.prototype);
154   derivedClass.setProperty(
155       runtime,
156       prototype,
157       createMethod.callWithThis(runtime, objectClass, {baseClass.getProperty(runtime, prototype)}));
158 
159   jsi::Object propertyOptions(runtime);
160   propertyOptions.setProperty(runtime, "value", derivedClass);
161   propertyOptions.setProperty(runtime, "enumerable", false);
162   propertyOptions.setProperty(runtime, "configurable", true);
163   propertyOptions.setProperty(runtime, "writable", true);
164 
165   // Object.defineProperty ...
166   definePropertyMethod.callWithThis(
167       runtime,
168       objectClass,
169       {
170           derivedClass.getProperty(runtime, prototype),
171           jsi::String::createFromUtf8(runtime, "constructor"),
172           std::move(propertyOptions),
173       });
174 }
175 
176 void ensurePrototypes(jsi::Runtime &runtime) {
177   if (runtime.global().hasProperty(runtime, "WebGLRenderingContext")) {
178     return;
179   }
180   runtime.global().setProperty(runtime, "__EXGLConstructorReady", true);
181 
182   auto evalBuffer = std::make_shared<jsi::StringBuffer>(evalStubConstructors);
183   runtime.evaluateJavaScript(evalBuffer, "expo-gl");
184 
185   auto inheritFromJsObject = [&runtime](EXWebGLClass classEnum) {
186     auto objectClass = runtime.global().getPropertyAsObject(runtime, "Object");
187     jsClassExtend(
188         runtime, objectClass, jsi::PropNameID::forUtf8(runtime, getConstructorName(classEnum)));
189   };
190 
191   // configure WebGLRenderingContext
192   {
193     inheritFromJsObject(EXWebGLClass::WebGLRenderingContext);
194     auto prototype =
195         runtime.global()
196             .getProperty(runtime, jsi::PropNameID::forUtf8(runtime, getConstructorName(EXWebGLClass::WebGLRenderingContext)))
197             .asObject(runtime)
198             .getPropertyAsObject(runtime, "prototype");
199     installConstants(runtime, prototype);
200     installWebGLMethods(runtime, prototype);
201   }
202 
203   // configure WebGL2RenderingContext
204   {
205     inheritFromJsObject(EXWebGLClass::WebGL2RenderingContext);
206     auto prototype =
207         runtime.global()
208             .getProperty(runtime, jsi::PropNameID::forUtf8(runtime, getConstructorName(EXWebGLClass::WebGL2RenderingContext)))
209             .asObject(runtime)
210             .getPropertyAsObject(runtime, "prototype");
211     installConstants(runtime, prototype);
212     installWebGL2Methods(runtime, prototype);
213   }
214 
215   // Configure rest of WebGL objects
216   inheritFromJsObject(EXWebGLClass::WebGLObject);
217 
218   jsi::Object webglObjectClass =
219       runtime.global()
220           .getProperty(
221               runtime,
222               jsi::PropNameID::forUtf8(runtime, getConstructorName(EXWebGLClass::WebGLObject)))
223           .asObject(runtime);
224   auto inheritFromWebGLObject = [&runtime, &webglObjectClass](EXWebGLClass classEnum) {
225     jsClassExtend(
226         runtime,
227         webglObjectClass,
228         jsi::PropNameID::forUtf8(runtime, getConstructorName(classEnum)));
229   };
230 
231   inheritFromWebGLObject(EXWebGLClass::WebGLBuffer);
232   inheritFromWebGLObject(EXWebGLClass::WebGLFramebuffer);
233   inheritFromWebGLObject(EXWebGLClass::WebGLProgram);
234   inheritFromWebGLObject(EXWebGLClass::WebGLRenderbuffer);
235   inheritFromWebGLObject(EXWebGLClass::WebGLShader);
236   inheritFromWebGLObject(EXWebGLClass::WebGLTexture);
237   inheritFromJsObject(EXWebGLClass::WebGLUniformLocation);
238   inheritFromJsObject(EXWebGLClass::WebGLActiveInfo);
239   inheritFromJsObject(EXWebGLClass::WebGLShaderPrecisionFormat);
240   inheritFromWebGLObject(EXWebGLClass::WebGLQuery);
241   inheritFromWebGLObject(EXWebGLClass::WebGLSampler);
242   inheritFromWebGLObject(EXWebGLClass::WebGLSync);
243   inheritFromWebGLObject(EXWebGLClass::WebGLTransformFeedback);
244   inheritFromWebGLObject(EXWebGLClass::WebGLVertexArrayObject);
245 }
246 
247 void installConstants(jsi::Runtime &runtime, jsi::Object &gl) {
248 #define GL_CONSTANT(name) gl.setProperty(runtime, #name, static_cast<double>(GL_##name));
249 #include "EXWebGLConstants.def"
250 #undef GL_CONSTANT
251 }
252 
253 void installWebGLMethods(jsi::Runtime &runtime, jsi::Object &gl) {
254 #define NATIVE_METHOD(name) setFunctionOnObject(runtime, gl, #name, method::glNativeMethod_##name);
255 
256 #define NATIVE_WEBGL2_METHOD(name) ;
257 #include "EXWebGLMethods.def"
258 #undef NATIVE_WEBGL2_METHOD
259 #undef NATIVE_METHOD
260 }
261 
262 void installWebGL2Methods(jsi::Runtime &runtime, jsi::Object &gl) {
263 #define CREATE_METHOD(name) setFunctionOnObject(runtime, gl, #name, method::glNativeMethod_##name);
264 
265 #define NATIVE_METHOD(name) CREATE_METHOD(name)
266 #define NATIVE_WEBGL2_METHOD(name) CREATE_METHOD(name)
267 #include "EXWebGLMethods.def"
268 #undef NATIVE_WEBGL2_METHOD
269 #undef NATIVE_METHOD
270 #undef CREATE_METHOD
271 }
272 
273 } // namespace gl_cpp
274 } // namespace expo
275