tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Adapter.cpp (24842B)


      1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "Adapter.h"
      7 
      8 #include <algorithm>
      9 
     10 #include "Device.h"
     11 #include "Instance.h"
     12 #include "SupportedFeatures.h"
     13 #include "SupportedLimits.h"
     14 #include "ipc/WebGPUChild.h"
     15 #include "mozilla/dom/BindingDeclarations.h"
     16 #include "mozilla/dom/Promise.h"
     17 #include "mozilla/dom/WebGPUBinding.h"
     18 #include "mozilla/webgpu/ffi/wgpu.h"
     19 
     20 namespace mozilla::webgpu {
     21 
     22 GPU_IMPL_CYCLE_COLLECTION(AdapterInfo, mParent)
     23 GPU_IMPL_JS_WRAP(AdapterInfo)
     24 
     25 uint32_t AdapterInfo::SubgroupMinSize() const {
     26  // From the spec. at
     27  // <https://www.w3.org/TR/2025/CRD-webgpu-20250319/#dom-gpuadapterinfo-subgroupminsize>:
     28  //
     29  // > If `["subgroups"](https://www.w3.org/TR/webgpu/#subgroups)` is supported,
     30  // > set `subgroupMinSize` to the smallest supported subgroup size. Otherwise,
     31  // > set this value to 4.
     32  // >
     33  // > Note: To preserve privacy, the user agent may choose to not support some
     34  // > features or provide values for the property which do not distinguish
     35  // > different devices, but are still usable (e.g. use the default value of
     36  // > 4 for all devices).
     37 
     38  if (GetParentObject()->ShouldResistFingerprinting(
     39          RFPTarget::WebGPUSubgroupSizes)) {
     40    return 4;
     41  }
     42 
     43  // TODO: When we support `subgroups`, use the supported amount instead:
     44  // <https://bugzilla.mozilla.org/show_bug.cgi?id=1955417>
     45  return 4;
     46 }
     47 
     48 uint32_t AdapterInfo::SubgroupMaxSize() const {
     49  // From the spec. at
     50  // <https://www.w3.org/TR/2025/CRD-webgpu-20250319/#dom-gpuadapterinfo-subgroupmaxsize>:
     51  //
     52  // > If `["subgroups"](https://www.w3.org/TR/webgpu/#subgroups)` is supported,
     53  // > set `subgroupMaxSize` to the largest supported subgroup size. Otherwise,
     54  // > set this value to 128.
     55  // >
     56  // > Note: To preserve privacy, the user agent may choose to not support some
     57  // > features or provide values for the property which do not distinguish
     58  // > different devices, but are still usable (e.g. use the default value of
     59  // > 128 for all devices).
     60 
     61  if (GetParentObject()->ShouldResistFingerprinting(
     62          RFPTarget::WebGPUSubgroupSizes)) {
     63    return 128;
     64  }
     65 
     66  // TODO: When we support `subgroups`, use the supported amount instead:
     67  // <https://bugzilla.mozilla.org/show_bug.cgi?id=1955417>
     68  return 128;
     69 }
     70 
     71 bool AdapterInfo::IsFallbackAdapter() const {
     72  if (GetParentObject()->ShouldResistFingerprinting(
     73          RFPTarget::WebGPUIsFallbackAdapter)) {
     74    // Always report hardware support for WebGPU.
     75    // This behaviour matches with media capabilities API.
     76    return false;
     77  }
     78 
     79  return mAboutSupportInfo->device_type ==
     80         ffi::WGPUDeviceType::WGPUDeviceType_Cpu;
     81 }
     82 
     83 void AdapterInfo::GetWgpuName(nsString& s) const {
     84  s = mAboutSupportInfo->name;
     85 }
     86 
     87 uint32_t AdapterInfo::WgpuVendor() const { return mAboutSupportInfo->vendor; }
     88 
     89 uint32_t AdapterInfo::WgpuDevice() const { return mAboutSupportInfo->device; }
     90 
     91 void AdapterInfo::GetWgpuDeviceType(nsString& s) const {
     92  switch (mAboutSupportInfo->device_type) {
     93    case ffi::WGPUDeviceType_Cpu:
     94      s.AssignLiteral("Cpu");
     95      return;
     96    case ffi::WGPUDeviceType_DiscreteGpu:
     97      s.AssignLiteral("DiscreteGpu");
     98      return;
     99    case ffi::WGPUDeviceType_IntegratedGpu:
    100      s.AssignLiteral("IntegratedGpu");
    101      return;
    102    case ffi::WGPUDeviceType_VirtualGpu:
    103      s.AssignLiteral("VirtualGpu");
    104      return;
    105    case ffi::WGPUDeviceType_Other:
    106      s.AssignLiteral("Other");
    107      return;
    108    case ffi::WGPUDeviceType_Sentinel:
    109      break;
    110  }
    111  MOZ_CRASH("Bad `ffi::WGPUDeviceType`");
    112 }
    113 
    114 void AdapterInfo::GetWgpuDriver(nsString& s) const {
    115  s = mAboutSupportInfo->driver;
    116 }
    117 
    118 void AdapterInfo::GetWgpuDriverInfo(nsString& s) const {
    119  s = mAboutSupportInfo->driver_info;
    120 }
    121 
    122 void AdapterInfo::GetWgpuBackend(nsString& s) const {
    123  switch (mAboutSupportInfo->backend) {
    124    case ffi::WGPUBackend_Noop:
    125      s.AssignLiteral("No-op");
    126      return;
    127    case ffi::WGPUBackend_Vulkan:
    128      s.AssignLiteral("Vulkan");
    129      return;
    130    case ffi::WGPUBackend_Metal:
    131      s.AssignLiteral("Metal");
    132      return;
    133    case ffi::WGPUBackend_Dx12:
    134      s.AssignLiteral("Dx12");
    135      return;
    136    case ffi::WGPUBackend_Gl:
    137      s.AssignLiteral("Gl");
    138      return;
    139    case ffi::WGPUBackend_BrowserWebGpu:  // This should never happen, because
    140                                          // we _are_ the browser.
    141    case ffi::WGPUBackend_Sentinel:
    142      break;
    143  }
    144  MOZ_CRASH("Bad `ffi::WGPUBackend`");
    145 }
    146 
    147 // -
    148 
    149 GPU_IMPL_CYCLE_COLLECTION(Adapter, mParent, mFeatures, mLimits, mInfo)
    150 GPU_IMPL_JS_WRAP(Adapter)
    151 
    152 enum class FeatureImplementationStatusTag {
    153  Implemented,
    154  NotImplemented,
    155 };
    156 
    157 struct FeatureImplementationStatus {
    158  FeatureImplementationStatusTag tag =
    159      FeatureImplementationStatusTag::NotImplemented;
    160  union {
    161    struct {
    162      ffi::WGPUFeaturesWebGPU wgpuBit;
    163    } implemented;
    164    struct {
    165      const char* bugzillaUrlAscii;
    166    } unimplemented;
    167  } value = {
    168      .unimplemented = {
    169          .bugzillaUrlAscii =
    170              "https://bugzilla.mozilla.org/"
    171              "enter_bug.cgi?product=Core&component=Graphics%3A+WebGPU"}};
    172 
    173  static FeatureImplementationStatus fromDomFeature(
    174      const dom::GPUFeatureName aFeature) {
    175    auto implemented = [](const ffi::WGPUFeaturesWebGPU aBit) {
    176      FeatureImplementationStatus feat;
    177      feat.tag = FeatureImplementationStatusTag::Implemented;
    178      feat.value.implemented.wgpuBit = aBit;
    179      return feat;
    180    };
    181    auto unimplemented = [](const char* aBugzillaUrl) {
    182      FeatureImplementationStatus feat;
    183      feat.tag = FeatureImplementationStatusTag::NotImplemented;
    184      feat.value.unimplemented.bugzillaUrlAscii = aBugzillaUrl;
    185      return feat;
    186    };
    187    switch (aFeature) {
    188      case dom::GPUFeatureName::Depth_clip_control:
    189        return implemented(WGPUWEBGPU_FEATURE_DEPTH_CLIP_CONTROL);
    190 
    191      case dom::GPUFeatureName::Depth32float_stencil8:
    192        return implemented(WGPUWEBGPU_FEATURE_DEPTH32FLOAT_STENCIL8);
    193 
    194      case dom::GPUFeatureName::Texture_compression_bc:
    195        return implemented(WGPUWEBGPU_FEATURE_TEXTURE_COMPRESSION_BC);
    196 
    197      case dom::GPUFeatureName::Texture_compression_bc_sliced_3d:
    198        return implemented(WGPUWEBGPU_FEATURE_TEXTURE_COMPRESSION_BC_SLICED_3D);
    199 
    200      case dom::GPUFeatureName::Texture_compression_etc2:
    201        return implemented(WGPUWEBGPU_FEATURE_TEXTURE_COMPRESSION_ETC2);
    202 
    203      case dom::GPUFeatureName::Texture_compression_astc:
    204        return implemented(WGPUWEBGPU_FEATURE_TEXTURE_COMPRESSION_ASTC);
    205 
    206      case dom::GPUFeatureName::Texture_compression_astc_sliced_3d:
    207        return implemented(
    208            WGPUWEBGPU_FEATURE_TEXTURE_COMPRESSION_ASTC_SLICED_3D);
    209 
    210      case dom::GPUFeatureName::Timestamp_query:
    211        return implemented(WGPUWEBGPU_FEATURE_TIMESTAMP_QUERY);
    212 
    213      case dom::GPUFeatureName::Indirect_first_instance:
    214        return implemented(WGPUWEBGPU_FEATURE_INDIRECT_FIRST_INSTANCE);
    215 
    216      case dom::GPUFeatureName::Shader_f16:
    217        return implemented(WGPUWEBGPU_FEATURE_SHADER_F16);
    218 
    219      case dom::GPUFeatureName::Rg11b10ufloat_renderable:
    220        return implemented(WGPUWEBGPU_FEATURE_RG11B10UFLOAT_RENDERABLE);
    221 
    222      case dom::GPUFeatureName::Bgra8unorm_storage:
    223        return implemented(WGPUWEBGPU_FEATURE_BGRA8UNORM_STORAGE);
    224 
    225      case dom::GPUFeatureName::Float32_filterable:
    226        return implemented(WGPUWEBGPU_FEATURE_FLOAT32_FILTERABLE);
    227 
    228      case dom::GPUFeatureName::Float32_blendable:
    229        return unimplemented(
    230            "https://bugzilla.mozilla.org/show_bug.cgi?id=1931630");
    231 
    232      case dom::GPUFeatureName::Clip_distances:
    233        return unimplemented(
    234            "https://bugzilla.mozilla.org/show_bug.cgi?id=1931629");
    235 
    236      case dom::GPUFeatureName::Dual_source_blending:
    237        // return implemented(WGPUWEBGPU_FEATURE_DUAL_SOURCE_BLENDING);
    238        return unimplemented(
    239            "https://bugzilla.mozilla.org/show_bug.cgi?id=1924328");
    240 
    241      case dom::GPUFeatureName::Subgroups:
    242        // return implemented(WGPUWEBGPU_FEATURE_SUBGROUPS);
    243        return unimplemented(
    244            "https://bugzilla.mozilla.org/show_bug.cgi?id=1955417");
    245 
    246      case dom::GPUFeatureName::Primitive_index:
    247        // return implemented(WGPUWEBGPU_FEATURE_PRIMITIVE_INDEX);
    248        return unimplemented(
    249            "https://bugzilla.mozilla.org/show_bug.cgi?id=1989116");
    250 
    251      case dom::GPUFeatureName::Core_features_and_limits:
    252        // NOTE: `0` means that no bits are set in calling code, but this is on
    253        // purpose. We currently _always_ return this feature elsewhere. If this
    254        // actually corresponds to a value in the future, remove the
    255        // unconditional setting of this feature!
    256        return implemented(0);
    257    }
    258    MOZ_CRASH("Bad GPUFeatureName.");
    259  }
    260 };
    261 
    262 double GetLimitDefault(Limit aLimit) {
    263  switch (aLimit) {
    264      // clang-format off
    265      case Limit::MaxTextureDimension1D: return 8192;
    266      case Limit::MaxTextureDimension2D: return 8192;
    267      case Limit::MaxTextureDimension3D: return 2048;
    268      case Limit::MaxTextureArrayLayers: return 256;
    269      case Limit::MaxBindGroups: return 4;
    270      case Limit::MaxBindGroupsPlusVertexBuffers: return 24;
    271      case Limit::MaxBindingsPerBindGroup: return 1000;
    272      case Limit::MaxDynamicUniformBuffersPerPipelineLayout: return 8;
    273      case Limit::MaxDynamicStorageBuffersPerPipelineLayout: return 4;
    274      case Limit::MaxSampledTexturesPerShaderStage: return 16;
    275      case Limit::MaxSamplersPerShaderStage: return 16;
    276      case Limit::MaxStorageBuffersPerShaderStage: return 8;
    277      case Limit::MaxStorageTexturesPerShaderStage: return 4;
    278      case Limit::MaxUniformBuffersPerShaderStage: return 12;
    279      case Limit::MaxUniformBufferBindingSize: return 65536;
    280      case Limit::MaxStorageBufferBindingSize: return 134217728;
    281      case Limit::MinUniformBufferOffsetAlignment: return 256;
    282      case Limit::MinStorageBufferOffsetAlignment: return 256;
    283      case Limit::MaxVertexBuffers: return 8;
    284      case Limit::MaxBufferSize: return 268435456;
    285      case Limit::MaxVertexAttributes: return 16;
    286      case Limit::MaxVertexBufferArrayStride: return 2048;
    287      case Limit::MaxInterStageShaderVariables: return 16;
    288      case Limit::MaxColorAttachments: return 8;
    289      case Limit::MaxColorAttachmentBytesPerSample: return 32;
    290      case Limit::MaxComputeWorkgroupStorageSize: return 16384;
    291      case Limit::MaxComputeInvocationsPerWorkgroup: return 256;
    292      case Limit::MaxComputeWorkgroupSizeX: return 256;
    293      case Limit::MaxComputeWorkgroupSizeY: return 256;
    294      case Limit::MaxComputeWorkgroupSizeZ: return 64;
    295      case Limit::MaxComputeWorkgroupsPerDimension: return 65535;
    296      // clang-format on
    297  }
    298  MOZ_CRASH("Bad Limit");
    299 }
    300 
    301 Adapter::Adapter(Instance* const aParent, WebGPUChild* const aChild,
    302                 const std::shared_ptr<ffi::WGPUAdapterInformation>& aInfo)
    303    : ObjectBase(aChild, aInfo->id, ffi::wgpu_client_drop_adapter),
    304      ChildOf(aParent),
    305      mFeatures(new SupportedFeatures(this)),
    306      mLimits(new SupportedLimits(this, aInfo->limits)),
    307      mInfo(new AdapterInfo(this, aInfo)),
    308      mInfoInner(aInfo) {
    309  ErrorResult ignoredRv;  // It's onerous to plumb this in from outside in this
    310                          // case, and we don't really need to.
    311 
    312  static const auto FEATURE_BY_BIT = []() {
    313    auto ret =
    314        std::unordered_map<ffi::WGPUFeaturesWebGPU, dom::GPUFeatureName>{};
    315 
    316    for (const auto feature :
    317         dom::MakeWebIDLEnumeratedRange<dom::GPUFeatureName>()) {
    318      const auto status = FeatureImplementationStatus::fromDomFeature(feature);
    319      switch (status.tag) {
    320        case FeatureImplementationStatusTag::Implemented:
    321          ret[status.value.implemented.wgpuBit] = feature;
    322          break;
    323        case FeatureImplementationStatusTag::NotImplemented:
    324          break;
    325      }
    326    }
    327 
    328    return ret;
    329  }();
    330 
    331  auto remainingFeatureBits = aInfo->features;
    332  auto bitMask = decltype(remainingFeatureBits){0};
    333  while (remainingFeatureBits) {
    334    if (bitMask) {
    335      bitMask <<= 1;
    336    } else {
    337      bitMask = 1;
    338    }
    339    const auto bit = remainingFeatureBits & bitMask;
    340    remainingFeatureBits &= ~bitMask;  // Clear bit.
    341    if (!bit) {
    342      continue;
    343    }
    344 
    345    const auto featureForBit = FEATURE_BY_BIT.find(bit);
    346    if (featureForBit != FEATURE_BY_BIT.end()) {
    347      mFeatures->Add(featureForBit->second, ignoredRv);
    348    } else {
    349      // One of two cases:
    350      //
    351      // 1. WGPU claims to implement this, but we've explicitly marked this as
    352      // not implemented.
    353      // 2. We don't recognize that bit, but maybe it's a wpgu-native-only
    354      // feature.
    355    }
    356  }
    357  // TODO: Once we implement compat mode (see
    358  // <https://bugzilla.mozilla.org/show_bug.cgi?id=1905951>), do not report this
    359  // unconditionally.
    360  //
    361  // Meanwhile, the current spec. proposal's `Initialization` section (see
    362  // <https://github.com/gpuweb/gpuweb/blob/main/proposals/compatibility-mode.md#initialization>)
    363  // says:
    364  //
    365  // > Core-defaulting adapters *always* support the
    366  // > `"core-features-and-limits"` feature. It is *automatically enabled* on
    367  // > devices created from such adapters.
    368  mFeatures->Add(dom::GPUFeatureName::Core_features_and_limits, ignoredRv);
    369 
    370  // We clamp limits to defaults when requestDevice is called, but
    371  // we return the actual limits when only requestAdapter is called.
    372  // So, we should clamp the limits here too if we should RFP.
    373  if (GetParentObject()->ShouldResistFingerprinting(RFPTarget::WebGPULimits)) {
    374    for (const auto limit : MakeInclusiveEnumeratedRange(Limit::_LAST)) {
    375      SetLimit(mLimits->mFfi.get(), limit, GetLimitDefault(limit));
    376    }
    377  }
    378 }
    379 
    380 Adapter::~Adapter() = default;
    381 
    382 const RefPtr<SupportedFeatures>& Adapter::Features() const { return mFeatures; }
    383 const RefPtr<SupportedLimits>& Adapter::Limits() const { return mLimits; }
    384 const RefPtr<AdapterInfo>& Adapter::Info() const { return mInfo; }
    385 
    386 bool Adapter::SupportSharedTextureInSwapChain() const {
    387  return mInfoInner->support_use_shared_texture_in_swap_chain;
    388 }
    389 
    390 static std::string_view ToJsKey(const Limit limit) {
    391  switch (limit) {
    392    case Limit::MaxTextureDimension1D:
    393      return "maxTextureDimension1D";
    394    case Limit::MaxTextureDimension2D:
    395      return "maxTextureDimension2D";
    396    case Limit::MaxTextureDimension3D:
    397      return "maxTextureDimension3D";
    398    case Limit::MaxTextureArrayLayers:
    399      return "maxTextureArrayLayers";
    400    case Limit::MaxBindGroups:
    401      return "maxBindGroups";
    402    case Limit::MaxBindGroupsPlusVertexBuffers:
    403      return "maxBindGroupsPlusVertexBuffers";
    404    case Limit::MaxBindingsPerBindGroup:
    405      return "maxBindingsPerBindGroup";
    406    case Limit::MaxDynamicUniformBuffersPerPipelineLayout:
    407      return "maxDynamicUniformBuffersPerPipelineLayout";
    408    case Limit::MaxDynamicStorageBuffersPerPipelineLayout:
    409      return "maxDynamicStorageBuffersPerPipelineLayout";
    410    case Limit::MaxSampledTexturesPerShaderStage:
    411      return "maxSampledTexturesPerShaderStage";
    412    case Limit::MaxSamplersPerShaderStage:
    413      return "maxSamplersPerShaderStage";
    414    case Limit::MaxStorageBuffersPerShaderStage:
    415      return "maxStorageBuffersPerShaderStage";
    416    case Limit::MaxStorageTexturesPerShaderStage:
    417      return "maxStorageTexturesPerShaderStage";
    418    case Limit::MaxUniformBuffersPerShaderStage:
    419      return "maxUniformBuffersPerShaderStage";
    420    case Limit::MaxUniformBufferBindingSize:
    421      return "maxUniformBufferBindingSize";
    422    case Limit::MaxStorageBufferBindingSize:
    423      return "maxStorageBufferBindingSize";
    424    case Limit::MinUniformBufferOffsetAlignment:
    425      return "minUniformBufferOffsetAlignment";
    426    case Limit::MinStorageBufferOffsetAlignment:
    427      return "minStorageBufferOffsetAlignment";
    428    case Limit::MaxVertexBuffers:
    429      return "maxVertexBuffers";
    430    case Limit::MaxBufferSize:
    431      return "maxBufferSize";
    432    case Limit::MaxVertexAttributes:
    433      return "maxVertexAttributes";
    434    case Limit::MaxVertexBufferArrayStride:
    435      return "maxVertexBufferArrayStride";
    436    case Limit::MaxInterStageShaderVariables:
    437      return "maxInterStageShaderVariables";
    438    case Limit::MaxColorAttachments:
    439      return "maxColorAttachments";
    440    case Limit::MaxColorAttachmentBytesPerSample:
    441      return "maxColorAttachmentBytesPerSample";
    442    case Limit::MaxComputeWorkgroupStorageSize:
    443      return "maxComputeWorkgroupStorageSize";
    444    case Limit::MaxComputeInvocationsPerWorkgroup:
    445      return "maxComputeInvocationsPerWorkgroup";
    446    case Limit::MaxComputeWorkgroupSizeX:
    447      return "maxComputeWorkgroupSizeX";
    448    case Limit::MaxComputeWorkgroupSizeY:
    449      return "maxComputeWorkgroupSizeY";
    450    case Limit::MaxComputeWorkgroupSizeZ:
    451      return "maxComputeWorkgroupSizeZ";
    452    case Limit::MaxComputeWorkgroupsPerDimension:
    453      return "maxComputeWorkgroupsPerDimension";
    454  }
    455  MOZ_CRASH("Bad Limit");
    456 }
    457 
    458 uint64_t Adapter::MissingFeatures() const {
    459  uint64_t missingFeatures = 0;
    460 
    461  // Turn on all implemented features.
    462  for (const auto feature :
    463       dom::MakeWebIDLEnumeratedRange<dom::GPUFeatureName>()) {
    464    const auto status = FeatureImplementationStatus::fromDomFeature(feature);
    465    switch (status.tag) {
    466      case FeatureImplementationStatusTag::Implemented:
    467        missingFeatures |= status.value.implemented.wgpuBit;
    468        break;
    469      case FeatureImplementationStatusTag::NotImplemented:
    470        break;
    471    }
    472  }
    473 
    474  // Turn off features that are supported by the adapter.
    475  for (auto feature : mFeatures->Features()) {
    476    const auto status = FeatureImplementationStatus::fromDomFeature(feature);
    477    switch (status.tag) {
    478      case FeatureImplementationStatusTag::Implemented:
    479        missingFeatures &= ~status.value.implemented.wgpuBit;
    480        break;
    481      case FeatureImplementationStatusTag::NotImplemented:
    482        break;
    483    }
    484  }
    485 
    486  return missingFeatures;
    487 }
    488 
    489 // -
    490 // String helpers
    491 
    492 static auto ToACString(const nsAString& s) { return NS_ConvertUTF16toUTF8(s); }
    493 
    494 // -
    495 // Adapter::RequestDevice
    496 
    497 already_AddRefed<dom::Promise> Adapter::RequestDevice(
    498    const dom::GPUDeviceDescriptor& aDesc, ErrorResult& aRv) {
    499  RefPtr<dom::Promise> promise = dom::Promise::Create(GetParentObject(), aRv);
    500  if (NS_WARN_IF(aRv.Failed())) {
    501    return nullptr;
    502  }
    503  RefPtr<dom::Promise> lost_promise =
    504      dom::Promise::Create(GetParentObject(), aRv);
    505  if (NS_WARN_IF(aRv.Failed())) {
    506    return nullptr;
    507  }
    508 
    509  ffi::WGPULimits deviceLimits = *mLimits->mFfi;
    510  for (const auto limit : MakeInclusiveEnumeratedRange(Limit::_LAST)) {
    511    SetLimit(&deviceLimits, limit, GetLimitDefault(limit));
    512  }
    513 
    514  // -
    515 
    516  [&]() {  // So that we can `return;` instead of `return promise.forget();`.
    517    // -
    518    // Validate Features
    519 
    520    ffi::WGPUFeaturesWebGPU featureBits = 0;
    521    for (const auto requested : aDesc.mRequiredFeatures) {
    522      auto status = FeatureImplementationStatus::fromDomFeature(requested);
    523      switch (status.tag) {
    524        case FeatureImplementationStatusTag::Implemented:
    525          featureBits |= status.value.implemented.wgpuBit;
    526          break;
    527        case FeatureImplementationStatusTag::NotImplemented: {
    528          const auto featureStr = dom::GetEnumString(requested);
    529          (void)featureStr;
    530          nsPrintfCString msg(
    531              "`GPUAdapter.requestDevice`: '%s' was requested in "
    532              "`requiredFeatures`, but it is not supported by Firefox. "
    533              "Follow <%s> for updates.",
    534              featureStr.get(), status.value.unimplemented.bugzillaUrlAscii);
    535          promise->MaybeRejectWithTypeError(msg);
    536          return;
    537        }
    538      }
    539 
    540      const bool supportedByAdapter = mFeatures->Features().count(requested);
    541      if (!supportedByAdapter) {
    542        const auto fstr = dom::GetEnumString(requested);
    543        const auto astr = this->LabelOrId();
    544        nsPrintfCString msg(
    545            "`GPUAdapter.requestDevice`: '%s' was requested in "
    546            "`requiredFeatures`, but it is not supported by adapter %s.",
    547            fstr.get(), astr.get());
    548        promise->MaybeRejectWithTypeError(msg);
    549        return;
    550      }
    551    }
    552 
    553    // -
    554    // Validate Limits
    555 
    556    if (aDesc.mRequiredLimits.WasPassed()) {
    557      static const auto LIMIT_BY_JS_KEY = []() {
    558        std::unordered_map<std::string_view, Limit> ret;
    559        for (const auto limit : MakeInclusiveEnumeratedRange(Limit::_LAST)) {
    560          const auto jsKeyU8 = ToJsKey(limit);
    561          ret[jsKeyU8] = limit;
    562        }
    563        return ret;
    564      }();
    565 
    566      for (const auto& entry : aDesc.mRequiredLimits.Value().Entries()) {
    567        const auto& keyU16 = entry.mKey;
    568        const nsCString keyU8 = ToACString(keyU16);
    569        const auto itr = LIMIT_BY_JS_KEY.find(keyU8.get());
    570        if (itr == LIMIT_BY_JS_KEY.end()) {
    571          nsPrintfCString msg("requestDevice: Limit '%s' not recognized.",
    572                              keyU8.get());
    573          promise->MaybeRejectWithOperationError(msg);
    574          return;
    575        }
    576 
    577        const auto& limit = itr->second;
    578        uint64_t requestedValue = entry.mValue;
    579        const auto supportedValue = GetLimit(*mLimits->mFfi, limit);
    580        if (StringBeginsWith(keyU8, "max"_ns)) {
    581          if (requestedValue > supportedValue) {
    582            nsPrintfCString msg(
    583                "requestDevice: Request for limit '%s' must be <= supported "
    584                "%s, was %s.",
    585                keyU8.get(), std::to_string(supportedValue).c_str(),
    586                std::to_string(requestedValue).c_str());
    587            promise->MaybeRejectWithOperationError(msg);
    588            return;
    589          }
    590          // Clamp to default if lower than default
    591          requestedValue =
    592              std::max(requestedValue, GetLimit(deviceLimits, limit));
    593        } else {
    594          MOZ_ASSERT(StringBeginsWith(keyU8, "min"_ns));
    595          if (requestedValue < supportedValue) {
    596            nsPrintfCString msg(
    597                "requestDevice: Request for limit '%s' must be >= supported "
    598                "%s, was %s.",
    599                keyU8.get(), std::to_string(supportedValue).c_str(),
    600                std::to_string(requestedValue).c_str());
    601            promise->MaybeRejectWithOperationError(msg);
    602            return;
    603          }
    604          if (StringEndsWith(keyU8, "Alignment"_ns)) {
    605            if (!IsPowerOfTwo(requestedValue)) {
    606              nsPrintfCString msg(
    607                  "requestDevice: Request for limit '%s' must be a power of "
    608                  "two, "
    609                  "was %s.",
    610                  keyU8.get(), std::to_string(requestedValue).c_str());
    611              promise->MaybeRejectWithOperationError(msg);
    612              return;
    613            }
    614          }
    615          /// Clamp to default if higher than default
    616          /// Changing implementation in a way that increases fingerprinting
    617          /// surface? Please create a bug in [Core::Privacy: Anti
    618          /// Tracking](https://bugzilla.mozilla.org/enter_bug.cgi?product=Core&component=Privacy%3A%20Anti-Tracking)
    619          requestedValue =
    620              std::min(requestedValue, GetLimit(deviceLimits, limit));
    621        }
    622 
    623        SetLimit(&deviceLimits, limit, requestedValue);
    624      }
    625    }
    626 
    627    // -
    628 
    629    RefPtr<SupportedFeatures> features = new SupportedFeatures(this);
    630    for (const auto& feature : aDesc.mRequiredFeatures) {
    631      features->Add(feature, aRv);
    632    }
    633    // TODO: Once we implement compat mode (see
    634    // <https://bugzilla.mozilla.org/show_bug.cgi?id=1905951>), do not report
    635    // this unconditionally.
    636    //
    637    // Meanwhile, the current spec. proposal's `Initialization` section (see
    638    // <https://github.com/gpuweb/gpuweb/blob/main/proposals/compatibility-mode.md#initialization>)
    639    // says:
    640    //
    641    // > Core-defaulting adapters *always* support the
    642    // > `"core-features-and-limits"` feature. It is *automatically enabled* on
    643    // > devices created from such adapters.
    644    features->Add(dom::GPUFeatureName::Core_features_and_limits, aRv);
    645 
    646    RefPtr<SupportedLimits> limits = new SupportedLimits(this, deviceLimits);
    647 
    648    ffi::WGPUFfiDeviceDescriptor ffiDesc = {};
    649    ffiDesc.required_features = featureBits;
    650    ffiDesc.required_limits = deviceLimits;
    651 
    652    ffi::WGPUDeviceQueueId ids =
    653        ffi::wgpu_client_request_device(GetClient(), GetId(), &ffiDesc);
    654 
    655    auto pending_promise = WebGPUChild::PendingRequestDevicePromise{
    656        RefPtr(promise), ids.device, ids.queue, aDesc.mLabel, RefPtr(this),
    657        features,        limits,     mInfo,     lost_promise};
    658    GetChild()->mPendingRequestDevicePromises.push_back(
    659        std::move(pending_promise));
    660 
    661  }();
    662 
    663  return promise.forget();
    664 }
    665 
    666 }  // namespace mozilla::webgpu