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 
createWebGLRenderer(jsi::Runtime & runtime,EXGLContext * ctx,glesContext viewport,jsi::Object && global)40 void createWebGLRenderer(jsi::Runtime &runtime, EXGLContext *ctx, glesContext 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   jsi::Value jsContextMap = global.getProperty(runtime, EXGLContextsMapPropertyName);
56   if (jsContextMap.isNull() || jsContextMap.isUndefined()) {
57     global.setProperty(runtime, EXGLContextsMapPropertyName, jsi::Object(runtime));
58   }
59   global.getProperty(runtime, EXGLContextsMapPropertyName)
60       .asObject(runtime)
61       .setProperty(runtime, jsi::PropNameID::forUtf8(runtime, std::to_string(ctx->ctxId)), gl);
62 }
63 
64 // We are assuming that eval was called before first object
65 // is created and all global.WebGL... stub functions already exist
createWebGLObject(jsi::Runtime & runtime,EXWebGLClass webglClass,std::initializer_list<jsi::Value> && args)66 jsi::Value createWebGLObject(
67     jsi::Runtime &runtime,
68     EXWebGLClass webglClass,
69     std::initializer_list<jsi::Value> &&args) {
70   jsi::Object webglObject = runtime.global()
71           .getProperty(runtime, jsi::PropNameID::forUtf8(runtime, getConstructorName(webglClass)))
72           .asObject(runtime)
73           .asFunction(runtime)
74           .callAsConstructor(runtime, {})
75           .asObject(runtime);
76   jsi::Value id = args.size() > 0 ? jsi::Value(runtime, *args.begin()) : jsi::Value::undefined();
77   webglObject.setProperty(runtime, "id", id);
78   return webglObject;
79 }
80 
getConstructorName(EXWebGLClass value)81 std::string getConstructorName(EXWebGLClass value) {
82   switch (value) {
83     case EXWebGLClass::WebGLRenderingContext:
84       return "WebGLRenderingContext";
85     case EXWebGLClass::WebGL2RenderingContext:
86       return "WebGL2RenderingContext";
87     case EXWebGLClass::WebGLObject:
88       return "WebGLObject";
89     case EXWebGLClass::WebGLBuffer:
90       return "WebGLBuffer";
91     case EXWebGLClass::WebGLFramebuffer:
92       return "WebGLFramebuffer";
93     case EXWebGLClass::WebGLProgram:
94       return "WebGLProgram";
95     case EXWebGLClass::WebGLRenderbuffer:
96       return "WebGLRenderbuffer";
97     case EXWebGLClass::WebGLShader:
98       return "WebGLShader";
99     case EXWebGLClass::WebGLTexture:
100       return "WebGLTexture";
101     case EXWebGLClass::WebGLUniformLocation:
102       return "WebGLUniformLocation";
103     case EXWebGLClass::WebGLActiveInfo:
104       return "WebGLActiveInfo";
105     case EXWebGLClass::WebGLShaderPrecisionFormat:
106       return "WebGLShaderPrecisionFormat";
107     case EXWebGLClass::WebGLQuery:
108       return "WebGLQuery";
109     case EXWebGLClass::WebGLSampler:
110       return "WebGLSampler";
111     case EXWebGLClass::WebGLSync:
112       return "WebGLSync";
113     case EXWebGLClass::WebGLTransformFeedback:
114       return "WebGLTransformFeedback";
115     case EXWebGLClass::WebGLVertexArrayObject:
116       return "WebGLVertexArrayObject";
117   }
118 }
119 
attachClass(jsi::Runtime & runtime,EXWebGLClass webglClass,std::function<void (EXWebGLClass webglClass)> installPrototypes)120 void attachClass(
121     jsi::Runtime &runtime,
122     EXWebGLClass webglClass,
123     std::function<void(EXWebGLClass webglClass)> installPrototypes) {
124   jsi::PropNameID name = jsi::PropNameID::forUtf8(runtime, getConstructorName(webglClass));
125   installPrototypes(webglClass);
126 }
127 
128 // https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance#setting_teachers_prototype_and_constructor_reference
129 //
130 // Below implementation is equivalent of `class WebGLBuffer extends WebGLObject {}`
131 // where baseClass=global.WebGLObject and derivedProp="WebGLBuffer"
132 //
133 // WebGLBuffer.prototype = Object.create(WebGLObject.prototype);
134 // Object.defineProperty(WebGLBuffer.prototype, 'constructor', {
135 //   value: WebGLBuffer,
136 //   enumerable: false,
137 //   configurable: true,
138 //   writable: true });
jsClassExtend(jsi::Runtime & runtime,jsi::Object & baseClass,jsi::PropNameID derivedProp)139 void jsClassExtend(jsi::Runtime &runtime, jsi::Object &baseClass, jsi::PropNameID derivedProp) {
140   jsi::PropNameID prototype = jsi::PropNameID::forUtf8(runtime, "prototype");
141   jsi::Object objectClass = runtime.global().getPropertyAsObject(runtime, "Object");
142   jsi::Function createMethod = objectClass.getPropertyAsFunction(runtime, "create");
143   jsi::Function definePropertyMethod = objectClass.getPropertyAsFunction(runtime, "defineProperty");
144   jsi::Object derivedClass = runtime.global().getProperty(runtime, derivedProp).asObject(runtime);
145 
146   // WebGLBuffer.prototype = Object.create(WebGLObject.prototype);
147   derivedClass.setProperty(
148       runtime,
149       prototype,
150       createMethod.callWithThis(runtime, objectClass, {baseClass.getProperty(runtime, prototype)}));
151 
152   jsi::Object propertyOptions(runtime);
153   propertyOptions.setProperty(runtime, "value", derivedClass);
154   propertyOptions.setProperty(runtime, "enumerable", false);
155   propertyOptions.setProperty(runtime, "configurable", true);
156   propertyOptions.setProperty(runtime, "writable", true);
157 
158   // Object.defineProperty ...
159   definePropertyMethod.callWithThis(
160       runtime,
161       objectClass,
162       {
163           derivedClass.getProperty(runtime, prototype),
164           jsi::String::createFromUtf8(runtime, "constructor"),
165           std::move(propertyOptions),
166       });
167 }
168 
ensurePrototypes(jsi::Runtime & runtime)169 void ensurePrototypes(jsi::Runtime &runtime) {
170   if (runtime.global().hasProperty(runtime, "WebGLRenderingContext")) {
171     return;
172   }
173   runtime.global().setProperty(runtime, "__EXGLConstructorReady", true);
174 
175   auto evalBuffer = std::make_shared<jsi::StringBuffer>(evalStubConstructors);
176   runtime.evaluateJavaScript(evalBuffer, "expo-gl");
177 
178   auto inheritFromJsObject = [&runtime](EXWebGLClass classEnum) {
179     auto objectClass = runtime.global().getPropertyAsObject(runtime, "Object");
180     jsClassExtend(
181         runtime, objectClass, jsi::PropNameID::forUtf8(runtime, getConstructorName(classEnum)));
182   };
183 
184   // configure WebGLRenderingContext
185   {
186     inheritFromJsObject(EXWebGLClass::WebGLRenderingContext);
187     auto prototype =
188         runtime.global()
189             .getProperty(runtime, jsi::PropNameID::forUtf8(runtime, getConstructorName(EXWebGLClass::WebGLRenderingContext)))
190             .asObject(runtime)
191             .getPropertyAsObject(runtime, "prototype");
192     installConstants(runtime, prototype);
193     installWebGLMethods(runtime, prototype);
194   }
195 
196   // configure WebGL2RenderingContext
197   {
198     inheritFromJsObject(EXWebGLClass::WebGL2RenderingContext);
199     auto prototype =
200         runtime.global()
201             .getProperty(runtime, jsi::PropNameID::forUtf8(runtime, getConstructorName(EXWebGLClass::WebGL2RenderingContext)))
202             .asObject(runtime)
203             .getPropertyAsObject(runtime, "prototype");
204     installConstants(runtime, prototype);
205     installWebGL2Methods(runtime, prototype);
206   }
207 
208   // Configure rest of WebGL objects
209   inheritFromJsObject(EXWebGLClass::WebGLObject);
210 
211   jsi::Object webglObjectClass =
212       runtime.global()
213           .getProperty(
214               runtime,
215               jsi::PropNameID::forUtf8(runtime, getConstructorName(EXWebGLClass::WebGLObject)))
216           .asObject(runtime);
217   auto inheritFromWebGLObject = [&runtime, &webglObjectClass](EXWebGLClass classEnum) {
218     jsClassExtend(
219         runtime,
220         webglObjectClass,
221         jsi::PropNameID::forUtf8(runtime, getConstructorName(classEnum)));
222   };
223 
224   inheritFromWebGLObject(EXWebGLClass::WebGLBuffer);
225   inheritFromWebGLObject(EXWebGLClass::WebGLFramebuffer);
226   inheritFromWebGLObject(EXWebGLClass::WebGLProgram);
227   inheritFromWebGLObject(EXWebGLClass::WebGLRenderbuffer);
228   inheritFromWebGLObject(EXWebGLClass::WebGLShader);
229   inheritFromWebGLObject(EXWebGLClass::WebGLTexture);
230   inheritFromJsObject(EXWebGLClass::WebGLUniformLocation);
231   inheritFromJsObject(EXWebGLClass::WebGLActiveInfo);
232   inheritFromJsObject(EXWebGLClass::WebGLShaderPrecisionFormat);
233   inheritFromWebGLObject(EXWebGLClass::WebGLQuery);
234   inheritFromWebGLObject(EXWebGLClass::WebGLSampler);
235   inheritFromWebGLObject(EXWebGLClass::WebGLSync);
236   inheritFromWebGLObject(EXWebGLClass::WebGLTransformFeedback);
237   inheritFromWebGLObject(EXWebGLClass::WebGLVertexArrayObject);
238 }
239 
installConstants(jsi::Runtime & runtime,jsi::Object & gl)240 void installConstants(jsi::Runtime &runtime, jsi::Object &gl) {
241 #define GL_CONSTANT(name) gl.setProperty(runtime, #name, static_cast<double>(GL_##name));
242 #include "EXWebGLConstants.def"
243 #undef GL_CONSTANT
244 }
245 
installWebGLMethods(jsi::Runtime & runtime,jsi::Object & gl)246 void installWebGLMethods(jsi::Runtime &runtime, jsi::Object &gl) {
247 #define NATIVE_METHOD(name) setFunctionOnObject(runtime, gl, #name, method::glNativeMethod_##name);
248 
249 #define NATIVE_WEBGL2_METHOD(name) ;
250 #include "EXWebGLMethods.def"
251 #undef NATIVE_WEBGL2_METHOD
252 #undef NATIVE_METHOD
253 }
254 
installWebGL2Methods(jsi::Runtime & runtime,jsi::Object & gl)255 void installWebGL2Methods(jsi::Runtime &runtime, jsi::Object &gl) {
256 #define CREATE_METHOD(name) setFunctionOnObject(runtime, gl, #name, method::glNativeMethod_##name);
257 
258 #define NATIVE_METHOD(name) CREATE_METHOD(name)
259 #define NATIVE_WEBGL2_METHOD(name) CREATE_METHOD(name)
260 #include "EXWebGLMethods.def"
261 #undef NATIVE_WEBGL2_METHOD
262 #undef NATIVE_METHOD
263 #undef CREATE_METHOD
264 }
265 
266 } // namespace gl_cpp
267 } // namespace expo
268