1 #pragma once
2 
3 #ifdef __ANDROID__
4 #include <GLES3/gl3.h>
5 #include <GLES3/gl3ext.h>
6 #endif
7 #ifdef __APPLE__
8 #include <OpenGLES/EAGL.h>
9 #include <OpenGLES/ES3/gl.h>
10 #include <OpenGLES/ES3/glext.h>
11 #endif
12 
13 #include <jsi/jsi.h>
14 #include <type_traits>
15 
16 #include "EXJsiUtils.h"
17 #include "EXWebGLRenderer.h"
18 #include "EXTypedArrayApi.h"
19 
20 namespace expo {
21 namespace gl_cpp {
22 
23 //
24 // unpackArg function is set of function overload and explicit specialization used to convert
25 // raw jsi::Value into specified (at compile time) type.
26 //
27 // Why we need to mix explicit specializations and function overloads?
28 // On the one hand we need to provide implementations for a range of types (e.g. all integers,
29 // all floats) so we can't do this with explicit specializations only, on the other hand we can't
30 // use only function overloads because only difference in signature is caused by return type which
31 // does not affect overloading.
32 //
33 // To prevent ambiguity all specializations should be directly under first unimplemented declaration
34 // of this function, and all new function overloads should be implemented under specializations
35 //
36 
37 template <typename T>
38 inline constexpr bool is_integral_v =
39     std::is_integral_v<T> && !std::is_same_v<bool, T> && !std::is_same_v<GLboolean, T>;
40 
41 template <typename T>
42 inline constexpr bool is_supported_vector = std::is_same_v<std::vector<uint32_t>, T> ||
43     std::is_same_v<std::vector<int32_t>, T> || std::is_same_v<std::vector<float>, T>;
44 
45 // if T = EXWebGLClass then return_type = EXGLObjectId else return_type = T
46 template <typename T>
47 using type_map = typename std::conditional<std::is_same_v<EXWebGLClass, T>, EXGLObjectId, T>::type;
48 
49 template <typename T>
50 inline std::enable_if_t<
51     !(is_integral_v<T> || std::is_floating_point_v<T> || is_supported_vector<T>),
52     type_map<T>>
53 unpackArg(jsi::Runtime &runtime, const jsi::Value *jsArgv);
54 
55 //
56 // unpackArgs explicit specializations
57 //
58 
59 template <>
60 inline bool unpackArg<bool>(jsi::Runtime &runtime, const jsi::Value *jsArgv) {
61   if (jsArgv->isBool()) {
62     return jsArgv->getBool();
63   } else if (jsArgv->isNull() || jsArgv->isUndefined()) {
64     return false;
65   } else if (jsArgv->isNumber()) {
66     return jsArgv->getNumber() != 0;
67   }
68   throw std::runtime_error("value is not a boolean");
69 }
70 
71 template <>
72 inline const void *unpackArg<const void *>(jsi::Runtime &runtime, const jsi::Value *jsArgv) {
73   if (jsArgv->isNumber()) {
74     return reinterpret_cast<const void *>(static_cast<uint64_t>(jsArgv->getNumber()));
75   } else if (jsArgv->isNull() || jsArgv->isUndefined()) {
76     return nullptr;
77   }
78   throw std::runtime_error("value is not a correct offset");
79 }
80 
81 template <>
82 inline GLboolean unpackArg<GLboolean>(jsi::Runtime &runtime, const jsi::Value *jsArgv) {
83   return unpackArg<bool>(runtime, jsArgv) ? GL_TRUE : GL_FALSE;
84 }
85 
86 template <>
87 inline const jsi::Value &unpackArg<const jsi::Value &>(
88     jsi::Runtime &runtime,
89     const jsi::Value *jsArgv) {
90   return *jsArgv;
91 }
92 
93 template <>
94 inline std::string unpackArg<std::string>(jsi::Runtime &runtime, const jsi::Value *jsArgv) {
95   return jsArgv->asString(runtime).utf8(runtime);
96 }
97 
98 template <>
99 inline jsi::Object unpackArg<jsi::Object>(jsi::Runtime &runtime, const jsi::Value *jsArgv) {
100   return jsArgv->asObject(runtime);
101 }
102 
103 template <>
104 inline jsi::Array unpackArg<jsi::Array>(jsi::Runtime &runtime, const jsi::Value *jsArgv) {
105   return jsArgv->asObject(runtime).asArray(runtime);
106 }
107 
108 template <>
109 inline TypedArrayBase unpackArg<TypedArrayBase>(jsi::Runtime &runtime, const jsi::Value *jsArgv) {
110   return getTypedArray(runtime, jsArgv->asObject(runtime));
111 }
112 
113 template <>
114 inline jsi::ArrayBuffer unpackArg<jsi::ArrayBuffer>(
115     jsi::Runtime &runtime,
116     const jsi::Value *jsArgv) {
117   if (!jsArgv->isObject() || !jsArgv->asObject(runtime).isArrayBuffer(runtime)) {
118     throw std::runtime_error("value is not an ArrayBuffer");
119   }
120   return jsArgv->asObject(runtime).getArrayBuffer(runtime);
121 }
122 
123 template <>
124 inline EXGLObjectId unpackArg<EXWebGLClass>(jsi::Runtime &runtime, const jsi::Value *jsArgv) {
125   if (!jsArgv->isObject() || !jsArgv->asObject(runtime).hasProperty(runtime, "id")) {
126     return 0;
127   }
128   return static_cast<EXGLObjectId>(
129       jsArgv->asObject(runtime).getProperty(runtime, "id").asNumber());
130 }
131 
132 //
133 // unpackArgs function overloads
134 //
135 
136 template <typename T>
unpackArg(jsi::Runtime & runtime,const jsi::Value * jsArgv)137 inline std::enable_if_t<is_integral_v<T>, T> unpackArg(
138     jsi::Runtime &runtime,
139     const jsi::Value *jsArgv) {
140   if (jsArgv->isNumber()) {
141     return jsArgv->getNumber(); // TODO: add api to jsi to handle integers more efficiently
142   } else if (jsArgv->isNull() || jsArgv->isUndefined()) {
143     return 0;
144   } else if (jsArgv->isBool()) {
145     // this case should not be necessary but one of the ncl threejs examples relies on this
146     // behaviour
147     return jsArgv->getBool() ? GL_TRUE : GL_FALSE;
148   }
149   return jsArgv->asNumber();
150 }
151 
152 template <typename T>
unpackArg(jsi::Runtime & runtime,const jsi::Value * jsArgv)153 inline std::enable_if_t<std::is_floating_point_v<T>, T> unpackArg(
154     jsi::Runtime &runtime,
155     const jsi::Value *jsArgv) {
156   if (jsArgv->isNumber()) {
157     return jsArgv->getNumber();
158   } else if (jsArgv->isNull() || jsArgv->isUndefined()) {
159     return 0;
160   }
161   return jsArgv->asNumber();
162 }
163 
164 template <typename T>
unpackArg(jsi::Runtime & runtime,const jsi::Value * jsArgv)165 inline std::enable_if_t<is_supported_vector<T>, T> unpackArg(
166     jsi::Runtime &runtime,
167     const jsi::Value *jsArgv) {
168   auto jsObj = jsArgv->asObject(runtime);
169   if (jsObj.isArray(runtime)) {
170     return jsArrayToVector<typename T::value_type>(runtime, jsObj.asArray(runtime));
171   } else if (isTypedArray(runtime, jsObj)) {
172     if constexpr (std::is_same_v<typename T::value_type, uint32_t>) {
173       return getTypedArray(runtime, std::move(jsObj))
174           .as<TypedArrayKind::Uint32Array>(runtime)
175           .toVector(runtime);
176     } else if constexpr (std::is_same_v<typename T::value_type, int32_t>) {
177       return getTypedArray(runtime, std::move(jsObj))
178           .as<TypedArrayKind::Int32Array>(runtime)
179           .toVector(runtime);
180     } else if constexpr (std::is_same_v<typename T::value_type, float>) {
181       return getTypedArray(runtime, std::move(jsObj))
182           .as<TypedArrayKind::Float32Array>(runtime)
183           .toVector(runtime);
184     }
185   }
186   throw std::runtime_error("unsupported type");
187 }
188 
189 template <TypedArrayKind T>
unpackArg(jsi::Runtime & runtime,const jsi::Value * jsArgv)190 inline TypedArray<T> unpackArg(jsi::Runtime &runtime, const jsi::Value *jsArgv) {
191   return getTypedArray(runtime, jsArgv->asObject(runtime)).as<T>(runtime);
192 }
193 
194 // set of private helpers, do not use directly
195 namespace methodHelper {
196 template <typename T>
197 struct Arg {
198   const jsi::Value *ptr;
unpackArg199   T unpack(jsi::Runtime &runtime) {
200     return unpackArg<T>(runtime, ptr);
201   }
202 };
203 
204 // Create tuple of arguments packed in helper class
205 // Wrapping is added to preserve mapping between type and pointer to jsi::Value
206 template <typename First, typename... T>
toArgTuple(const jsi::Value * jsArgv)207 constexpr std::tuple<Arg<First>, Arg<T>...> toArgTuple(const jsi::Value *jsArgv) {
208   if constexpr (sizeof...(T) >= 1) {
209     return std::tuple_cat(std::tuple(Arg<First>{jsArgv}), toArgTuple<T...>(jsArgv + 1));
210   } else {
211     return std::tuple(Arg<First>{jsArgv});
212   }
213 }
214 
215 // We need to unpack this in separate step because unpackArg
216 // used in Arg class is not an constexpr.
217 template <typename Tuple, size_t... I>
unpackArgsTuple(jsi::Runtime & runtime,Tuple && tuple,std::index_sequence<I...>)218 auto unpackArgsTuple(jsi::Runtime &runtime, Tuple &&tuple, std::index_sequence<I...>) {
219   return std::make_tuple(std::get<I>(tuple).unpack(runtime)...);
220 }
221 
222 template <typename Tuple, typename F, size_t... I>
generateNativeMethodBind(F fn,Tuple && tuple,std::index_sequence<I...>)223 auto generateNativeMethodBind(F fn, Tuple &&tuple, std::index_sequence<I...>) {
224   return std::bind(fn, std::get<I>(tuple)...);
225 }
226 
227 } // namespace methodHelper
228 
229 //
230 // unpackArgs is parsing arguments passed to function from JS
231 // conversion from *jsi::Value to declared type is done by specici specialization or overloads
232 // of unpackArg method defined above
233 //
234 // e.g. usage
235 // auto [ arg1, arg2, arg3 ] = unpackArgs<int, string, js::Object>(runtime, jsArgv, argc)
236 // used in EXGLNativeMethods wrapped in ARGS macro
237 //
238 template <typename... T>
unpackArgs(jsi::Runtime & runtime,const jsi::Value * jsArgv,size_t argc)239 inline std::tuple<T...> unpackArgs(jsi::Runtime &runtime, const jsi::Value *jsArgv, size_t argc) {
240   if (argc < sizeof...(T)) {
241     throw std::runtime_error("EXGL: Too few arguments");
242   }
243   // create tuple of Arg<T> structs containg pointer to unprocessed arguments
244   auto argTuple = methodHelper::toArgTuple<T...>(jsArgv);
245 
246   // transform tuple by running unpackArg<T>() on every element
247   return methodHelper::unpackArgsTuple(
248       runtime, std::move(argTuple), std::make_index_sequence<sizeof...(T)>());
249 }
250 
251 template <>
unpackArgs(jsi::Runtime &,const jsi::Value *,size_t)252 inline std::tuple<> unpackArgs(jsi::Runtime &, const jsi::Value *, size_t) {
253   return std::tuple<>();
254 }
255 
256 //
257 // converts jsi::Value's passed to js method into c++ values based on type of declaration
258 // of OpenGl function
259 //
260 // e.g. usage
261 // NATIVE_METHOD(scissor) {
262 //   addToNextBatch(generateNativeMethod(runtime, glScissor, jsArgv, argc));
263 //   return nullptr;
264 // }
265 // used in EXGLNativeMethods wrapped in SIMPLE_NATIVE_METHOD macro
266 //
267 template <typename... T>
generateNativeMethod(jsi::Runtime & runtime,void fn (T...),const jsi::Value * jsArgv,size_t argc)268 auto generateNativeMethod(
269     jsi::Runtime &runtime,
270     void fn(T...),
271     const jsi::Value *jsArgv,
272     size_t argc) {
273   // generate tuple of arguments of correct type
274   auto argTuple = unpackArgs<T...>(runtime, jsArgv, argc);
275 
276   // bind tuple values as consecutive function arguments
277   return methodHelper::generateNativeMethodBind(
278       fn, std::move(argTuple), std::make_index_sequence<sizeof...(T)>());
279 }
280 } // namespace gl_cpp
281 } // namespace expo
282