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