dawn_node: Begin implementing GPUSupportedFeatures

Requires setlike interface interop.

Bug: dawn:1123
Bug: dawn:1143
Change-Id: I1451f72b32b99858be871db99888f86872b53fd0
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/65245
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_node/binding/GPUAdapter.cpp b/src/dawn_node/binding/GPUAdapter.cpp
index 0d47304..b86d747 100644
--- a/src/dawn_node/binding/GPUAdapter.cpp
+++ b/src/dawn_node/binding/GPUAdapter.cpp
@@ -19,6 +19,51 @@
 
 namespace wgpu { namespace binding {
 
+    namespace {
+
+        ////////////////////////////////////////////////////////////////////////////////
+        // wgpu::binding::<anon>::Features
+        // Implements interop::GPUSupportedFeatures
+        ////////////////////////////////////////////////////////////////////////////////
+        class Features : public interop::GPUSupportedFeatures {
+          public:
+            Features(WGPUDeviceProperties properties) : properties_(std::move(properties)) {
+            }
+
+            bool has(interop::GPUFeatureName feature) {
+                switch (feature) {
+                    case interop::GPUFeatureName::kDepthClamping:
+                        return properties_.depthClamping;
+                    case interop::GPUFeatureName::kDepth24UnormStencil8:
+                        return false;  // TODO(crbug.com/dawn/1130)
+                    case interop::GPUFeatureName::kDepth32FloatStencil8:
+                        return false;  // TODO(crbug.com/dawn/1130)
+                    case interop::GPUFeatureName::kPipelineStatisticsQuery:
+                        return properties_.pipelineStatisticsQuery;
+                    case interop::GPUFeatureName::kTextureCompressionBc:
+                        return properties_.textureCompressionBC;
+                    case interop::GPUFeatureName::kTimestampQuery:
+                        return properties_.timestampQuery;
+                }
+                UNIMPLEMENTED("feature: ", feature);
+                return false;
+            }
+
+            // interop::GPUSupportedFeatures compliance
+            bool has(Napi::Env, std::string name) override {
+                interop::GPUFeatureName feature;
+                if (interop::Converter<interop::GPUFeatureName>::FromString(name, feature)) {
+                    return has(feature);
+                }
+                return false;
+            }
+
+          private:
+            WGPUDeviceProperties properties_;
+        };
+
+    }  // namespace
+
     ////////////////////////////////////////////////////////////////////////////////
     // wgpu::bindings::GPUAdapter
     // TODO(crbug.com/dawn/1133): This is a stub implementation. Properly implement.
@@ -31,8 +76,8 @@
     }
 
     interop::Interface<interop::GPUSupportedFeatures> GPUAdapter::getFeatures(Napi::Env env) {
-        class Features : public interop::GPUSupportedFeatures {};
-        return interop::GPUSupportedFeatures::Create<Features>(env);
+        return interop::GPUSupportedFeatures::Create<Features>(env,
+                                                               adapter_.GetAdapterProperties());
     }
 
     interop::Interface<interop::GPUSupportedLimits> GPUAdapter::getLimits(Napi::Env env) {
@@ -49,6 +94,30 @@
         dawn_native::DeviceDescriptor desc{};  // TODO(crbug.com/dawn/1133): Fill in.
         interop::Promise<interop::Interface<interop::GPUDevice>> promise(env);
 
+        if (descriptor.has_value()) {
+            // See src/dawn_native/Extensions.cpp for feature <-> extension mappings.
+            for (auto required : descriptor->requiredFeatures) {
+                switch (required) {
+                    case interop::GPUFeatureName::kDepthClamping:
+                        desc.requiredExtensions.emplace_back("depth_clamping");
+                        continue;
+                    case interop::GPUFeatureName::kPipelineStatisticsQuery:
+                        desc.requiredExtensions.emplace_back("pipeline_statistics_query");
+                        continue;
+                    case interop::GPUFeatureName::kTextureCompressionBc:
+                        desc.requiredExtensions.emplace_back("texture_compression_bc");
+                        continue;
+                    case interop::GPUFeatureName::kTimestampQuery:
+                        desc.requiredExtensions.emplace_back("timestamp_query");
+                        continue;
+                    case interop::GPUFeatureName::kDepth24UnormStencil8:
+                    case interop::GPUFeatureName::kDepth32FloatStencil8:
+                        continue;  // TODO(crbug.com/dawn/1130)
+                }
+                UNIMPLEMENTED("required: ", required);
+            }
+        }
+
         auto wgpu_device = adapter_.CreateDevice(&desc);
         if (wgpu_device) {
             promise.Resolve(interop::GPUDevice::Create<GPUDevice>(env, env, wgpu_device));
@@ -57,5 +126,4 @@
         }
         return promise;
     }
-
 }}  // namespace wgpu::binding
diff --git a/src/dawn_node/binding/GPUDevice.cpp b/src/dawn_node/binding/GPUDevice.cpp
index d87da51..5d40b26 100644
--- a/src/dawn_node/binding/GPUDevice.cpp
+++ b/src/dawn_node/binding/GPUDevice.cpp
@@ -109,7 +109,12 @@
     }
 
     interop::Interface<interop::GPUSupportedFeatures> GPUDevice::getFeatures(Napi::Env env) {
-        class Features : public interop::GPUSupportedFeatures {};
+        class Features : public interop::GPUSupportedFeatures {
+          public:
+            bool has(Napi::Env, std::string feature) override {
+                UNIMPLEMENTED();
+            }
+        };
         return interop::GPUSupportedFeatures::Create<Features>(env);
     }
 
