node: Implement GPUDevice.features

Bug: None
Change-Id: I29f2832d22567d357a474e2b9522d3b7f195b3fb
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/112600
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Kokoro: Ben Clayton <bclayton@google.com>
diff --git a/src/dawn/node/binding/CMakeLists.txt b/src/dawn/node/binding/CMakeLists.txt
index 7907ae7..a4e427b 100644
--- a/src/dawn/node/binding/CMakeLists.txt
+++ b/src/dawn/node/binding/CMakeLists.txt
@@ -59,6 +59,8 @@
     "GPUSampler.h"
     "GPUShaderModule.cpp"
     "GPUShaderModule.h"
+    "GPUSupportedFeatures.cpp"
+    "GPUSupportedFeatures.h"
     "GPUSupportedLimits.cpp"
     "GPUSupportedLimits.h"
     "GPUTexture.cpp"
diff --git a/src/dawn/node/binding/Converter.cpp b/src/dawn/node/binding/Converter.cpp
index f2718bd..524fdca 100644
--- a/src/dawn/node/binding/Converter.cpp
+++ b/src/dawn/node/binding/Converter.cpp
@@ -1622,6 +1622,85 @@
     return false;
 }
 
+bool Converter::Convert(wgpu::FeatureName& out, interop::GPUFeatureName in) {
+    switch (in) {
+        case interop::GPUFeatureName::kTextureCompressionBc:
+            out = wgpu::FeatureName::TextureCompressionBC;
+            return true;
+        case interop::GPUFeatureName::kTextureCompressionEtc2:
+            out = wgpu::FeatureName::TextureCompressionETC2;
+            return true;
+        case interop::GPUFeatureName::kTextureCompressionAstc:
+            out = wgpu::FeatureName::TextureCompressionASTC;
+            return true;
+        case interop::GPUFeatureName::kTimestampQuery:
+            out = wgpu::FeatureName::TimestampQuery;
+            return true;
+        case interop::GPUFeatureName::kDepth32FloatStencil8:
+            out = wgpu::FeatureName::Depth32FloatStencil8;
+            return true;
+        case interop::GPUFeatureName::kDepthClipControl:
+            out = wgpu::FeatureName::DepthClipControl;
+            return true;
+        case interop::GPUFeatureName::kIndirectFirstInstance:
+            out = wgpu::FeatureName::IndirectFirstInstance;
+            return true;
+        case interop::GPUFeatureName::kShaderF16:
+            out = wgpu::FeatureName::ShaderF16;
+            return true;
+        case interop::GPUFeatureName::kRg11B10UfloatRenderable:
+            out = wgpu::FeatureName::RG11B10UfloatRenderable;
+            return true;
+        case interop::GPUFeatureName::kBgra8UnormStorage:
+            // TODO(dawn:1123) Add support for these extensions when possible.
+            return false;
+    }
+    return false;
+}
+
+bool Converter::Convert(interop::GPUFeatureName& out, wgpu::FeatureName in) {
+    switch (in) {
+        case wgpu::FeatureName::Depth32FloatStencil8:
+            out = interop::GPUFeatureName::kDepth32FloatStencil8;
+            return true;
+        case wgpu::FeatureName::TimestampQuery:
+            out = interop::GPUFeatureName::kTimestampQuery;
+            return true;
+        case wgpu::FeatureName::TextureCompressionBC:
+            out = interop::GPUFeatureName::kTextureCompressionBc;
+            return true;
+        case wgpu::FeatureName::TextureCompressionETC2:
+            out = interop::GPUFeatureName::kTextureCompressionEtc2;
+            return true;
+        case wgpu::FeatureName::TextureCompressionASTC:
+            out = interop::GPUFeatureName::kTextureCompressionAstc;
+            return true;
+        case wgpu::FeatureName::IndirectFirstInstance:
+            out = interop::GPUFeatureName::kIndirectFirstInstance;
+            return true;
+        case wgpu::FeatureName::DepthClipControl:
+            out = interop::GPUFeatureName::kDepthClipControl;
+            return true;
+        case wgpu::FeatureName::ShaderF16:
+            out = interop::GPUFeatureName::kShaderF16;
+            return true;
+        case wgpu::FeatureName::RG11B10UfloatRenderable:
+            out = interop::GPUFeatureName::kRg11B10UfloatRenderable;
+            return true;
+
+        case wgpu::FeatureName::PipelineStatisticsQuery:
+        case wgpu::FeatureName::DawnShaderFloat16:
+        case wgpu::FeatureName::DawnInternalUsages:
+        case wgpu::FeatureName::DawnMultiPlanarFormats:
+        case wgpu::FeatureName::DawnNative:
+        case wgpu::FeatureName::ChromiumExperimentalDp4a:
+        case wgpu::FeatureName::TimestampQueryInsidePasses:
+        case wgpu::FeatureName::Undefined:
+            return false;
+    }
+    return false;
+}
+
 bool Converter::Convert(interop::GPUQueryType& out, wgpu::QueryType in) {
     switch (in) {
         case wgpu::QueryType::Occlusion:
diff --git a/src/dawn/node/binding/Converter.h b/src/dawn/node/binding/Converter.h
index 564b8a6..16ad9b9 100644
--- a/src/dawn/node/binding/Converter.h
+++ b/src/dawn/node/binding/Converter.h
@@ -270,6 +270,12 @@
 
     [[nodiscard]] bool Convert(interop::GPUQueryType& out, wgpu::QueryType in);
 
+    // The two conversion methods don't generate an error when false is returned. That
+    // responsibility is left to the caller if it is needed (it isn't always needed, see
+    // https://gpuweb.github.io/gpuweb/#gpu-supportedfeatures)
+    [[nodiscard]] bool Convert(wgpu::FeatureName& out, interop::GPUFeatureName in);
+    [[nodiscard]] bool Convert(interop::GPUFeatureName& out, wgpu::FeatureName in);
+
     // std::string to C string
     inline bool Convert(const char*& out, const std::string& in) {
         out = in.c_str();
diff --git a/src/dawn/node/binding/GPUAdapter.cpp b/src/dawn/node/binding/GPUAdapter.cpp
index c396a6a..73c8c11 100644
--- a/src/dawn/node/binding/GPUAdapter.cpp
+++ b/src/dawn/node/binding/GPUAdapter.cpp
@@ -18,9 +18,11 @@
 #include <utility>
 #include <vector>
 
+#include "src/dawn/node/binding/Converter.h"
 #include "src/dawn/node/binding/Errors.h"
 #include "src/dawn/node/binding/Flags.h"
 #include "src/dawn/node/binding/GPUDevice.h"
+#include "src/dawn/node/binding/GPUSupportedFeatures.h"
 #include "src/dawn/node/binding/GPUSupportedLimits.h"
 
 namespace {
@@ -88,72 +90,6 @@
 
 namespace wgpu::binding {
 
-namespace {
-
-////////////////////////////////////////////////////////////////////////////////
-// wgpu::binding::<anon>::Features
-// Implements interop::GPUSupportedFeatures
-////////////////////////////////////////////////////////////////////////////////
-class Features : public interop::GPUSupportedFeatures {
-  public:
-    explicit Features(std::vector<wgpu::FeatureName> features) {
-        for (wgpu::FeatureName feature : features) {
-            switch (feature) {
-                case wgpu::FeatureName::Depth32FloatStencil8:
-                    enabled_.emplace(interop::GPUFeatureName::kDepth32FloatStencil8);
-                    break;
-                case wgpu::FeatureName::TimestampQuery:
-                    enabled_.emplace(interop::GPUFeatureName::kTimestampQuery);
-                    break;
-                case wgpu::FeatureName::TextureCompressionBC:
-                    enabled_.emplace(interop::GPUFeatureName::kTextureCompressionBc);
-                    break;
-                case wgpu::FeatureName::TextureCompressionETC2:
-                    enabled_.emplace(interop::GPUFeatureName::kTextureCompressionEtc2);
-                    break;
-                case wgpu::FeatureName::TextureCompressionASTC:
-                    enabled_.emplace(interop::GPUFeatureName::kTextureCompressionAstc);
-                    break;
-                case wgpu::FeatureName::IndirectFirstInstance:
-                    enabled_.emplace(interop::GPUFeatureName::kIndirectFirstInstance);
-                    break;
-                case wgpu::FeatureName::DepthClipControl:
-                    enabled_.emplace(interop::GPUFeatureName::kDepthClipControl);
-                    break;
-                default:
-                    break;
-            }
-        }
-        // TODO(dawn:1123) add support for these extensions when possible.
-        // wgpu::interop::GPUFeatureName::kShaderF16
-        // wgpu::interop::GPUFeatureName::kBgra8UnormStorage
-    }
-
-    bool has(interop::GPUFeatureName feature) { return enabled_.count(feature) != 0; }
-
-    // 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;
-    }
-    std::vector<std::string> keys(Napi::Env) override {
-        std::vector<std::string> out;
-        out.reserve(enabled_.size());
-        for (auto feature : enabled_) {
-            out.push_back(interop::Converter<interop::GPUFeatureName>::ToString(feature));
-        }
-        return out;
-    }
-
-  private:
-    std::unordered_set<interop::GPUFeatureName> enabled_;
-};
-
-}  // namespace
-
 ////////////////////////////////////////////////////////////////////////////////
 // wgpu::bindings::GPUAdapter
 // TODO(crbug.com/dawn/1133): This is a stub implementation. Properly implement.
@@ -167,7 +103,8 @@
     size_t count = adapter.EnumerateFeatures(nullptr);
     std::vector<wgpu::FeatureName> features(count);
     adapter.EnumerateFeatures(&features[0]);
-    return interop::GPUSupportedFeatures::Create<Features>(env, std::move(features));
+    return interop::GPUSupportedFeatures::Create<GPUSupportedFeatures>(env, env,
+                                                                       std::move(features));
 }
 
 interop::Interface<interop::GPUSupportedLimits> GPUAdapter::getLimits(Napi::Env env) {
@@ -195,34 +132,19 @@
     wgpu::DeviceDescriptor desc{};  // TODO(crbug.com/dawn/1133): Fill in.
     interop::Promise<interop::Interface<interop::GPUDevice>> promise(env, PROMISE_INFO);
 
+    Converter conv(env);
     std::vector<wgpu::FeatureName> requiredFeatures;
-    // See src/dawn/native/Features.cpp for enum <-> string mappings.
     for (auto required : descriptor.requiredFeatures) {
-        switch (required) {
-            case interop::GPUFeatureName::kTextureCompressionBc:
-                requiredFeatures.emplace_back(wgpu::FeatureName::TextureCompressionBC);
-                continue;
-            case interop::GPUFeatureName::kTextureCompressionEtc2:
-                requiredFeatures.emplace_back(wgpu::FeatureName::TextureCompressionETC2);
-                continue;
-            case interop::GPUFeatureName::kTextureCompressionAstc:
-                requiredFeatures.emplace_back(wgpu::FeatureName::TextureCompressionASTC);
-                continue;
-            case interop::GPUFeatureName::kTimestampQuery:
-                requiredFeatures.emplace_back(wgpu::FeatureName::TimestampQuery);
-                continue;
-            case interop::GPUFeatureName::kDepth32FloatStencil8:
-                requiredFeatures.emplace_back(wgpu::FeatureName::Depth32FloatStencil8);
-                continue;
-            case interop::GPUFeatureName::kDepthClipControl:
-            case interop::GPUFeatureName::kShaderF16:
-            case interop::GPUFeatureName::kIndirectFirstInstance:
-            case interop::GPUFeatureName::kBgra8UnormStorage:
-            case interop::GPUFeatureName::kRg11B10UfloatRenderable:
-                // TODO(dawn:1123) Add support for these extensions when possible.
-                continue;
+        wgpu::FeatureName feature = wgpu::FeatureName::Undefined;
+
+        // requiredFeatures is a "sequence<GPUFeatureName>" so a Javascript exception should be
+        // thrown if one of the strings isn't one of the known features.
+        if (!conv(feature, required)) {
+            Napi::Error::New(env, "invalid value for GPUFeatureName").ThrowAsJavaScriptException();
+            return promise;
         }
-        UNIMPLEMENTED("required: ", required);
+
+        requiredFeatures.emplace_back(feature);
     }
 
     wgpu::RequiredLimits limits;
diff --git a/src/dawn/node/binding/GPUDevice.cpp b/src/dawn/node/binding/GPUDevice.cpp
index 5aab3eb..3623621 100644
--- a/src/dawn/node/binding/GPUDevice.cpp
+++ b/src/dawn/node/binding/GPUDevice.cpp
@@ -33,6 +33,7 @@
 #include "src/dawn/node/binding/GPURenderPipeline.h"
 #include "src/dawn/node/binding/GPUSampler.h"
 #include "src/dawn/node/binding/GPUShaderModule.h"
+#include "src/dawn/node/binding/GPUSupportedFeatures.h"
 #include "src/dawn/node/binding/GPUSupportedLimits.h"
 #include "src/dawn/node/binding/GPUTexture.h"
 #include "src/dawn/node/utils/Debug.h"
@@ -178,12 +179,11 @@
 }
 
 interop::Interface<interop::GPUSupportedFeatures> GPUDevice::getFeatures(Napi::Env env) {
-    class Features : public interop::GPUSupportedFeatures {
-      public:
-        bool has(Napi::Env, std::string feature) override { UNIMPLEMENTED(); }
-        std::vector<std::string> keys(Napi::Env) override { UNIMPLEMENTED(); }
-    };
-    return interop::GPUSupportedFeatures::Create<Features>(env);
+    size_t count = device_.EnumerateFeatures(nullptr);
+    std::vector<wgpu::FeatureName> features(count);
+    device_.EnumerateFeatures(&features[0]);
+    return interop::GPUSupportedFeatures::Create<GPUSupportedFeatures>(env, env,
+                                                                       std::move(features));
 }
 
 interop::Interface<interop::GPUSupportedLimits> GPUDevice::getLimits(Napi::Env env) {
diff --git a/src/dawn/node/binding/GPUSupportedFeatures.cpp b/src/dawn/node/binding/GPUSupportedFeatures.cpp
new file mode 100644
index 0000000..cebce06
--- /dev/null
+++ b/src/dawn/node/binding/GPUSupportedFeatures.cpp
@@ -0,0 +1,56 @@
+// Copyright 2022 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/binding/GPUSupportedFeatures.h"
+
+#include "src/dawn/node/binding/Converter.h"
+
+namespace wgpu::binding {
+
+////////////////////////////////////////////////////////////////////////////////
+// wgpu::bindings::GPUSupportedFeatures
+////////////////////////////////////////////////////////////////////////////////
+
+GPUSupportedFeatures::GPUSupportedFeatures(Napi::Env env, std::vector<wgpu::FeatureName> features) {
+    Converter conv(env);
+
+    // Add all known GPUFeatureNames that are known by dawn.node and skip the other ones are they
+    // may be native-only extension, Dawn-specific or other special cases.
+    for (wgpu::FeatureName feature : features) {
+        interop::GPUFeatureName gpuFeature;
+        if (conv(gpuFeature, feature)) {
+            enabled_.emplace(gpuFeature);
+        }
+    }
+}
+
+bool GPUSupportedFeatures::has(Napi::Env, std::string name) {
+    interop::GPUFeatureName feature;
+    if (!interop::Converter<interop::GPUFeatureName>::FromString(name, feature)) {
+        return false;
+    }
+
+    return enabled_.count(feature);
+}
+
+std::vector<std::string> GPUSupportedFeatures::keys(Napi::Env) {
+    std::vector<std::string> out;
+    out.reserve(enabled_.size());
+    for (auto feature : enabled_) {
+        out.push_back(interop::Converter<interop::GPUFeatureName>::ToString(feature));
+    }
+    return out;
+}
+
+}  // namespace wgpu::binding
diff --git a/src/dawn/node/binding/GPUSupportedFeatures.h b/src/dawn/node/binding/GPUSupportedFeatures.h
new file mode 100644
index 0000000..922cefa
--- /dev/null
+++ b/src/dawn/node/binding/GPUSupportedFeatures.h
@@ -0,0 +1,44 @@
+// Copyright 2022 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.
+
+#ifndef SRC_DAWN_NODE_BINDING_GPUSUPPORTEDFEATURES_H_
+#define SRC_DAWN_NODE_BINDING_GPUSUPPORTEDFEATURES_H_
+
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "dawn/webgpu_cpp.h"
+
+#include "src/dawn/node/interop/Napi.h"
+#include "src/dawn/node/interop/WebGPU.h"
+
+namespace wgpu::binding {
+
+// GPUSupportedLFeatures is an implementation of interop::GPUSupportedFeatures.
+class GPUSupportedFeatures final : public interop::GPUSupportedFeatures {
+  public:
+    GPUSupportedFeatures(Napi::Env env, std::vector<wgpu::FeatureName> features);
+
+    // interop::GPUSupportedFeatures interface compliance
+    bool has(Napi::Env, std::string name) override;
+    std::vector<std::string> keys(Napi::Env) override;
+
+  private:
+    std::unordered_set<interop::GPUFeatureName> enabled_;
+};
+
+}  // namespace wgpu::binding
+
+#endif  // SRC_DAWN_NODE_BINDING_GPUSUPPORTEDFEATURES_H_