diff --git a/src/dawn_node/interop/Core.cpp b/src/dawn_node/interop/Core.cpp new file mode 100644 index 0000000000..7c505ca065 --- /dev/null +++ b/src/dawn_node/interop/Core.cpp @@ -0,0 +1,154 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/dawn_node/interop/Core.h" + +namespace wgpu { namespace interop { + + bool Converter::FromJS(Napi::Env env, Napi::Value value, bool& out) { + if (value.IsBoolean()) { + out = value.ToBoolean(); + return true; + } + return false; + } + Napi::Value Converter::ToJS(Napi::Env env, bool value) { + return Napi::Value::From(env, value); + } + + bool Converter::FromJS(Napi::Env env, Napi::Value value, std::string& out) { + if (value.IsString()) { + out = value.ToString(); + return true; + } + return false; + } + Napi::Value Converter::ToJS(Napi::Env env, std::string value) { + return Napi::Value::From(env, value); + } + + bool Converter::FromJS(Napi::Env env, Napi::Value value, int8_t& out) { + if (value.IsNumber()) { + out = value.ToNumber().Int32Value(); + return true; + } + return false; + } + Napi::Value Converter::ToJS(Napi::Env env, int8_t value) { + return Napi::Value::From(env, value); + } + + bool Converter::FromJS(Napi::Env env, Napi::Value value, uint8_t& out) { + if (value.IsNumber()) { + out = value.ToNumber().Uint32Value(); + return true; + } + return false; + } + Napi::Value Converter::ToJS(Napi::Env env, uint8_t value) { + return Napi::Value::From(env, value); + } + + bool Converter::FromJS(Napi::Env env, Napi::Value value, int16_t& out) { + if (value.IsNumber()) { + out = value.ToNumber().Int32Value(); + return true; + } + return false; + } + Napi::Value Converter::ToJS(Napi::Env env, int16_t value) { + return Napi::Value::From(env, value); + } + + bool Converter::FromJS(Napi::Env env, Napi::Value value, uint16_t& out) { + if (value.IsNumber()) { + out = value.ToNumber().Uint32Value(); + return true; + } + return false; + } + Napi::Value Converter::ToJS(Napi::Env env, uint16_t value) { + return Napi::Value::From(env, value); + } + + bool Converter::FromJS(Napi::Env env, Napi::Value value, int32_t& out) { + if (value.IsNumber()) { + out = value.ToNumber().Int32Value(); + return true; + } + return false; + } + Napi::Value Converter::ToJS(Napi::Env env, int32_t value) { + return Napi::Value::From(env, value); + } + + bool Converter::FromJS(Napi::Env env, Napi::Value value, uint32_t& out) { + if (value.IsNumber()) { + out = value.ToNumber().Uint32Value(); + return true; + } + return false; + } + Napi::Value Converter::ToJS(Napi::Env env, uint32_t value) { + return Napi::Value::From(env, value); + } + + bool Converter::FromJS(Napi::Env env, Napi::Value value, int64_t& out) { + if (value.IsNumber()) { + out = value.ToNumber().Int64Value(); + return true; + } + return false; + } + Napi::Value Converter::ToJS(Napi::Env env, int64_t value) { + return Napi::Value::From(env, value); + } + + bool Converter::FromJS(Napi::Env env, Napi::Value value, uint64_t& out) { + if (value.IsNumber()) { + // Note that the JS Number type only stores doubles, so the max integer + // range of values without precision loss is -2^53 to 2^53 (52 bit mantissa + // with 1 implicit bit). This is why there's no UInt64Value() function. + out = static_cast(value.ToNumber().Int64Value()); + return true; + } + return false; + } + Napi::Value Converter::ToJS(Napi::Env env, uint64_t value) { + return Napi::Value::From(env, value); + } + + bool Converter::FromJS(Napi::Env env, Napi::Value value, float& out) { + if (value.IsNumber()) { + out = value.ToNumber().FloatValue(); + return true; + } + return false; + } + Napi::Value Converter::ToJS(Napi::Env env, float value) { + return Napi::Value::From(env, value); + } + + bool Converter::FromJS(Napi::Env env, Napi::Value value, double& out) { + if (value.IsNumber()) { + out = value.ToNumber().DoubleValue(); + return true; + } + return false; + } + Napi::Value Converter::ToJS(Napi::Env env, double value) { + return Napi::Value::From(env, value); + } + +}} // namespace wgpu::interop diff --git a/src/dawn_node/interop/Core.h b/src/dawn_node/interop/Core.h new file mode 100644 index 0000000000..1ad3bb9bee --- /dev/null +++ b/src/dawn_node/interop/Core.h @@ -0,0 +1,571 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file provides core interop helpers used by the code generated by the +// templates. + +#ifndef DAWN_NODE_INTEROP_CORE_WEBGPU_H_ +#define DAWN_NODE_INTEROP_CORE_WEBGPU_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "napi.h" + +#include "src/dawn_node/utils/Debug.h" + +#define ENABLE_INTEROP_LOGGING 0 // Enable for verbose interop logging + +#if ENABLE_INTEROP_LOGGING +# define INTEROP_LOG(...) LOG(__VA_ARGS__) +#else +# define INTEROP_LOG(...) +#endif + +namespace wgpu { namespace interop { + + //////////////////////////////////////////////////////////////////////////////// + // Primitive JavaScript types + //////////////////////////////////////////////////////////////////////////////// + using Object = Napi::Object; + using ArrayBuffer = Napi::ArrayBuffer; + using Int8Array = Napi::TypedArrayOf; + using Int16Array = Napi::TypedArrayOf; + using Int32Array = Napi::TypedArrayOf; + using Uint8Array = Napi::TypedArrayOf; + using Uint16Array = Napi::TypedArrayOf; + using Uint32Array = Napi::TypedArrayOf; + using Float32Array = Napi::TypedArrayOf; + using Float64Array = Napi::TypedArrayOf; + using DataView = Napi::TypedArray; + + template + using FrozenArray = std::vector; + + //////////////////////////////////////////////////////////////////////////////// + // Interface + //////////////////////////////////////////////////////////////////////////////// + + // Interface is a templated wrapper around a JavaScript object, which + // implements the template-generated interface type T. Interfaces are returned + // by either calling T::Bind() or T::Create(). + template + class Interface { + public: + // Constructs an Interface with no JS object. + inline Interface() { + } + + // Constructs an Interface wrapping the given JS object. + // The JS object must have been created with a call to T::Bind(). + explicit inline Interface(Napi::Object o) : object(o) { + } + + // Implicit conversion operators to Napi objects. + inline operator napi_value() const { + return object; + } + inline operator const Napi::Value &() const { + return object; + } + inline operator const Napi::Object &() const { + return object; + } + + // Member and dereference operators + inline T* operator->() const { + return T::Unwrap(object); + } + inline T* operator*() const { + return T::Unwrap(object); + } + + // As() returns the unwrapped object cast to the implementation type. + // The interface implementation *must* be of the template type IMPL. + template + inline IMPL* As() const { + return static_cast(T::Unwrap(object)); + } + + private: + Napi::Object object; + }; + + //////////////////////////////////////////////////////////////////////////////// + // Promise + //////////////////////////////////////////////////////////////////////////////// + + // Promise is a templated wrapper around a JavaScript promise, which can + // resolve to the template type T. + template + class Promise { + public: + // Constructor + Promise(Napi::Env env) : deferred(Napi::Promise::Deferred::New(env)) { + } + + // Implicit conversion operators to Napi promises. + inline operator napi_value() const { + return deferred.Promise(); + } + inline operator Napi::Value() const { + return deferred.Promise(); + } + inline operator Napi::Promise() const { + return deferred.Promise(); + } + + // Resolve() fulfills the promise with the given value. + void Resolve(T&& value) const { + deferred.Resolve(ToJS(deferred.Env(), std::forward(value))); + } + + // Reject() rejects the promise with the given failure value. + void Reject(Napi::Object obj) const { + deferred.Reject(obj); + } + void Reject(Napi::Error err) const { + deferred.Reject(err.Value()); + } + void Reject(std::string err) const { + Reject(Napi::Error::New(deferred.Env(), err)); + } + + private: + Napi::Promise::Deferred deferred; + }; + + // Specialization for Promises that resolve with no value + template <> + class Promise { + public: + // Constructor + Promise(Napi::Env env) : deferred(Napi::Promise::Deferred::New(env)) { + } + + // Implicit conversion operators to Napi promises. + inline operator napi_value() const { + return deferred.Promise(); + } + inline operator Napi::Value() const { + return deferred.Promise(); + } + inline operator Napi::Promise() const { + return deferred.Promise(); + } + + // Resolve() fulfills the promise. + void Resolve() const { + deferred.Resolve(deferred.Env().Undefined()); + } + + // Reject() rejects the promise with the given failure value. + void Reject(Napi::Object obj) const { + deferred.Reject(obj); + } + void Reject(Napi::Error err) const { + deferred.Reject(err.Value()); + } + void Reject(std::string err) const { + Reject(Napi::Error::New(deferred.Env(), err)); + } + + private: + Napi::Promise::Deferred deferred; + }; + + //////////////////////////////////////////////////////////////////////////////// + // Converter + //////////////////////////////////////////////////////////////////////////////// + + // Converter is specialized for each type T which can be converted from C++ + // to JavaScript, or JavaScript to C++. + // Each specialization of Converter is expected to have two static methods + // with the signatures: + // + // // FromJS() converts the JavaScript value 'in' to the C++ value 'out'. + // // Returns true on success, false on failure. + // static bool FromJS(Napi::Env, Napi::Value in, T& out); + // + // // ToJS() converts the C++ value 'in' to a JavaScript value, and returns + // // this value. + // static Napi::Value ToJS(Napi::Env, T in); + template + class Converter {}; + + template <> + class Converter { + public: + static inline bool FromJS(Napi::Env, Napi::Value value, Napi::Object& out) { + if (value.IsObject()) { + out = value.ToObject(); + return true; + } + return false; + } + static inline Napi::Value ToJS(Napi::Env, Napi::Object value) { + return value; + } + }; + + template <> + class Converter { + public: + static inline bool FromJS(Napi::Env, Napi::Value value, ArrayBuffer& out) { + if (value.IsArrayBuffer()) { + out = value.As(); + return true; + } + return false; + }; + static inline Napi::Value ToJS(Napi::Env, ArrayBuffer value) { + return value; + } + }; + + template <> + class Converter { + public: + static inline bool FromJS(Napi::Env, Napi::Value value, Napi::TypedArray& out) { + if (value.IsTypedArray()) { + out = value.As(); + return true; + } + return false; + }; + static inline Napi::Value ToJS(Napi::Env, ArrayBuffer value) { + return value; + } + }; + + template + class Converter> { + public: + // clang-format off + // The Napi element type of T + static constexpr napi_typedarray_type element_type = + std::is_same::value ? napi_int8_array + : std::is_same::value ? napi_uint8_array + : std::is_same::value ? napi_int16_array + : std::is_same::value ? napi_uint16_array + : std::is_same::value ? napi_int32_array + : std::is_same::value ? napi_uint32_array + : std::is_same::value ? napi_float32_array + : std::is_same::value ? napi_float64_array + : std::is_same::value ? napi_bigint64_array + : std::is_same::value ? napi_biguint64_array + : static_cast(-1); + // clang-format on + static_assert(static_cast(element_type) >= 0, + "unsupported T type for Napi::TypedArrayOf"); + static inline bool FromJS(Napi::Env, Napi::Value value, Napi::TypedArrayOf& out) { + if (value.IsTypedArray()) { + auto arr = value.As>(); + if (arr.TypedArrayType() == element_type) { + out = arr; + } + return true; + } + return false; + }; + static inline Napi::Value ToJS(Napi::Env, ArrayBuffer value) { + return value; + } + }; + + template <> + class Converter { + public: + static bool FromJS(Napi::Env, Napi::Value, std::string&); + static Napi::Value ToJS(Napi::Env, std::string); + }; + + template <> + class Converter { + public: + static bool FromJS(Napi::Env, Napi::Value, bool&); + static Napi::Value ToJS(Napi::Env, bool); + }; + + template <> + class Converter { + public: + static bool FromJS(Napi::Env, Napi::Value, int8_t&); + static Napi::Value ToJS(Napi::Env, int8_t); + }; + + template <> + class Converter { + public: + static bool FromJS(Napi::Env, Napi::Value, uint8_t&); + static Napi::Value ToJS(Napi::Env, uint8_t); + }; + + template <> + class Converter { + public: + static bool FromJS(Napi::Env, Napi::Value, int16_t&); + static Napi::Value ToJS(Napi::Env, int16_t); + }; + + template <> + class Converter { + public: + static bool FromJS(Napi::Env, Napi::Value, uint16_t&); + static Napi::Value ToJS(Napi::Env, uint16_t); + }; + + template <> + class Converter { + public: + static bool FromJS(Napi::Env, Napi::Value, int32_t&); + static Napi::Value ToJS(Napi::Env, int32_t); + }; + + template <> + class Converter { + public: + static bool FromJS(Napi::Env, Napi::Value, uint32_t&); + static Napi::Value ToJS(Napi::Env, uint32_t); + }; + + template <> + class Converter { + public: + static bool FromJS(Napi::Env, Napi::Value, int64_t&); + static Napi::Value ToJS(Napi::Env, int64_t); + }; + + template <> + class Converter { + public: + static bool FromJS(Napi::Env, Napi::Value, uint64_t&); + static Napi::Value ToJS(Napi::Env, uint64_t); + }; + + template <> + class Converter { + public: + static bool FromJS(Napi::Env, Napi::Value, float&); + static Napi::Value ToJS(Napi::Env, float); + }; + + template <> + class Converter { + public: + static bool FromJS(Napi::Env, Napi::Value, double&); + static Napi::Value ToJS(Napi::Env, double); + }; + + template + class Converter> { + public: + static bool FromJS(Napi::Env env, Napi::Value value, Interface& out) { + if (value.IsObject()) { + auto obj = value.As(); + if (T::Unwrap(obj)) { + out = Interface(obj); + return true; + } + } + return false; + } + static Napi::Value ToJS(Napi::Env env, const Interface& value) { + return {env, value}; + } + }; + + template + class Converter> { + public: + static bool FromJS(Napi::Env env, Napi::Value value, std::optional& out) { + if (value.IsNull() || value.IsUndefined()) { + out.reset(); + return true; + } + T v{}; + if (!Converter::FromJS(env, value, v)) { + return false; + } + out = std::move(v); + return true; + } + static Napi::Value ToJS(Napi::Env env, std::optional value) { + if (value.has_value()) { + return Converter::ToJS(env, value.value()); + } + return env.Null(); + } + }; + + template + class Converter> { + public: + static inline bool FromJS(Napi::Env env, Napi::Value value, std::vector& out) { + if (!value.IsArray()) { + return false; + } + auto arr = value.As(); + std::vector vec(arr.Length()); + for (size_t i = 0; i < vec.size(); i++) { + if (!Converter::FromJS(env, arr[static_cast(i)], vec[i])) { + return false; + } + } + out = std::move(vec); + return true; + } + static inline Napi::Value ToJS(Napi::Env env, const std::vector& vec) { + auto arr = Napi::Array::New(env, vec.size()); + for (size_t i = 0; i < vec.size(); i++) { + arr.Set(static_cast(i), Converter::ToJS(env, vec[i])); + } + return arr; + } + }; + + template + class Converter> { + public: + static inline bool FromJS(Napi::Env env, Napi::Value value, std::unordered_map& out) { + if (!value.IsObject()) { + return false; + } + auto obj = value.ToObject(); + auto keys = obj.GetPropertyNames(); + std::unordered_map map(keys.Length()); + for (uint32_t i = 0; i < static_cast(map.size()); i++) { + K key{}; + V value{}; + if (!Converter::FromJS(env, keys[i], key) || + !Converter::FromJS(env, obj.Get(keys[i]), value)) { + return false; + } + map[key] = value; + } + out = std::move(map); + return true; + } + static inline Napi::Value ToJS(Napi::Env env, std::unordered_map value) { + auto obj = Napi::Object::New(env); + for (auto it : value) { + obj.Set(Converter::ToJS(env, it.first), Converter::ToJS(env, it.second)); + } + return obj; + } + }; + + template + class Converter> { + template + static inline bool TryFromJS(Napi::Env env, + Napi::Value value, + std::variant& out) { + TY v{}; + if (Converter::FromJS(env, value, v)) { + out = std::move(v); + return true; + } + return false; + } + + template + static inline bool TryFromJS(Napi::Env env, + Napi::Value value, + std::variant& out) { + if (TryFromJS(env, value, out)) { + return true; + } + return TryFromJS(env, value, out); + } + + public: + static inline bool FromJS(Napi::Env env, Napi::Value value, std::variant& out) { + return TryFromJS(env, value, out); + } + static inline Napi::Value ToJS(Napi::Env env, std::variant value) { + return std::visit( + [&](auto&& v) { + using T = std::remove_cv_t>; + return Converter::ToJS(env, v); + }, + value); + } + }; + + template + class Converter> { + public: + static inline bool FromJS(Napi::Env, Napi::Value, Promise&) { + UNIMPLEMENTED(); + } + static inline Napi::Value ToJS(Napi::Env, Promise promise) { + return promise; + } + }; + + //////////////////////////////////////////////////////////////////////////////// + // Helpers + //////////////////////////////////////////////////////////////////////////////// + + // FromJS() is a helper function which delegates to + // Converter::FromJS() + template + inline bool FromJS(Napi::Env env, Napi::Value value, T& out) { + return Converter::FromJS(env, value, out); + } + + // FromJSOptional() is similar to FromJS(), but if 'value' is either null + // or undefined then FromJSOptional() returns true and 'out' is left + // unassigned. + // Returns true on success, false on failure. + template + inline bool FromJSOptional(Napi::Env env, Napi::Value value, T& out) { + if (value.IsNull() || value.IsUndefined()) { + return true; + } + return Converter::FromJS(env, value, out); + } + + // ToJS() is a helper function which delegates to Converter::ToJS() + template + inline Napi::Value ToJS(Napi::Env env, T&& value) { + return Converter>>::ToJS( + env, std::forward(value)); + } + + // FromJS() is a helper function for bulk converting the arguments of 'info'. + // PARAM_TYPES is a std::tuple<> describing the C++ function parameter types. + // Returns true on success, false on failure. + template + inline bool FromJS(const Napi::CallbackInfo& info, PARAM_TYPES& args) { + if constexpr (BASE_INDEX < std::tuple_size_v) { + using T = std::tuple_element_t; + if (!FromJS(info.Env(), info[BASE_INDEX], std::get(args))) { + return false; + } + return FromJS(info, args); + } else { + return true; + } + } + +}} // namespace wgpu::interop + +#endif // DAWN_NODE_INTEROP_CORE_WEBGPU_H_