diff --git a/src/dawn_node/interop/WebGPU.cpp.tmpl b/src/dawn_node/interop/WebGPU.cpp.tmpl
index d52a991..388bb96 100644
--- a/src/dawn_node/interop/WebGPU.cpp.tmpl
+++ b/src/dawn_node/interop/WebGPU.cpp.tmpl
@@ -141,6 +141,9 @@
 {{-  end}}
     static Napi::Function Class(Napi::Env env) {
       return DefineClass(env, "{{$.Name}}", {
+{{   if $s := SetlikeOf $}}
+        InstanceMethod("has", &W{{$.Name}}::has),
+{{-  end}}
 {{-  range $m := MethodsOf $}}
         InstanceMethod("{{$m.Name}}", &W{{$.Name}}::{{$m.Name}}),
 {{-  end}}
@@ -156,6 +159,17 @@
     }
 
     W{{$.Name}}(const Napi::CallbackInfo& info) : ObjectWrap(info) {}
+
+{{   if $s := SetlikeOf $}}
+    Napi::Value has(const Napi::CallbackInfo& info) {
+      std::tuple<{{template "Type" $s.Elem}}> args;
+      if (FromJS(info, args)) {
+          return ToJS(info.Env(), impl->has(info.Env(), std::get<0>(args)));
+      }
+      Napi::Error::New(info.Env(), "invalid arguments to has()").ThrowAsJavaScriptException();
+      return {};
+    }
+{{-  end}}
 {{-  range $m := MethodsOf $}}
     Napi::Value {{$m.Name}}(const Napi::CallbackInfo& info) {
 {{-    range $overload_idx, $o := $m.Overloads}}
@@ -307,8 +321,7 @@
 --------------------------------------------------------------------------------
 */ -}}
 {{- define "Enum"}}
-bool Converter<{{$.Name}}>::FromJS(Napi::Env env, Napi::Value value, {{$.Name}}& out) {
-  std::string str = value.ToString();
+bool Converter<{{$.Name}}>::FromString(std::string str, {{$.Name}}& out) {
 {{-  range $e := $.Values}}
   if (str == {{$e.Value}}) {
     out = {{$.Name}}::{{EnumEntryName $e.Value}};
@@ -317,6 +330,9 @@
 {{-  end}}
   return false;
 }
+bool Converter<{{$.Name}}>::FromJS(Napi::Env env, Napi::Value value, {{$.Name}}& out) {
+  return FromString(value.ToString(), out);
+}
 Napi::Value Converter<{{$.Name}}>::ToJS(Napi::Env env, {{$.Name}} value) {
   switch (value) {
 {{-  range $e := $.Values}}
diff --git a/src/dawn_node/interop/WebGPU.h.tmpl b/src/dawn_node/interop/WebGPU.h.tmpl
index 7c791c6..62a222c 100644
--- a/src/dawn_node/interop/WebGPU.h.tmpl
+++ b/src/dawn_node/interop/WebGPU.h.tmpl
@@ -123,6 +123,9 @@
 
   virtual ~{{$.Name}}();
   {{$.Name}}();
+{{-  if $s := SetlikeOf $}}
+{{-    template "InterfaceSetlike" $s}}
+{{-  end}}
 {{-  range $m := MethodsOf $}}
 {{-    template "InterfaceMethod" $m}}
 {{-  end}}
@@ -165,6 +168,7 @@
 public:
   static bool FromJS(Napi::Env, Napi::Value, {{$.Name}}&);
   static Napi::Value ToJS(Napi::Env, {{$.Name}});
+  static bool FromString(std::string, {{$.Name}}&);
 };
 
 std::ostream& operator<<(std::ostream& o, {{$.Name}});
@@ -186,6 +190,20 @@
 
 {{- /*
 --------------------------------------------------------------------------------
+-- InterfaceSetlike emits the C++ methods for a setlike interface
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "InterfaceSetlike"}}
+  virtual bool has(Napi::Env, {{template "Type" $.Elem}}) = 0;
+{{- /* TODO(crbug.com/dawn/1143):
+       entries, forEach, keys, size, values
+       read-write: add, clear, or delete
+*/}}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
 -- InterfaceMethod emits the C++ declaration for a single interface ast.Member
 -- method
 --------------------------------------------------------------------------------
diff --git a/src/dawn_node/tools/cmd/idlgen/main.go b/src/dawn_node/tools/cmd/idlgen/main.go
index e8e4104..5ea5499 100644
--- a/src/dawn_node/tools/cmd/idlgen/main.go
+++ b/src/dawn_node/tools/cmd/idlgen/main.go
@@ -128,6 +128,7 @@
 		"IsUnionType":                is(ast.UnionType{}),
 		"Lookup":                     g.lookup,
 		"MethodsOf":                  methodsOf,
+		"SetlikeOf":                  setlikeOf,
 		"Title":                      strings.Title,
 	}
 	t, err := g.t.
@@ -569,6 +570,20 @@
 	return out
 }
 
+// setlikeOf returns the setlike ast.Pattern, if obj is a setlike interface.
+func setlikeOf(obj interface{}) *ast.Pattern {
+	iface, ok := obj.(*ast.Interface)
+	if !ok {
+		return nil
+	}
+	for _, pattern := range iface.Patterns {
+		if pattern.Type == ast.Setlike {
+			return pattern
+		}
+	}
+	return nil
+}
+
 // pascalCase returns the snake-case string s transformed into 'PascalCase',
 // Rules:
 // * The first letter of the string is capitalized