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