Repository: gfx-rs/wgpu
Pull request #5452 (2024-03-29 03:24:26)

Add more hal methods
**Connections**
Revival of https://github.com/gfx-rs/wgpu/pull/3966

**Description**
* Adds CommandEncoder::as_hal_mut()
* Adds TextureView::as_hal()
* Adds a return value to Texture::has_hal()

Enable some interop with native graphics APIs. I'm not convinced that this is in any way a good idea. Users have to be extremely careful about not accidentally taking locks on or destroying resources while using them from native dispatches. They also have to consider pipeline barriers and image transitions themselves, with no help from wgpu in tracking them. To work around this in Bevy, I plan to bind the resources to a dummy compute pass that does nothing, but that's run before+after my native graphics API to try and coerce wgpu into tracking the lifetimes and barriers appropriately. This is very much a band-aid patch while I wait for a proper fix in https://github.com/gfx-rs/wgpu/issues/4067.

**Testing**
Will be tested as part of Bevy's FSR integration https://github.com/bevyengine/bevy/pull/12768.

**Checklist**

- [ x ] Run `cargo fmt`.
- [ x ] Run `cargo clippy`. If applicable, add:
  - [ x ] `--target wasm32-unknown-unknown`
  - [ x ] `--target wasm32-unknown-emscripten`
- [ x ] Run `cargo xtask test` to run tests.
- [ x ] Add change to `CHANGELOG.md`. See simple instructions inside file.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7e447cc67..a8cd7def4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -111,6 +111,11 @@ Bottom level categories:
 - Breaking change: [`wgpu_core::pipeline::ProgrammableStageDescriptor`](https://docs.rs/wgpu-core/latest/wgpu_core/pipeline/struct.ProgrammableStageDescriptor.html#structfield.entry_point) is now optional. By @ErichDonGubler in [#5305](https://github.com/gfx-rs/wgpu/pull/5305).
 - `Features::downlevel{_webgl2,}_features` was made const by @MultisampledNight in [#5343](https://github.com/gfx-rs/wgpu/pull/5343)
 
+- More as_hal methods and improvements by @JMS55 in [#5452](https://github.com/gfx-rs/wgpu/pull/5452)
+  - Added `wgpu::CommandEncoder::as_hal_mut`
+  - Added `wgpu::TextureView::as_hal`
+  - `wgpu::Texture::as_hal` now returns a user-defined type to match the other as_hal functions
+
 #### GLES
 
 - Log an error when GLES texture format heuristics fail. By @PolyMeilex in [#5266](https://github.com/gfx-rs/wgpu/issues/5266)
@@ -120,11 +125,12 @@ Bottom level categories:
 #### Naga
 
 - Allow user to select which MSL version to use via `--metal-version` with Naga CLI. By @pcleavelin in [#5392](https://github.com/gfx-rs/wgpu/pull/5392)
+- Support `arrayLength` for runtime-sized arrays inside binding arrays (for WGSL input and SPIR-V output). By @kvark in [#5428](https://github.com/gfx-rs/wgpu/pull/5428)
 
 #### WebGPU
 
 - Implement the `device_set_device_lost_callback` method for `ContextWebGpu`. By @suti in [#5438](https://github.com/gfx-rs/wgpu/pull/5438)
-- Add support for storage texture access modes `ReadOnly` and `ReadWrite`. By @JolifantoBambla in [#5434](https://github.com/gfx-rs/wgpu/pull/5434) 
+- Add support for storage texture access modes `ReadOnly` and `ReadWrite`. By @JolifantoBambla in [#5434](https://github.com/gfx-rs/wgpu/pull/5434)
 
 ### Bug Fixes
 
@@ -147,6 +153,7 @@ Bottom level categories:
 #### Naga
 - In spv-in, remove unnecessary "gl_PerVertex" name check so unused builtins will always be skipped. By @Imberflur in [#5227](https://github.com/gfx-rs/wgpu/pull/5227).
 - GLSL 410 does not support layout(binding = ...), enable only for GLSL 420. By @bes in [#5357](https://github.com/gfx-rs/wgpu/pull/5357)
+- In spv-out, check for acceleration and ray-query types when enabling ray-query extension to prevent validation error. By @Vecvec in [#5463](https://github.com/gfx-rs/wgpu/pull/5463)
 
 #### Tests
 
@@ -179,7 +186,7 @@ This release includes `wgpu`, `wgpu-core`, and `wgpu-hal`. All other crates are
 
 ### Major Changes
 
-#### Vendored WebGPU Bindings from `web_sys` 
+#### Vendored WebGPU Bindings from `web_sys`
 
 **`--cfg=web_sys_unstable_apis` is no longer needed in your `RUSTFLAGS` to compile for WebGPU!!!**
 
diff --git a/Cargo.lock b/Cargo.lock
index eb2e05042..69b4d58d9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1077,7 +1077,7 @@ dependencies = [
 
 [[package]]
 name = "deno_webgpu"
-version = "0.85.0"
+version = "0.110.0"
 dependencies = [
  "deno_core",
  "raw-window-handle 0.6.0",
diff --git a/Cargo.toml b/Cargo.toml
index 7cb26434b..52e8c5c17 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -170,7 +170,7 @@ deno_core = "0.272.0"
 deno_url = "0.143.0"
 deno_web = "0.174.0"
 deno_webidl = "0.143.0"
-deno_webgpu = { version = "0.85.0", path = "./deno_webgpu" }
+deno_webgpu = { version = "0.110.0", path = "./deno_webgpu" }
 tokio = "1.36.0"
 termcolor = "1.4.1"
 
diff --git a/cts_runner/src/bootstrap.js b/cts_runner/src/bootstrap.js
index b16884393..5d9c6f65d 100644
--- a/cts_runner/src/bootstrap.js
+++ b/cts_runner/src/bootstrap.js
@@ -26,8 +26,9 @@ import { Console } from "ext:deno_console/01_console.js";
 import * as url from "ext:deno_url/00_url.js";
 import { DOMException } from "ext:deno_web/01_dom_exception.js";
 import * as performance from "ext:deno_web/15_performance.js";
-import * as webgpu from "ext:deno_webgpu/01_webgpu.js";
+import { loadWebGPU } from "ext:deno_webgpu/00_init.js";
 import * as imageData from "ext:deno_web/16_image_data.js";
+const webgpu = loadWebGPU();
 
 // imports needed to pass module evaluation
 import "ext:deno_url/01_urlpattern.js";
@@ -39,6 +40,7 @@ import "ext:deno_web/10_filereader.js";
 import "ext:deno_web/12_location.js";
 import "ext:deno_web/13_message_port.js";
 import "ext:deno_web/14_compression.js";
+import "ext:deno_webgpu/02_surface.js";
 
 let globalThis_;
 
diff --git a/cts_runner/src/main.rs b/cts_runner/src/main.rs
index 201fda80d..fe8c1cf81 100644
--- a/cts_runner/src/main.rs
+++ b/cts_runner/src/main.rs
@@ -29,6 +29,9 @@ mod native {
             .ok_or_else(|| anyhow!("missing specifier in first command line argument"))?;
         let specifier = resolve_url_or_path(&url, &env::current_dir()?)?;
 
+        let mut feature_checker = deno_core::FeatureChecker::default();
+        feature_checker.enable_feature(deno_webgpu::UNSTABLE_FEATURE_NAME);
+
         let options = RuntimeOptions {
             module_loader: Some(Rc::new(deno_core::FsModuleLoader)),
             get_error_class_fn: Some(&get_error_class_name),
@@ -40,9 +43,10 @@ mod native {
                     Arc::new(BlobStore::default()),
                     None,
                 ),
-                deno_webgpu::deno_webgpu::init_ops_and_esm(true),
+                deno_webgpu::deno_webgpu::init_ops_and_esm(),
                 cts_runner::init_ops_and_esm(),
             ],
+            feature_checker: Some(Arc::new(feature_checker)),
             ..Default::default()
         };
         let mut js_runtime = JsRuntime::new(options);
diff --git a/deno_webgpu/00_init.js b/deno_webgpu/00_init.js
new file mode 100644
index 000000000..0f10847ce
--- /dev/null
+++ b/deno_webgpu/00_init.js
@@ -0,0 +1,7 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { core } from "ext:core/mod.js";
+
+const loadWebGPU = core.createLazyLoader("ext:deno_webgpu/01_webgpu.js");
+
+export { loadWebGPU };
diff --git a/deno_webgpu/01_webgpu.js b/deno_webgpu/01_webgpu.js
index 6aecb0323..11d7a5a44 100644
--- a/deno_webgpu/01_webgpu.js
+++ b/deno_webgpu/01_webgpu.js
@@ -1,4 +1,4 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 
 // @ts-check
 /// <reference path="../../core/lib.deno_core.d.ts" />
@@ -7,22 +7,100 @@
 /// <reference path="./lib.deno_webgpu.d.ts" />
 
 import { core, primordials } from "ext:core/mod.js";
-import * as ops from "ext:core/ops";
-import * as webidl from "ext:deno_webidl/00_webidl.js";
-import { EventTarget } from "ext:deno_web/02_event.js";
-import { DOMException } from "ext:deno_web/01_dom_exception.js";
-import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
+const {
+  isDataView,
+  isTypedArray,
+} = core;
+import {
+  op_webgpu_buffer_get_map_async,
+  op_webgpu_buffer_get_mapped_range,
+  op_webgpu_buffer_unmap,
+  op_webgpu_command_encoder_begin_compute_pass,
+  op_webgpu_command_encoder_begin_render_pass,
+  op_webgpu_command_encoder_clear_buffer,
+  op_webgpu_command_encoder_copy_buffer_to_buffer,
+  op_webgpu_command_encoder_copy_buffer_to_texture,
+  op_webgpu_command_encoder_copy_texture_to_buffer,
+  op_webgpu_command_encoder_copy_texture_to_texture,
+  op_webgpu_command_encoder_finish,
+  op_webgpu_command_encoder_insert_debug_marker,
+  op_webgpu_command_encoder_pop_debug_group,
+  op_webgpu_command_encoder_push_debug_group,
+  op_webgpu_command_encoder_resolve_query_set,
+  op_webgpu_command_encoder_write_timestamp,
+  op_webgpu_compute_pass_dispatch_workgroups,
+  op_webgpu_compute_pass_dispatch_workgroups_indirect,
+  op_webgpu_compute_pass_end,
+  op_webgpu_compute_pass_insert_debug_marker,
+  op_webgpu_compute_pass_pop_debug_group,
+  op_webgpu_compute_pass_push_debug_group,
+  op_webgpu_compute_pass_set_bind_group,
+  op_webgpu_compute_pass_set_pipeline,
+  op_webgpu_compute_pipeline_get_bind_group_layout,
+  op_webgpu_create_bind_group,
+  op_webgpu_create_bind_group_layout,
+  op_webgpu_create_buffer,
+  op_webgpu_create_command_encoder,
+  op_webgpu_create_compute_pipeline,
+  op_webgpu_create_pipeline_layout,
+  op_webgpu_create_query_set,
+  op_webgpu_create_render_bundle_encoder,
+  op_webgpu_create_render_pipeline,
+  op_webgpu_create_sampler,
+  op_webgpu_create_shader_module,
+  op_webgpu_create_texture,
+  op_webgpu_create_texture_view,
+  op_webgpu_queue_submit,
+  op_webgpu_render_bundle_encoder_draw,
+  op_webgpu_render_bundle_encoder_draw_indexed,
+  op_webgpu_render_bundle_encoder_draw_indirect,
+  op_webgpu_render_bundle_encoder_finish,
+  op_webgpu_render_bundle_encoder_insert_debug_marker,
+  op_webgpu_render_bundle_encoder_pop_debug_group,
+  op_webgpu_render_bundle_encoder_push_debug_group,
+  op_webgpu_render_bundle_encoder_set_bind_group,
+  op_webgpu_render_bundle_encoder_set_index_buffer,
+  op_webgpu_render_bundle_encoder_set_pipeline,
+  op_webgpu_render_bundle_encoder_set_vertex_buffer,
+  op_webgpu_render_pass_begin_occlusion_query,
+  op_webgpu_render_pass_draw,
+  op_webgpu_render_pass_draw_indexed,
+  op_webgpu_render_pass_draw_indexed_indirect,
+  op_webgpu_render_pass_draw_indirect,
+  op_webgpu_render_pass_end,
+  op_webgpu_render_pass_end_occlusion_query,
+  op_webgpu_render_pass_execute_bundles,
+  op_webgpu_render_pass_insert_debug_marker,
+  op_webgpu_render_pass_pop_debug_group,
+  op_webgpu_render_pass_push_debug_group,
+  op_webgpu_render_pass_set_bind_group,
+  op_webgpu_render_pass_set_blend_constant,
+  op_webgpu_render_pass_set_index_buffer,
+  op_webgpu_render_pass_set_pipeline,
+  op_webgpu_render_pass_set_scissor_rect,
+  op_webgpu_render_pass_set_stencil_reference,
+  op_webgpu_render_pass_set_vertex_buffer,
+  op_webgpu_render_pass_set_viewport,
+  op_webgpu_render_pipeline_get_bind_group_layout,
+  op_webgpu_request_adapter,
+  op_webgpu_request_adapter_info,
+  op_webgpu_request_device,
+  op_webgpu_write_buffer,
+  op_webgpu_write_texture,
+} from "ext:core/ops";
 const {
   ArrayBuffer,
-  ArrayBufferIsView,
+  ArrayBufferPrototypeGetByteLength,
   ArrayIsArray,
   ArrayPrototypeFilter,
   ArrayPrototypeMap,
   ArrayPrototypePop,
   ArrayPrototypePush,
+  DataViewPrototypeGetBuffer,
   Error,
   MathMax,
   ObjectDefineProperty,
+  ObjectHasOwn,
   ObjectPrototypeIsPrototypeOf,
   Promise,
   PromisePrototypeCatch,
@@ -31,17 +109,28 @@ const {
   PromiseResolve,
   SafeArrayIterator,
   SafePromiseAll,
-  Set,
+  SafeSet,
+  SafeWeakRef,
   SetPrototypeHas,
   Symbol,
   SymbolFor,
   SymbolIterator,
   TypeError,
+  TypedArrayPrototypeGetBuffer,
+  TypedArrayPrototypeGetSymbolToStringTag,
   Uint32Array,
-  Uint32ArrayPrototype,
   Uint8Array,
 } = primordials;
 
+import * as webidl from "ext:deno_webidl/00_webidl.js";
+import {
+  Event,
+  EventTarget,
+  defineEventHandler,
+} from "ext:deno_web/02_event.js";
+import { DOMException } from "ext:deno_web/01_dom_exception.js";
+import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
+
 const _rid = Symbol("[[rid]]");
 const _size = Symbol("[[size]]");
 const _usage = Symbol("[[usage]]");
@@ -228,6 +317,42 @@ class GPUOutOfMemoryError extends GPUError {
 }
 const GPUOutOfMemoryErrorPrototype = GPUOutOfMemoryError.prototype;
 
+class GPUInternalError extends GPUError {
+  name = "GPUInternalError";
+  constructor() {
+    super(illegalConstructorKey);
+    this[webidl.brand] = webidl.brand;
+  }
+}
+const GPUInternalErrorPrototype = GPUInternalError.prototype;
+
+class GPUUncapturedErrorEvent extends Event {
+  #error;
+
+  constructor(type, gpuUncapturedErrorEventInitDict) {
+    super(type, gpuUncapturedErrorEventInitDict);
+    this[webidl.brand] = webidl.brand;
+
+    const prefix = "Failed to construct 'GPUUncapturedErrorEvent'";
+    webidl.requiredArguments(arguments.length, 2, prefix);
+    gpuUncapturedErrorEventInitDict = webidl.converters
+      .gpuUncapturedErrorEventInitDict(
+        gpuUncapturedErrorEventInitDict,
+        prefix,
+        "Argument 2",
+      );
+
+    this.#error = gpuUncapturedErrorEventInitDict.error;
+  }
+
+  get error() {
+    webidl.assertBranded(this, GPUUncapturedErrorEventPrototype);
+    return this.#error;
+  }
+}
+const GPUUncapturedErrorEventPrototype = GPUUncapturedErrorEvent.prototype;
+defineEventHandler(GPUUncapturedErrorEvent.prototype, "uncapturederror");
+
 class GPU {
   [webidl.brand] = webidl.brand;
 
@@ -238,6 +363,7 @@ class GPU {
   /**
    * @param {GPURequestAdapterOptions} options
    */
+  // deno-lint-ignore require-await
   async requestAdapter(options = {}) {
     webidl.assertBranded(this, GPUPrototype);
     options = webidl.converters.GPURequestAdapterOptions(
@@ -246,7 +372,7 @@ class GPU {
       "Argument 1",
     );
 
-    const { err, ...data } = await ops.op_webgpu_request_adapter(
+    const { err, ...data } = op_webgpu_request_adapter(
       options.powerPreference,
       options.forceFallbackAdapter,
     );
@@ -258,6 +384,16 @@ class GPU {
     }
   }
 
+  getPreferredCanvasFormat() {
+    // Same as Gecko.
+    //
+    // https://github.com/mozilla/gecko-dev/blob/b75080bb8b11844d18cb5f9ac6e68a866ef8e243/dom/webgpu/Instance.h#L42-L47
+    if (core.build.os == "android") {
+      return "rgba8unorm";
+    }
+    return "bgra8unorm";
+  }
+
   [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
     return `${this.constructor.name} ${inspect({}, inspectOptions)}`;
   }
@@ -303,7 +439,6 @@ class GPUAdapter {
   }
   /** @returns {boolean} */
   get isFallbackAdapter() {
-    webidl.assertBranded(this, GPUAdapterPrototype);
     webidl.assertBranded(this, GPUAdapterPrototype);
     return this[_adapter].isFallbackAdapter;
   }
@@ -316,6 +451,7 @@ class GPUAdapter {
    * @param {GPUDeviceDescriptor} descriptor
    * @returns {Promise<GPUDevice>}
    */
+  // deno-lint-ignore require-await
   async requestDevice(descriptor = {}) {
     webidl.assertBranded(this, GPUAdapterPrototype);
     const prefix = "Failed to execute 'requestDevice' on 'GPUAdapter'";
@@ -336,7 +472,7 @@ class GPUAdapter {
       }
     }
 
-    const { rid, features, limits } = await ops.op_webgpu_request_device(
+    const { rid, queueRid, features, limits } = op_webgpu_request_device(
       this[_adapter].rid,
       descriptor.label,
       requiredFeatures,
@@ -349,43 +485,34 @@ class GPUAdapter {
       features: createGPUSupportedFeatures(features),
       limits: createGPUSupportedLimits(limits),
     });
-    return createGPUDevice(
+    const device = createGPUDevice(
       descriptor.label,
       inner,
-      createGPUQueue(descriptor.label, inner),
+      createGPUQueue(descriptor.label, inner, queueRid),
     );
+    inner.device = device;
+    return device;
   }
 
   /**
-   * @param {string[]} unmaskHints
    * @returns {Promise<GPUAdapterInfo>}
    */
-  async requestAdapterInfo(unmaskHints = []) {
+  requestAdapterInfo() {
     webidl.assertBranded(this, GPUAdapterPrototype);
-    const prefix = "Failed to execute 'requestAdapterInfo' on 'GPUAdapter'";
-    unmaskHints = webidl.converters["sequence<DOMString>"](
-      unmaskHints,
-      prefix,
-      "Argument 1",
-    );
 
     const {
       vendor,
       architecture,
       device,
       description,
-    } = await ops.op_webgpu_request_adapter_info(this[_adapter].rid);
+    } = op_webgpu_request_adapter_info(this[_adapter].rid);
 
     const adapterInfo = webidl.createBranded(GPUAdapterInfo);
-    adapterInfo[_vendor] = unmaskHints.includes("vendor") ? vendor : "";
-    adapterInfo[_architecture] = unmaskHints.includes("architecture")
-      ? architecture
-      : "";
-    adapterInfo[_device] = unmaskHints.includes("device") ? device : "";
-    adapterInfo[_description] = unmaskHints.includes("description")
-      ? description
-      : "";
-    return adapterInfo;
+    adapterInfo[_vendor] = vendor;
+    adapterInfo[_architecture] = architecture;
+    adapterInfo[_device] = device;
+    adapterInfo[_description] = description;
+    return PromiseResolve(adapterInfo);
   }
 
   [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
@@ -588,6 +715,14 @@ class GPUSupportedLimits {
     webidl.assertBranded(this, GPUSupportedLimitsPrototype);
     return this[_limits].maxInterStageShaderComponents;
   }
+  get maxColorAttachments() {
+    webidl.assertBranded(this, GPUSupportedLimitsPrototype);
+    return this[_limits].maxColorAttachments;
+  }
+  get maxColorAttachmentBytesPerSample() {
+    webidl.assertBranded(this, GPUSupportedLimitsPrototype);
+    return this[_limits].maxColorAttachmentBytesPerSample;
+  }
   get maxComputeWorkgroupStorageSize() {
     webidl.assertBranded(this, GPUSupportedLimitsPrototype);
     return this[_limits].maxComputeWorkgroupStorageSize;
@@ -617,7 +752,10 @@ class GPUSupportedLimits {
     return inspect(
       createFilteredInspectProxy({
         object: this,
-        evaluate: ObjectPrototypeIsPrototypeOf(GPUSupportedLimitsPrototype, this),
+        evaluate: ObjectPrototypeIsPrototypeOf(
+          GPUSupportedLimitsPrototype,
+          this,
+        ),
         keys: [
           "maxTextureDimension1D",
           "maxTextureDimension2D",
@@ -641,6 +779,8 @@ class GPUSupportedLimits {
           "maxVertexAttributes",
           "maxVertexBufferArrayStride",
           "maxInterStageShaderComponents",
+          "maxColorAttachments",
+          "maxColorAttachmentBytesPerSample",
           "maxComputeWorkgroupStorageSize",
           "maxComputeInvocationsPerWorkgroup",
           "maxComputeWorkgroupSizeX",
@@ -658,7 +798,7 @@ const GPUSupportedLimitsPrototype = GPUSupportedLimits.prototype;
 function createGPUSupportedFeatures(features) {
   /** @type {GPUSupportedFeatures} */
   const supportedFeatures = webidl.createBranded(GPUSupportedFeatures);
-  supportedFeatures[webidl.setlikeInner] = new Set(features);
+  supportedFeatures[webidl.setlikeInner] = new SafeSet(features);
   webidl.setlike(
     supportedFeatures,
     GPUSupportedFeaturesPrototype,
@@ -676,8 +816,7 @@ class GPUSupportedFeatures {
     if (ObjectPrototypeIsPrototypeOf(GPUSupportedFeaturesPrototype, this)) {
       return `${this.constructor.name} ${
         // deno-lint-ignore prefer-primordials
-        inspect([...this], inspectOptions)
-      }`;
+        inspect([...this], inspectOptions)}`;
     } else {
       return `${this.constructor.name} ${inspect({}, inspectOptions)}`;
     }
@@ -721,7 +860,10 @@ class GPUDeviceLostInfo {
     return inspect(
       createFilteredInspectProxy({
         object: this,
-        evaluate: ObjectPrototypeIsPrototypeOf(GPUDeviceLostInfoPrototype, this),
+        evaluate: ObjectPrototypeIsPrototypeOf(
+          GPUDeviceLostInfoPrototype,
+          this,
+        ),
         keys: [
           "reason",
           "message",
@@ -775,6 +917,7 @@ function GPUObjectBaseMixin(name, type) {
  * @property {number | undefined} rid
  * @property {GPUSupportedFeatures} features
  * @property {GPUSupportedLimits} limits
+ * @property {GPUDevice} device
  */
 
 class InnerGPUDevice {
@@ -786,7 +929,7 @@ class InnerGPUDevice {
   features;
   /** @type {GPUSupportedLimits} */
   limits;
-  /** @type {WeakRef<any>[]} */
+  /** @type {SafeWeakRef<any>[]} */
   resources;
   /** @type {boolean} */
   isLost;
@@ -796,6 +939,8 @@ class InnerGPUDevice {
   resolveLost;
   /** @type {ErrorScope[]} */
   errorScopeStack;
+  /** @type {GPUDevice} */
+  device;
 
   /**
    * @param {InnerGPUDeviceOptions} options
@@ -816,7 +961,7 @@ class InnerGPUDevice {
 
   /** @param {any} resource */
   trackResource(resource) {
-    ArrayPrototypePush(this.resources, new WeakRef(resource));
+    ArrayPrototypePush(this.resources, new SafeWeakRef(resource));
   }
 
   /** @param {{ type: string, value: string | null } | undefined} err */
@@ -841,6 +986,8 @@ class InnerGPUDevice {
             );
           case "out-of-memory":
             return PromiseReject(new GPUOutOfMemoryError());
+          case "internal":
+            return PromiseReject(new GPUInternalError());
         }
       }
     });
@@ -865,8 +1012,12 @@ class InnerGPUDevice {
         validationFilteredPromise,
       );
     } else {
-      PromisePrototypeCatch(validationFilteredPromise, () => {
-        // TODO(lucacasonato): emit an UncapturedErrorEvent
+      PromisePrototypeCatch(validationFilteredPromise, (err) => {
+        this.device.dispatchEvent(
+          new GPUUncapturedErrorEvent("uncapturederror", {
+            error: err,
+          }),
+        );
       });
     }
     // prevent uncaptured promise rejections
@@ -886,12 +1037,41 @@ class InnerGPUDevice {
     if (oomScope) {
       ArrayPrototypePush(oomScope.operations, oomFilteredPromise);
     } else {
-      PromisePrototypeCatch(oomFilteredPromise, () => {
-        // TODO(lucacasonato): emit an UncapturedErrorEvent
+      PromisePrototypeCatch(oomFilteredPromise, (err) => {
+        this.device.dispatchEvent(
+          new GPUUncapturedErrorEvent("uncapturederror", {
+            error: err,
+          }),
+        );
       });
     }
     // prevent uncaptured promise rejections
     PromisePrototypeCatch(oomFilteredPromise, (_err) => {});
+
+    const internalStack = ArrayPrototypeFilter(
+      this.errorScopeStack,
+      ({ filter }) => filter == "internal",
+    );
+    const internalScope = internalStack[internalStack.length - 1];
+    const internalFilteredPromise = PromisePrototypeCatch(operation, (err) => {
+      if (ObjectPrototypeIsPrototypeOf(GPUInternalErrorPrototype, err)) {
+        return PromiseReject(err);
+      }
+      return PromiseResolve();
+    });
+    if (internalScope) {
+      ArrayPrototypePush(internalScope.operations, internalFilteredPromise);
+    } else {
+      PromisePrototypeCatch(internalFilteredPromise, (err) => {
+        this.device.dispatchEvent(
+          new GPUUncapturedErrorEvent("uncapturederror", {
+            error: err,
+          }),
+        );
+      });
+    }
+    // prevent uncaptured promise rejections
+    PromisePrototypeCatch(internalFilteredPromise, (_err) => {});
   }
 }
 
@@ -971,7 +1151,7 @@ class GPUDevice extends EventTarget {
       "Argument 1",
     );
     const device = assertDevice(this, prefix, "this");
-    const { rid, err } = ops.op_webgpu_create_buffer(
+    const { rid, err } = op_webgpu_create_buffer(
       device.rid,
       descriptor.label,
       descriptor.size,
@@ -1022,7 +1202,7 @@ class GPUDevice extends EventTarget {
       "Argument 1",
     );
     const device = assertDevice(this, prefix, "this");
-    const { rid, err } = ops.op_webgpu_create_texture({
+    const { rid, err } = op_webgpu_create_texture({
       deviceRid: device.rid,
       ...descriptor,
       size: normalizeGPUExtent3D(descriptor.size),
@@ -1051,7 +1231,7 @@ class GPUDevice extends EventTarget {
       "Argument 1",
     );
     const device = assertDevice(this, prefix, "this");
-    const { rid, err } = ops.op_webgpu_create_sampler({
+    const { rid, err } = op_webgpu_create_sampler({
       deviceRid: device.rid,
       ...descriptor,
     });
@@ -1084,6 +1264,7 @@ class GPUDevice extends EventTarget {
       const entry = descriptor.entries[i];
 
       let j = 0;
+      // deno-lint-ignore prefer-primordials
       if (entry.buffer) j++;
       if (entry.sampler) j++;
       if (entry.texture) j++;
@@ -1094,7 +1275,7 @@ class GPUDevice extends EventTarget {
       }
     }
 
-    const { rid, err } = ops.op_webgpu_create_bind_group_layout(
+    const { rid, err } = op_webgpu_create_bind_group_layout(
       device.rid,
       descriptor.label,
       descriptor.entries,
@@ -1137,7 +1318,7 @@ class GPUDevice extends EventTarget {
         return rid;
       },
     );
-    const { rid, err } = ops.op_webgpu_create_pipeline_layout(
+    const { rid, err } = op_webgpu_create_pipeline_layout(
       device.rid,
       descriptor.label,
       bindGroupLayouts,
@@ -1204,7 +1385,9 @@ class GPUDevice extends EventTarget {
           resource: rid,
         };
       } else {
+        // deno-lint-ignore prefer-primordials
         const rid = assertResource(resource.buffer, prefix, context);
+        // deno-lint-ignore prefer-primordials
         assertDeviceMatch(device, resource.buffer, {
           prefix,
           resourceContext: context,
@@ -1220,7 +1403,7 @@ class GPUDevice extends EventTarget {
       }
     });
 
-    const { rid, err } = ops.op_webgpu_create_bind_group(
+    const { rid, err } = op_webgpu_create_bind_group(
       device.rid,
       descriptor.label,
       layout,
@@ -1250,7 +1433,7 @@ class GPUDevice extends EventTarget {
       "Argument 1",
     );
     const device = assertDevice(this, prefix, "this");
-    const { rid, err } = ops.op_webgpu_create_shader_module(
+    const { rid, err } = op_webgpu_create_shader_module(
       device.rid,
       descriptor.label,
       descriptor.code,
@@ -1301,7 +1484,7 @@ class GPUDevice extends EventTarget {
       selfContext: "this",
     });
 
-    const { rid, err } = ops.op_webgpu_create_compute_pipeline(
+    const { rid, err } = op_webgpu_create_compute_pipeline(
       device.rid,
       descriptor.label,
       layout,
@@ -1375,7 +1558,7 @@ class GPUDevice extends EventTarget {
       };
     }
 
-    const { rid, err } = ops.op_webgpu_create_render_pipeline({
+    const { rid, err } = op_webgpu_create_render_pipeline({
       deviceRid: device.rid,
       label: descriptor.label,
       layout,
@@ -1402,12 +1585,166 @@ class GPUDevice extends EventTarget {
 
   createComputePipelineAsync(descriptor) {
     // TODO(lucacasonato): this should be real async
-    return PromiseResolve(this.createComputePipeline(descriptor));
+
+    webidl.assertBranded(this, GPUDevicePrototype);
+    const prefix =
+      "Failed to execute 'createComputePipelineAsync' on 'GPUDevice'";
+    webidl.requiredArguments(arguments.length, 1, prefix);
+    descriptor = webidl.converters.GPUComputePipelineDescriptor(
+      descriptor,
+      prefix,
+      "Argument 1",
+    );
+    const device = assertDevice(this, prefix, "this");
+    let layout = descriptor.layout;
+    if (typeof descriptor.layout !== "string") {
+      const context = "layout";
+      layout = assertResource(descriptor.layout, prefix, context);
+      assertDeviceMatch(device, descriptor.layout, {
+        prefix,
+        resourceContext: context,
+        selfContext: "this",
+      });
+    }
+    const module = assertResource(
+      descriptor.compute.module,
+      prefix,
+      "compute shader module",
+    );
+    assertDeviceMatch(device, descriptor.compute.module, {
+      prefix,
+      resourceContext: "compute shader module",
+      selfContext: "this",
+    });
+
+    const { rid, err } = op_webgpu_create_compute_pipeline(
+      device.rid,
+      descriptor.label,
+      layout,
+      {
+        module,
+        entryPoint: descriptor.compute.entryPoint,
+        constants: descriptor.compute.constants,
+      },
+    );
+    device.pushError(err);
+    if (err) {
+      switch (err.type) {
+        case "validation":
+          return PromiseReject(
+            new GPUPipelineError(err.value ?? "validation error", {
+              reason: "validation",
+            }),
+          );
+        case "internal":
+          return PromiseReject(
+            new GPUPipelineError("internal error", {
+              reason: "validation",
+            }),
+          );
+      }
+    }
+
+    const computePipeline = createGPUComputePipeline(
+      descriptor.label,
+      device,
+      rid,
+    );
+    device.trackResource(computePipeline);
+    return PromiseResolve(computePipeline);
   }
 
   createRenderPipelineAsync(descriptor) {
     // TODO(lucacasonato): this should be real async
-    return PromiseResolve(this.createRenderPipeline(descriptor));
+
+    webidl.assertBranded(this, GPUDevicePrototype);
+    const prefix =
+      "Failed to execute 'createRenderPipelineAsync' on 'GPUDevice'";
+    webidl.requiredArguments(arguments.length, 1, prefix);
+    descriptor = webidl.converters.GPURenderPipelineDescriptor(
+      descriptor,
+      prefix,
+      "Argument 1",
+    );
+    const device = assertDevice(this, prefix, "this");
+    let layout = descriptor.layout;
+    if (typeof descriptor.layout !== "string") {
+      const context = "layout";
+      layout = assertResource(descriptor.layout, prefix, context);
+      assertDeviceMatch(device, descriptor.layout, {
+        prefix,
+        resourceContext: context,
+        selfContext: "this",
+      });
+    }
+    const module = assertResource(
+      descriptor.vertex.module,
+      prefix,
+      "vertex shader module",
+    );
+    assertDeviceMatch(device, descriptor.vertex.module, {
+      prefix,
+      resourceContext: "vertex shader module",
+      selfContext: "this",
+    });
+    let fragment = undefined;
+    if (descriptor.fragment) {
+      const module = assertResource(
+        descriptor.fragment.module,
+        prefix,
+        "fragment shader module",
+      );
+      assertDeviceMatch(device, descriptor.fragment.module, {
+        prefix,
+        resourceContext: "fragment shader module",
+        selfContext: "this",
+      });
+      fragment = {
+        module,
+        entryPoint: descriptor.fragment.entryPoint,
+        targets: descriptor.fragment.targets,
+      };
+    }
+
+    const { rid, err } = op_webgpu_create_render_pipeline({
+      deviceRid: device.rid,
+      label: descriptor.label,
+      layout,
+      vertex: {
+        module,
+        entryPoint: descriptor.vertex.entryPoint,
+        buffers: descriptor.vertex.buffers,
+      },
+      primitive: descriptor.primitive,
+      depthStencil: descriptor.depthStencil,
+      multisample: descriptor.multisample,
+      fragment,
+    });
+    device.pushError(err);
+    if (err) {
+      switch (err.type) {
+        case "validation":
+          return PromiseReject(
+            new GPUPipelineError(err.value ?? "validation error", {
+              reason: "validation",
+            }),
+          );
+        case "internal":
+          return PromiseReject(
+            new GPUPipelineError("internal error", {
+              reason: "validation",
+            }),
+          );
+      }
+    }
+
+    const renderPipeline = createGPURenderPipeline(
+      descriptor.label,
+      device,
+      rid,
+    );
+    device.trackResource(renderPipeline);
+    return renderPipeline;
   }
 
   /**
@@ -1423,7 +1760,7 @@ class GPUDevice extends EventTarget {
       "Argument 1",
     );
     const device = assertDevice(this, prefix, "this");
-    const { rid, err } = ops.op_webgpu_create_command_encoder(
+    const { rid, err } = op_webgpu_create_command_encoder(
       device.rid,
       descriptor.label,
     );
@@ -1453,7 +1790,7 @@ class GPUDevice extends EventTarget {
       "Argument 1",
     );
     const device = assertDevice(this, prefix, "this");
-    const { rid, err } = ops.op_webgpu_create_render_bundle_encoder({
+    const { rid, err } = op_webgpu_create_render_bundle_encoder({
       deviceRid: device.rid,
       ...descriptor,
     });
@@ -1482,7 +1819,7 @@ class GPUDevice extends EventTarget {
       "Argument 1",
     );
     const device = assertDevice(this, prefix, "this");
-    const { rid, err } = ops.op_webgpu_create_query_set({
+    const { rid, err } = op_webgpu_create_query_set({
       deviceRid: device.rid,
       ...descriptor,
     });
@@ -1570,22 +1907,49 @@ class GPUDevice extends EventTarget {
 GPUObjectBaseMixin("GPUDevice", GPUDevice);
 const GPUDevicePrototype = GPUDevice.prototype;
 
+class GPUPipelineError extends DOMException {
+  #reason;
+
+  constructor(message = "", options = {}) {
+    const prefix = "Failed to construct 'GPUPipelineError'";
+    message = webidl.converters.DOMString(message, prefix, "Argument 1");
+    options = webidl.converters.GPUPipelineErrorInit(
+      options,
+      prefix,
+      "Argument 2",
+    );
+    super(message, "GPUPipelineError");
+
+    this.#reason = options.reason;
+  }
+
+  get reason() {
+    webidl.assertBranded(this, GPUPipelineErrorPrototype);
+    return this.#reason;
+  }
+}
+const GPUPipelineErrorPrototype = GPUPipelineError.prototype;
+
 /**
  * @param {string | null} label
  * @param {InnerGPUDevice} device
+ * @param {number} rid
  * @returns {GPUQueue}
  */
-function createGPUQueue(label, device) {
+function createGPUQueue(label, device, rid) {
   /** @type {GPUQueue} */
   const queue = webidl.createBranded(GPUQueue);
   queue[_label] = label;
   queue[_device] = device;
+  queue[_rid] = rid;
   return queue;
 }
 
 class GPUQueue {
   /** @type {InnerGPUDevice} */
   [_device];
+  /** @type {number} */
+  [_rid];
 
   constructor() {
     webidl.illegalConstructor();
@@ -1619,7 +1983,7 @@ class GPUQueue {
         return rid;
       },
     );
-    const { err } = ops.op_webgpu_queue_submit(device.rid, commandBufferRids);
+    const { err } = op_webgpu_queue_submit(this[_rid], commandBufferRids);
     for (let i = 0; i < commandBuffers.length; ++i) {
       commandBuffers[i][_rid] = undefined;
     }
@@ -1664,13 +2028,23 @@ class GPUQueue {
       selfContext: "this",
       resourceContext: "Argument 1",
     });
-    const { err } = ops.op_webgpu_write_buffer(
-      device.rid,
+    /** @type {ArrayBufferLike} */
+    let abLike = data;
+    if (isTypedArray(data)) {
+      abLike = TypedArrayPrototypeGetBuffer(
+        /** @type {Uint8Array} */ (data),
+      );
+    } else if (isDataView(data)) {
+      abLike = DataViewPrototypeGetBuffer(/** @type {DataView} */ (data));
+    }
+
+    const { err } = op_webgpu_write_buffer(
+      this[_rid],
       bufferRid,
       bufferOffset,
       dataOffset,
       size,
-      new Uint8Array(ArrayBufferIsView(data) ? data.buffer : data),
+      new Uint8Array(abLike),
     );
     device.pushError(err);
   }
@@ -1704,8 +2078,19 @@ class GPUQueue {
       selfContext: "this",
       resourceContext: "texture",
     });
-    const { err } = ops.op_webgpu_write_texture(
-      device.rid,
+
+    /** @type {ArrayBufferLike} */
+    let abLike = data;
+    if (isTypedArray(data)) {
+      abLike = TypedArrayPrototypeGetBuffer(
+        /** @type {Uint8Array} */ (data),
+      );
+    } else if (isDataView(data)) {
+      abLike = DataViewPrototypeGetBuffer(/** @type {DataView} */ (data));
+    }
+
+    const { err } = op_webgpu_write_texture(
+      this[_rid],
       {
         texture: textureRid,
         mipLevel: destination.mipLevel,
@@ -1716,7 +2101,7 @@ class GPUQueue {
       },
       dataLayout,
       normalizeGPUExtent3D(size),
-      new Uint8Array(ArrayBufferIsView(data) ? data.buffer : data),
+      new Uint8Array(abLike),
     );
     device.pushError(err);
   }
@@ -1900,8 +2285,7 @@ class GPUBuffer {
     this[_mapMode] = mode;
     this[_state] = "pending";
     const promise = PromisePrototypeThen(
-      core.opAsync(
-        "op_webgpu_buffer_get_map_async",
+      op_webgpu_buffer_get_map_async(
         bufferRid,
         device.rid,
         mode,
@@ -1947,9 +2331,9 @@ class GPUBuffer {
       throw new DOMException(`${prefix}: invalid state.`, "OperationError");
     }
     for (let i = 0; i < mappedRanges.length; ++i) {
-      const [buffer, _rid, start] = mappedRanges[i];
+      const { 0: buffer, 1: _rid, 2: start } = mappedRanges[i];
       // TODO(lucacasonato): is this logic correct?
-      const end = start + buffer.byteLength;
+      const end = start + ArrayBufferPrototypeGetByteLength(buffer);
       if (
         (start >= offset && start < (offset + rangeSize)) ||
         (end >= offset && end < (offset + rangeSize))
@@ -1962,7 +2346,7 @@ class GPUBuffer {
     }
 
     const buffer = new ArrayBuffer(rangeSize);
-    const { rid } = ops.op_webgpu_buffer_get_mapped_range(
+    const { rid } = op_webgpu_buffer_get_mapped_range(
       bufferRid,
       offset,
       size,
@@ -2016,8 +2400,8 @@ class GPUBuffer {
         throw new DOMException(`${prefix}: invalid state.`, "OperationError");
       }
       for (let i = 0; i < mappedRanges.length; ++i) {
-        const [buffer, mappedRid] = mappedRanges[i];
-        const { err } = ops.op_webgpu_buffer_unmap(
+        const { 0: buffer, 1: mappedRid } = mappedRanges[i];
+        const { err } = op_webgpu_buffer_unmap(
           bufferRid,
           mappedRid,
           ...new SafeArrayIterator(write ? [new Uint8Array(buffer)] : []),
@@ -2135,7 +2519,7 @@ class GPUTexture {
   [_device];
   /** @type {number | undefined} */
   [_rid];
-  /** @type {WeakRef<GPUTextureView>[]} */
+  /** @type {SafeWeakRef<GPUTextureView>[]} */
   [_views];
 
   /** @type {number} */
@@ -2189,7 +2573,7 @@ class GPUTexture {
     );
     const device = assertDevice(this, prefix, "this");
     const textureRid = assertResource(this, prefix, "this");
-    const { rid, err } = ops.op_webgpu_create_texture_view({
+    const { rid, err } = op_webgpu_create_texture_view({
       textureRid,
       ...descriptor,
     });
@@ -2200,7 +2584,7 @@ class GPUTexture {
       this,
       rid,
     );
-    ArrayPrototypePush(this[_views], new WeakRef(textureView));
+    ArrayPrototypePush(this[_views], new SafeWeakRef(textureView));
     return textureView;
   }
 
@@ -2423,7 +2807,10 @@ class GPUBindGroupLayout {
     return inspect(
       createFilteredInspectProxy({
         object: this,
-        evaluate: ObjectPrototypeIsPrototypeOf(GPUBindGroupLayoutPrototype, this),
+        evaluate: ObjectPrototypeIsPrototypeOf(
+          GPUBindGroupLayoutPrototype,
+          this,
+        ),
         keys: [
           "label",
         ],
@@ -2471,7 +2858,10 @@ class GPUPipelineLayout {
     return inspect(
       createFilteredInspectProxy({
         object: this,
-        evaluate: ObjectPrototypeIsPrototypeOf(GPUPipelineLayoutPrototype, this),
+        evaluate: ObjectPrototypeIsPrototypeOf(
+          GPUPipelineLayoutPrototype,
+          this,
+        ),
         keys: [
           "label",
         ],
@@ -2483,7 +2873,6 @@ class GPUPipelineLayout {
 GPUObjectBaseMixin("GPUPipelineLayout", GPUPipelineLayout);
 const GPUPipelineLayoutPrototype = GPUPipelineLayout.prototype;
 
-
 /**
  * @param {string | null} label
  * @param {InnerGPUDevice} device
@@ -2643,8 +3032,8 @@ class GPUComputePipeline {
     index = webidl.converters["unsigned long"](index, prefix, "Argument 1");
     const device = assertDevice(this, prefix, "this");
     const computePipelineRid = assertResource(this, prefix, "this");
-    const { rid, label, err } = ops
-      .op_webgpu_compute_pipeline_get_bind_group_layout(
+    const { rid, label, err } =
+      op_webgpu_compute_pipeline_get_bind_group_layout(
         computePipelineRid,
         index,
       );
@@ -2663,7 +3052,10 @@ class GPUComputePipeline {
     return inspect(
       createFilteredInspectProxy({
         object: this,
-        evaluate: ObjectPrototypeIsPrototypeOf(GPUComputePipelinePrototype, this),
+        evaluate: ObjectPrototypeIsPrototypeOf(
+          GPUComputePipelinePrototype,
+          this,
+        ),
         keys: [
           "label",
         ],
@@ -2719,11 +3111,10 @@ class GPURenderPipeline {
     index = webidl.converters["unsigned long"](index, prefix, "Argument 1");
     const device = assertDevice(this, prefix, "this");
     const renderPipelineRid = assertResource(this, prefix, "this");
-    const { rid, label, err } = ops
-      .op_webgpu_render_pipeline_get_bind_group_layout(
-        renderPipelineRid,
-        index,
-      );
+    const { rid, label, err } = op_webgpu_render_pipeline_get_bind_group_layout(
+      renderPipelineRid,
+      index,
+    );
     device.pushError(err);
 
     const bindGroupLayout = createGPUBindGroupLayout(
@@ -2739,7 +3130,10 @@ class GPURenderPipeline {
     return inspect(
       createFilteredInspectProxy({
         object: this,
-        evaluate: ObjectPrototypeIsPrototypeOf(GPURenderPipelinePrototype, this),
+        evaluate: ObjectPrototypeIsPrototypeOf(
+          GPURenderPipelinePrototype,
+          this,
+        ),
         keys: [
           "label",
         ],
@@ -2793,7 +3187,7 @@ class GPUCommandEncoder {
   [_device];
   /** @type {number | undefined} */
   [_rid];
-  /** @type {WeakRef<GPURenderPassEncoder | GPUComputePassEncoder>[]} */
+  /** @type {SafeWeakRef<GPURenderPassEncoder | GPUComputePassEncoder>[]} */
   [_encoders];
 
   [_cleanup]() {
@@ -2843,7 +3237,7 @@ class GPUCommandEncoder {
     if (descriptor.depthStencilAttachment) {
       if (
         descriptor.depthStencilAttachment.depthLoadOp === "clear" &&
-        !("depthClearValue" in descriptor.depthStencilAttachment)
+        !(ObjectHasOwn(descriptor.depthStencilAttachment, "depthClearValue"))
       ) {
         throw webidl.makeException(
           TypeError,
@@ -2903,7 +3297,11 @@ class GPUCommandEncoder {
             prefix,
             `resolve target texture view for ${context}`,
           );
-          assertResource(colorAttachment.resolveTarget[_texture], prefix, `texture backing resolve target texture view for ${context}`);
+          assertResource(
+            colorAttachment.resolveTarget[_texture],
+            prefix,
+            `texture backing resolve target texture view for ${context}`,
+          );
           assertDeviceMatch(
             device,
             colorAttachment.resolveTarget[_texture],
@@ -2936,16 +3334,21 @@ class GPUCommandEncoder {
 
     let timestampWrites = null;
     if (descriptor.timestampWrites) {
-      const querySet = assertResource(descriptor.timestampWrites.querySet, prefix, "querySet");
+      const querySet = assertResource(
+        descriptor.timestampWrites.querySet,
+        prefix,
+        "querySet",
+      );
 
       timestampWrites = {
         querySet,
-        beginningOfPassWriteIndex: descriptor.timestampWrites.beginningOfPassWriteIndex,
+        beginningOfPassWriteIndex:
+        descriptor.timestampWrites.beginningOfPassWriteIndex,
         endOfPassWriteIndex: descriptor.timestampWrites.endOfPassWriteIndex,
       };
     }
 
-    const { rid } = ops.op_webgpu_command_encoder_begin_render_pass(
+    const { rid } = op_webgpu_command_encoder_begin_render_pass(
       commandEncoderRid,
       descriptor.label,
       colorAttachments,
@@ -2959,7 +3362,7 @@ class GPUCommandEncoder {
       this,
       rid,
     );
-    ArrayPrototypePush(this[_encoders], new WeakRef(renderPassEncoder));
+    ArrayPrototypePush(this[_encoders], new SafeWeakRef(renderPassEncoder));
     return renderPassEncoder;
   }
 
@@ -2981,16 +3384,21 @@ class GPUCommandEncoder {
 
     let timestampWrites = null;
     if (descriptor.timestampWrites) {
-      const querySet = assertResource(descriptor.timestampWrites.querySet, prefix, "querySet");
+      const querySet = assertResource(
+        descriptor.timestampWrites.querySet,
+        prefix,
+        "querySet",
+      );
 
       timestampWrites = {
         querySet,
-        beginningOfPassWriteIndex: descriptor.timestampWrites.beginningOfPassWriteIndex,
+        beginningOfPassWriteIndex:
+        descriptor.timestampWrites.beginningOfPassWriteIndex,
         endOfPassWriteIndex: descriptor.timestampWrites.endOfPassWriteIndex,
       };
     }
 
-    const { rid } = ops.op_webgpu_command_encoder_begin_compute_pass(
+    const { rid } = op_webgpu_command_encoder_begin_compute_pass(
       commandEncoderRid,
       descriptor.label,
       timestampWrites,
@@ -3001,7 +3409,7 @@ class GPUCommandEncoder {
       this,
       rid,
     );
-    ArrayPrototypePush(this[_encoders], new WeakRef(computePassEncoder));
+    ArrayPrototypePush(this[_encoders], new SafeWeakRef(computePassEncoder));
     return computePassEncoder;
   }
 
@@ -3055,7 +3463,7 @@ class GPUCommandEncoder {
       selfContext: "this",
     });
 
-    const { err } = ops.op_webgpu_command_encoder_copy_buffer_to_buffer(
+    const { err } = op_webgpu_command_encoder_copy_buffer_to_buffer(
       commandEncoderRid,
       sourceRid,
       sourceOffset,
@@ -3086,10 +3494,12 @@ class GPUCommandEncoder {
     const device = assertDevice(this, prefix, "this");
     const commandEncoderRid = assertResource(this, prefix, "this");
     const sourceBufferRid = assertResource(
+      // deno-lint-ignore prefer-primordials
       source.buffer,
       prefix,
       "source in Argument 1",
     );
+    // deno-lint-ignore prefer-primordials
     assertDeviceMatch(device, source.buffer, {
       prefix,
       resourceContext: "source in Argument 1",
@@ -3106,7 +3516,7 @@ class GPUCommandEncoder {
       selfContext: "this",
     });
 
-    const { err } = ops.op_webgpu_command_encoder_copy_buffer_to_texture(
+    const { err } = op_webgpu_command_encoder_copy_buffer_to_texture(
       commandEncoderRid,
       {
         ...source,
@@ -3159,16 +3569,18 @@ class GPUCommandEncoder {
       selfContext: "this",
     });
     const destinationBufferRid = assertResource(
+      // deno-lint-ignore prefer-primordials
       destination.buffer,
       prefix,
       "buffer in Argument 2",
     );
+    // deno-lint-ignore prefer-primordials
     assertDeviceMatch(device, destination.buffer, {
       prefix,
       resourceContext: "buffer in Argument 2",
       selfContext: "this",
     });
-    const { err } = ops.op_webgpu_command_encoder_copy_texture_to_buffer(
+    const { err } = op_webgpu_command_encoder_copy_texture_to_buffer(
       commandEncoderRid,
       {
         texture: sourceTextureRid,
@@ -3228,7 +3640,7 @@ class GPUCommandEncoder {
       resourceContext: "texture in Argument 2",
       selfContext: "this",
     });
-    const { err } = ops.op_webgpu_command_encoder_copy_texture_to_texture(
+    const { err } = op_webgpu_command_encoder_copy_texture_to_texture(
       commandEncoderRid,
       {
         texture: sourceTextureRid,
@@ -3264,7 +3676,7 @@ class GPUCommandEncoder {
     const device = assertDevice(this, prefix, "this");
     const commandEncoderRid = assertResource(this, prefix, "this");
     const bufferRid = assertResource(buffer, prefix, "Argument 1");
-    const { err } = ops.op_webgpu_command_encoder_clear_buffer(
+    const { err } = op_webgpu_command_encoder_clear_buffer(
       commandEncoderRid,
       bufferRid,
       offset,
@@ -3283,7 +3695,7 @@ class GPUCommandEncoder {
     groupLabel = webidl.converters.USVString(groupLabel, prefix, "Argument 1");
     const device = assertDevice(this, prefix, "this");
     const commandEncoderRid = assertResource(this, prefix, "this");
-    const { err } = ops.op_webgpu_command_encoder_push_debug_group(
+    const { err } = op_webgpu_command_encoder_push_debug_group(
       commandEncoderRid,
       groupLabel,
     );
@@ -3295,7 +3707,7 @@ class GPUCommandEncoder {
     const prefix = "Failed to execute 'popDebugGroup' on 'GPUCommandEncoder'";
     const device = assertDevice(this, prefix, "this");
     const commandEncoderRid = assertResource(this, prefix, "this");
-    const { err } = ops.op_webgpu_command_encoder_pop_debug_group(
+    const { err } = op_webgpu_command_encoder_pop_debug_group(
       commandEncoderRid,
     );
     device.pushError(err);
@@ -3316,7 +3728,7 @@ class GPUCommandEncoder {
     );
     const device = assertDevice(this, prefix, "this");
     const commandEncoderRid = assertResource(this, prefix, "this");
-    const { err } = ops.op_webgpu_command_encoder_insert_debug_marker(
+    const { err } = op_webgpu_command_encoder_insert_debug_marker(
       commandEncoderRid,
       markerLabel,
     );
@@ -3341,7 +3753,7 @@ class GPUCommandEncoder {
       resourceContext: "Argument 1",
       selfContext: "this",
     });
-    const { err } = ops.op_webgpu_command_encoder_write_timestamp(
+    const { err } = op_webgpu_command_encoder_write_timestamp(
       commandEncoderRid,
       querySetRid,
       queryIndex,
@@ -3393,7 +3805,7 @@ class GPUCommandEncoder {
       resourceContext: "Argument 3",
       selfContext: "this",
     });
-    const { err } = ops.op_webgpu_command_encoder_resolve_query_set(
+    const { err } = op_webgpu_command_encoder_resolve_query_set(
       commandEncoderRid,
       querySetRid,
       firstQuery,
@@ -3418,7 +3830,7 @@ class GPUCommandEncoder {
     );
     const device = assertDevice(this, prefix, "this");
     const commandEncoderRid = assertResource(this, prefix, "this");
-    const { rid, err } = ops.op_webgpu_command_encoder_finish(
+    const { rid, err } = op_webgpu_command_encoder_finish(
       commandEncoderRid,
       descriptor.label,
     );
@@ -3439,7 +3851,10 @@ class GPUCommandEncoder {
     return inspect(
       createFilteredInspectProxy({
         object: this,
-        evaluate: ObjectPrototypeIsPrototypeOf(GPUCommandEncoderPrototype, this),
+        evaluate: ObjectPrototypeIsPrototypeOf(
+          GPUCommandEncoderPrototype,
+          this,
+        ),
         keys: [
           "label",
         ],
@@ -3506,7 +3921,7 @@ class GPURenderPassEncoder {
     assertDevice(this[_encoder], prefix, "encoder referenced by this");
     assertResource(this[_encoder], prefix, "encoder referenced by this");
     const renderPassRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_render_pass_set_viewport({
+    op_webgpu_render_pass_set_viewport({
       renderPassRid,
       x,
       y,
@@ -3539,7 +3954,7 @@ class GPURenderPassEncoder {
     assertDevice(this[_encoder], prefix, "encoder referenced by this");
     assertResource(this[_encoder], prefix, "encoder referenced by this");
     const renderPassRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_render_pass_set_scissor_rect(
+    op_webgpu_render_pass_set_scissor_rect(
       renderPassRid,
       x,
       y,
@@ -3560,7 +3975,7 @@ class GPURenderPassEncoder {
     assertDevice(this[_encoder], prefix, "encoder referenced by this");
     assertResource(this[_encoder], prefix, "encoder referenced by this");
     const renderPassRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_render_pass_set_blend_constant(
+    op_webgpu_render_pass_set_blend_constant(
       renderPassRid,
       normalizeGPUColor(color),
     );
@@ -3582,7 +3997,7 @@ class GPURenderPassEncoder {
     assertDevice(this[_encoder], prefix, "encoder referenced by this");
     assertResource(this[_encoder], prefix, "encoder referenced by this");
     const renderPassRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_render_pass_set_stencil_reference(
+    op_webgpu_render_pass_set_stencil_reference(
       renderPassRid,
       reference,
     );
@@ -3600,7 +4015,7 @@ class GPURenderPassEncoder {
     assertDevice(this[_encoder], prefix, "encoder referenced by this");
     assertResource(this[_encoder], prefix, "encoder referenced by this");
     const renderPassRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_render_pass_begin_occlusion_query(
+    op_webgpu_render_pass_begin_occlusion_query(
       renderPassRid,
       queryIndex,
     );
@@ -3613,7 +4028,7 @@ class GPURenderPassEncoder {
     assertDevice(this[_encoder], prefix, "encoder referenced by this");
     assertResource(this[_encoder], prefix, "encoder referenced by this");
     const renderPassRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_render_pass_end_occlusion_query(renderPassRid);
+    op_webgpu_render_pass_end_occlusion_query(renderPassRid);
   }
 
   /**
@@ -3646,7 +4061,7 @@ class GPURenderPassEncoder {
       });
       return rid;
     });
-    ops.op_webgpu_render_pass_execute_bundles(renderPassRid, bundleRids);
+    op_webgpu_render_pass_execute_bundles(renderPassRid, bundleRids);
   }
 
   end() {
@@ -3663,7 +4078,7 @@ class GPURenderPassEncoder {
       "encoder referenced by this",
     );
     const renderPassRid = assertResource(this, prefix, "this");
-    const { err } = ops.op_webgpu_render_pass_end(
+    const { err } = op_webgpu_render_pass_end(
       commandEncoderRid,
       renderPassRid,
     );
@@ -3695,16 +4110,14 @@ class GPURenderPassEncoder {
       selfContext: "this",
     });
     if (
-      !(ObjectPrototypeIsPrototypeOf(
-        Uint32ArrayPrototype,
-        dynamicOffsetsData,
-      ))
+      TypedArrayPrototypeGetSymbolToStringTag(dynamicOffsetsData) !==
+      "Uint32Array"
     ) {
       dynamicOffsetsData = new Uint32Array(dynamicOffsetsData ?? []);
       dynamicOffsetsDataStart = 0;
       dynamicOffsetsDataLength = dynamicOffsetsData.length;
     }
-    ops.op_webgpu_render_pass_set_bind_group(
+    op_webgpu_render_pass_set_bind_group(
       renderPassRid,
       index,
       bindGroupRid,
@@ -3726,7 +4139,7 @@ class GPURenderPassEncoder {
     assertDevice(this[_encoder], prefix, "encoder referenced by this");
     assertResource(this[_encoder], prefix, "encoder referenced by this");
     const renderPassRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_render_pass_push_debug_group(renderPassRid, groupLabel);
+    op_webgpu_render_pass_push_debug_group(renderPassRid, groupLabel);
   }
 
   popDebugGroup() {
@@ -3736,7 +4149,7 @@ class GPURenderPassEncoder {
     assertDevice(this[_encoder], prefix, "encoder referenced by this");
     assertResource(this[_encoder], prefix, "encoder referenced by this");
     const renderPassRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_render_pass_pop_debug_group(renderPassRid);
+    op_webgpu_render_pass_pop_debug_group(renderPassRid);
   }
 
   /**
@@ -3755,7 +4168,7 @@ class GPURenderPassEncoder {
     assertDevice(this[_encoder], prefix, "encoder referenced by this");
     assertResource(this[_encoder], prefix, "encoder referenced by this");
     const renderPassRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_render_pass_insert_debug_marker(renderPassRid, markerLabel);
+    op_webgpu_render_pass_insert_debug_marker(renderPassRid, markerLabel);
   }
 
   /**
@@ -3783,7 +4196,7 @@ class GPURenderPassEncoder {
       resourceContext: "Argument 1",
       selfContext: "this",
     });
-    ops.op_webgpu_render_pass_set_pipeline(renderPassRid, pipelineRid);
+    op_webgpu_render_pass_set_pipeline(renderPassRid, pipelineRid);
   }
 
   /**
@@ -3820,7 +4233,7 @@ class GPURenderPassEncoder {
       resourceContext: "Argument 1",
       selfContext: "this",
     });
-    ops.op_webgpu_render_pass_set_index_buffer(
+    op_webgpu_render_pass_set_index_buffer(
       renderPassRid,
       bufferRid,
       indexFormat,
@@ -3859,7 +4272,7 @@ class GPURenderPassEncoder {
       resourceContext: "Argument 2",
       selfContext: "this",
     });
-    ops.op_webgpu_render_pass_set_vertex_buffer(
+    op_webgpu_render_pass_set_vertex_buffer(
       renderPassRid,
       slot,
       bufferRid,
@@ -3901,7 +4314,7 @@ class GPURenderPassEncoder {
     assertDevice(this[_encoder], prefix, "encoder referenced by this");
     assertResource(this[_encoder], prefix, "encoder referenced by this");
     const renderPassRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_render_pass_draw(
+    op_webgpu_render_pass_draw(
       renderPassRid,
       vertexCount,
       instanceCount,
@@ -3947,7 +4360,7 @@ class GPURenderPassEncoder {
     assertDevice(this[_encoder], prefix, "encoder referenced by this");
     assertResource(this[_encoder], prefix, "encoder referenced by this");
     const renderPassRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_render_pass_draw_indexed(
+    op_webgpu_render_pass_draw_indexed(
       renderPassRid,
       indexCount,
       instanceCount,
@@ -3992,7 +4405,7 @@ class GPURenderPassEncoder {
       resourceContext: "Argument 1",
       selfContext: "this",
     });
-    ops.op_webgpu_render_pass_draw_indirect(
+    op_webgpu_render_pass_draw_indirect(
       renderPassRid,
       indirectBufferRid,
       indirectOffset,
@@ -4035,7 +4448,7 @@ class GPURenderPassEncoder {
       resourceContext: "Argument 1",
       selfContext: "this",
     });
-    ops.op_webgpu_render_pass_draw_indexed_indirect(
+    op_webgpu_render_pass_draw_indexed_indirect(
       renderPassRid,
       indirectBufferRid,
       indirectOffset,
@@ -4046,7 +4459,10 @@ class GPURenderPassEncoder {
     return inspect(
       createFilteredInspectProxy({
         object: this,
-        evaluate: ObjectPrototypeIsPrototypeOf(GPURenderPassEncoderPrototype, this),
+        evaluate: ObjectPrototypeIsPrototypeOf(
+          GPURenderPassEncoderPrototype,
+          this,
+        ),
         keys: [
           "label",
         ],
@@ -4118,7 +4534,7 @@ class GPUComputePassEncoder {
       resourceContext: "Argument 1",
       selfContext: "this",
     });
-    ops.op_webgpu_compute_pass_set_pipeline(computePassRid, pipelineRid);
+    op_webgpu_compute_pass_set_pipeline(computePassRid, pipelineRid);
   }
 
   /**
@@ -4153,7 +4569,7 @@ class GPUComputePassEncoder {
     assertDevice(this[_encoder], prefix, "encoder referenced by this");
     assertResource(this[_encoder], prefix, "encoder referenced by this");
     const computePassRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_compute_pass_dispatch_workgroups(
+    op_webgpu_compute_pass_dispatch_workgroups(
       computePassRid,
       workgroupCountX,
       workgroupCountY,
@@ -4197,7 +4613,7 @@ class GPUComputePassEncoder {
       resourceContext: "Argument 1",
       selfContext: "this",
     });
-    ops.op_webgpu_compute_pass_dispatch_workgroups_indirect(
+    op_webgpu_compute_pass_dispatch_workgroups_indirect(
       computePassRid,
       indirectBufferRid,
       indirectOffset,
@@ -4218,7 +4634,7 @@ class GPUComputePassEncoder {
       "encoder referenced by this",
     );
     const computePassRid = assertResource(this, prefix, "this");
-    const { err } = ops.op_webgpu_compute_pass_end(
+    const { err } = op_webgpu_compute_pass_end(
       commandEncoderRid,
       computePassRid,
     );
@@ -4251,16 +4667,14 @@ class GPUComputePassEncoder {
       selfContext: "this",
     });
     if (
-      !(ObjectPrototypeIsPrototypeOf(
-        Uint32ArrayPrototype,
-        dynamicOffsetsData,
-      ))
+      TypedArrayPrototypeGetSymbolToStringTag(dynamicOffsetsData) !==
+      "Uint32Array"
     ) {
       dynamicOffsetsData = new Uint32Array(dynamicOffsetsData ?? []);
       dynamicOffsetsDataStart = 0;
       dynamicOffsetsDataLength = dynamicOffsetsData.length;
     }
-    ops.op_webgpu_compute_pass_set_bind_group(
+    op_webgpu_compute_pass_set_bind_group(
       computePassRid,
       index,
       bindGroupRid,
@@ -4282,7 +4696,7 @@ class GPUComputePassEncoder {
     assertDevice(this[_encoder], prefix, "encoder referenced by this");
     assertResource(this[_encoder], prefix, "encoder referenced by this");
     const computePassRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_compute_pass_push_debug_group(computePassRid, groupLabel);
+    op_webgpu_compute_pass_push_debug_group(computePassRid, groupLabel);
   }
 
   popDebugGroup() {
@@ -4292,7 +4706,7 @@ class GPUComputePassEncoder {
     assertDevice(this[_encoder], prefix, "encoder referenced by this");
     assertResource(this[_encoder], prefix, "encoder referenced by this");
     const computePassRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_compute_pass_pop_debug_group(computePassRid);
+    op_webgpu_compute_pass_pop_debug_group(computePassRid);
   }
 
   /**
@@ -4311,7 +4725,7 @@ class GPUComputePassEncoder {
     assertDevice(this[_encoder], prefix, "encoder referenced by this");
     assertResource(this[_encoder], prefix, "encoder referenced by this");
     const computePassRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_compute_pass_insert_debug_marker(
+    op_webgpu_compute_pass_insert_debug_marker(
       computePassRid,
       markerLabel,
     );
@@ -4321,7 +4735,10 @@ class GPUComputePassEncoder {
     return inspect(
       createFilteredInspectProxy({
         object: this,
-        evaluate: ObjectPrototypeIsPrototypeOf(GPUComputePassEncoderPrototype, this),
+        evaluate: ObjectPrototypeIsPrototypeOf(
+          GPUComputePassEncoderPrototype,
+          this,
+        ),
         keys: [
           "label",
         ],
@@ -4430,7 +4847,7 @@ class GPURenderBundleEncoder {
     );
     const device = assertDevice(this, prefix, "this");
     const renderBundleEncoderRid = assertResource(this, prefix, "this");
-    const { rid, err } = ops.op_webgpu_render_bundle_encoder_finish(
+    const { rid, err } = op_webgpu_render_bundle_encoder_finish(
       renderBundleEncoderRid,
       descriptor.label,
     );
@@ -4466,16 +4883,14 @@ class GPURenderBundleEncoder {
       selfContext: "this",
     });
     if (
-      !(ObjectPrototypeIsPrototypeOf(
-        Uint32ArrayPrototype,
-        dynamicOffsetsData,
-      ))
+      TypedArrayPrototypeGetSymbolToStringTag(dynamicOffsetsData) !==
+      "Uint32Array"
     ) {
       dynamicOffsetsData = new Uint32Array(dynamicOffsetsData ?? []);
       dynamicOffsetsDataStart = 0;
       dynamicOffsetsDataLength = dynamicOffsetsData.length;
     }
-    ops.op_webgpu_render_bundle_encoder_set_bind_group(
+    op_webgpu_render_bundle_encoder_set_bind_group(
       renderBundleEncoderRid,
       index,
       bindGroupRid,
@@ -4496,7 +4911,7 @@ class GPURenderBundleEncoder {
     groupLabel = webidl.converters.USVString(groupLabel, prefix, "Argument 1");
     assertDevice(this, prefix, "this");
     const renderBundleEncoderRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_render_bundle_encoder_push_debug_group(
+    op_webgpu_render_bundle_encoder_push_debug_group(
       renderBundleEncoderRid,
       groupLabel,
     );
@@ -4508,7 +4923,7 @@ class GPURenderBundleEncoder {
       "Failed to execute 'popDebugGroup' on 'GPURenderBundleEncoder'";
     assertDevice(this, prefix, "this");
     const renderBundleEncoderRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_render_bundle_encoder_pop_debug_group(
+    op_webgpu_render_bundle_encoder_pop_debug_group(
       renderBundleEncoderRid,
     );
   }
@@ -4528,7 +4943,7 @@ class GPURenderBundleEncoder {
     );
     assertDevice(this, prefix, "this");
     const renderBundleEncoderRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_render_bundle_encoder_insert_debug_marker(
+    op_webgpu_render_bundle_encoder_insert_debug_marker(
       renderBundleEncoderRid,
       markerLabel,
     );
@@ -4555,7 +4970,7 @@ class GPURenderBundleEncoder {
       resourceContext: "Argument 1",
       selfContext: "this",
     });
-    ops.op_webgpu_render_bundle_encoder_set_pipeline(
+    op_webgpu_render_bundle_encoder_set_pipeline(
       renderBundleEncoderRid,
       pipelineRid,
     );
@@ -4588,7 +5003,7 @@ class GPURenderBundleEncoder {
       resourceContext: "Argument 1",
       selfContext: "this",
     });
-    ops.op_webgpu_render_bundle_encoder_set_index_buffer(
+    op_webgpu_render_bundle_encoder_set_index_buffer(
       renderBundleEncoderRid,
       bufferRid,
       indexFormat,
@@ -4622,7 +5037,7 @@ class GPURenderBundleEncoder {
       resourceContext: "Argument 2",
       selfContext: "this",
     });
-    ops.op_webgpu_render_bundle_encoder_set_vertex_buffer(
+    op_webgpu_render_bundle_encoder_set_vertex_buffer(
       renderBundleEncoderRid,
       slot,
       bufferRid,
@@ -4663,7 +5078,7 @@ class GPURenderBundleEncoder {
     );
     assertDevice(this, prefix, "this");
     const renderBundleEncoderRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_render_bundle_encoder_draw(
+    op_webgpu_render_bundle_encoder_draw(
       renderBundleEncoderRid,
       vertexCount,
       instanceCount,
@@ -4709,7 +5124,7 @@ class GPURenderBundleEncoder {
     );
     assertDevice(this, prefix, "this");
     const renderBundleEncoderRid = assertResource(this, prefix, "this");
-    ops.op_webgpu_render_bundle_encoder_draw_indexed(
+    op_webgpu_render_bundle_encoder_draw_indexed(
       renderBundleEncoderRid,
       indexCount,
       instanceCount,
@@ -4750,7 +5165,7 @@ class GPURenderBundleEncoder {
       resourceContext: "Argument 1",
       selfContext: "this",
     });
-    ops.op_webgpu_render_bundle_encoder_draw_indirect(
+    op_webgpu_render_bundle_encoder_draw_indirect(
       renderBundleEncoderRid,
       indirectBufferRid,
       indirectOffset,
@@ -4761,7 +5176,10 @@ class GPURenderBundleEncoder {
     return inspect(
       createFilteredInspectProxy({
         object: this,
-        evaluate: ObjectPrototypeIsPrototypeOf(GPURenderBundleEncoderPrototype, this),
+        evaluate: ObjectPrototypeIsPrototypeOf(
+          GPURenderBundleEncoderPrototype,
+          this,
+        ),
         keys: [
           "label",
         ],
@@ -4870,12 +5288,12 @@ class GPUQuerySet {
 
   get type() {
     webidl.assertBranded(this, GPUQuerySetPrototype);
-    this[_type]();
+    return this[_type]();
   }
 
   get count() {
     webidl.assertBranded(this, GPUQuerySetPrototype);
-    this[_count]();
+    return this[_count]();
   }
 
   [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
@@ -5014,6 +5432,27 @@ webidl.converters["GPUFeatureName"] = webidl.createEnumConverter(
   ],
 );
 
+// DICTIONARY: GPUPipelineErrorInit
+webidl.converters["GPUPipelineErrorInit"] = webidl.createDictionaryConverter(
+  "GPUPipelineErrorInit",
+  [
+    {
+      key: "reason",
+      converter: webidl.converters.GPUPipelineErrorReason,
+      required: true,
+    },
+  ],
+);
+
+// ENUM: GPUPipelineErrorReason
+webidl.converters["GPUPipelineErrorReason"] = webidl.createEnumConverter(
+  "GPUPipelineErrorReason",
+  [
+    "validation",
+    "internal",
+  ],
+);
+
 // TYPEDEF: GPUSize32
 webidl.converters["GPUSize32"] = (V, opts) =>
   webidl.converters["unsigned long"](V, { ...opts, enforceRange: true });
@@ -5193,6 +5632,7 @@ webidl.converters["GPUTextureFormat"] = webidl.createEnumConverter(
     "bgra8unorm",
     "bgra8unorm-srgb",
     "rgb9e5ufloat",
+    "rgb10a2uint",
     "rgb10a2unorm",
     "rg11b10ufloat",
     "rg32uint",
@@ -5608,6 +6048,8 @@ webidl.converters["GPUStorageTextureAccess"] = webidl.createEnumConverter(
   "GPUStorageTextureAccess",
   [
     "write-only",
+    "read-only",
+    "read-write",
   ],
 );
 
@@ -5867,7 +6309,6 @@ const dictMembersGPUProgrammableStage = [
   {
     key: "entryPoint",
     converter: webidl.converters["USVString"],
-    required: true,
   },
   {
     key: "constants",
@@ -6910,7 +7351,7 @@ webidl.converters["GPUErrorFilter"] = webidl.createEnumConverter(
   [
     "out-of-memory",
     "validation",
-    "internal"
+    "internal",
   ],
 );
 
@@ -6957,6 +7398,78 @@ webidl.converters["GPUSignedOffset32"] = (V, opts) =>
 // TYPEDEF: GPUFlagsConstant
 webidl.converters["GPUFlagsConstant"] = webidl.converters["unsigned long"];
 
+// ENUM: GPUCanvasAlphaMode
+webidl.converters["GPUCanvasAlphaMode"] = webidl.createEnumConverter(
+  "GPUCanvasAlphaMode",
+  [
+    "opaque",
+    "premultiplied",
+  ],
+);
+
+// NON-SPEC: ENUM: GPUPresentMode
+webidl.converters["GPUPresentMode"] = webidl.createEnumConverter(
+  "GPUPresentMode",
+  [
+    "autoVsync",
+    "autoNoVsync",
+    "fifo",
+    "fifoRelaxed",
+    "immediate",
+    "mailbox",
+  ],
+);
+
+// DICT: GPUCanvasConfiguration
+const dictMembersGPUCanvasConfiguration = [
+  { key: "device", converter: webidl.converters.GPUDevice, required: true },
+  {
+    key: "format",
+    converter: webidl.converters.GPUTextureFormat,
+    required: true,
+  },
+  {
+    key: "usage",
+    converter: webidl.converters["GPUTextureUsageFlags"],
+    defaultValue: GPUTextureUsage.RENDER_ATTACHMENT,
+  },
+  {
+    key: "alphaMode",
+    converter: webidl.converters["GPUCanvasAlphaMode"],
+    defaultValue: "opaque",
+  },
+
+  // Extended from spec
+  {
+    key: "presentMode",
+    converter: webidl.converters["GPUPresentMode"],
+  },
+  {
+    key: "width",
+    converter: webidl.converters["long"],
+    required: true,
+  },
+  {
+    key: "height",
+    converter: webidl.converters["long"],
+    required: true,
+  },
+  {
+    key: "viewFormats",
+    converter: webidl.createSequenceConverter(
+      webidl.converters["GPUTextureFormat"],
+    ),
+    get defaultValue() {
+      return [];
+    },
+  },
+];
+webidl.converters["GPUCanvasConfiguration"] = webidl
+  .createDictionaryConverter(
+    "GPUCanvasConfiguration",
+    dictMembersGPUCanvasConfiguration,
+  );
+
 const gpu = webidl.createBranded(GPU);
 export {
   _device,
@@ -6978,6 +7491,7 @@ export {
   GPUDevice,
   GPUDeviceLostInfo,
   GPUError,
+  GPUInternalError,
   GPUMapMode,
   GPUOutOfMemoryError,
   GPUPipelineLayout,
@@ -6995,5 +7509,6 @@ export {
   GPUTexture,
   GPUTextureUsage,
   GPUTextureView,
+  GPUUncapturedErrorEvent,
   GPUValidationError,
 };
diff --git a/deno_webgpu/02_surface.js b/deno_webgpu/02_surface.js
index d16f5c245..f35f745af 100644
--- a/deno_webgpu/02_surface.js
+++ b/deno_webgpu/02_surface.js
@@ -1,4 +1,4 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 
 // @ts-check
 /// <reference path="../../core/lib.deno_core.d.ts" />
@@ -6,27 +6,32 @@
 /// <reference path="../web/lib.deno_web.d.ts" />
 /// <reference path="./lib.deno_webgpu.d.ts" />
 
-const core = globalThis.Deno.core;
-const ops = core.ops;
-import * as webidl from "ext:deno_webidl/00_webidl.js";
-const primordials = globalThis.__bootstrap.primordials;
-const { Symbol } = primordials;
+import { primordials } from "ext:core/mod.js";
 import {
-  _device,
-  assertDevice,
-  createGPUTexture,
-  GPUTextureUsage,
-} from "ext:deno_webgpu/01_webgpu.js";
+  op_webgpu_surface_configure,
+  op_webgpu_surface_create,
+  op_webgpu_surface_get_current_texture,
+  op_webgpu_surface_present,
+} from "ext:core/ops";
+const {
+  ObjectPrototypeIsPrototypeOf,
+  Symbol,
+  SymbolFor,
+  TypeError,
+} = primordials;
+
+import * as webidl from "ext:deno_webidl/00_webidl.js";
+import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
+import { loadWebGPU } from "ext:deno_webgpu/00_init.js";
 
 const _surfaceRid = Symbol("[[surfaceRid]]");
 const _configuration = Symbol("[[configuration]]");
 const _canvas = Symbol("[[canvas]]");
 const _currentTexture = Symbol("[[currentTexture]]");
+const _present = Symbol("[[present]]");
 class GPUCanvasContext {
   /** @type {number} */
   [_surfaceRid];
-  /** @type {InnerGPUDevice} */
-  [_device];
   [_configuration];
   [_canvas];
   /** @type {GPUTexture | undefined} */
@@ -50,11 +55,15 @@ class GPUCanvasContext {
       context: "Argument 1",
     });
 
+    const { _device, assertDevice } = loadWebGPU();
     this[_device] = configuration.device[_device];
     this[_configuration] = configuration;
-    const device = assertDevice(this, { prefix, context: "configuration.device" });
+    const device = assertDevice(this, {
+      prefix,
+      context: "configuration.device",
+    });
 
-    const { err } = ops.op_webgpu_surface_configure({
+    const { err } = op_webgpu_surface_configure({
       surfaceRid: this[_surfaceRid],
       deviceRid: device.rid,
       format: configuration.format,
@@ -69,6 +78,8 @@ class GPUCanvasContext {
   }
 
   unconfigure() {
+    const { _device } = loadWebGPU();
+
     webidl.assertBranded(this, GPUCanvasContextPrototype);
 
     this[_configuration] = null;
@@ -77,11 +88,13 @@ class GPUCanvasContext {
 
   getCurrentTexture() {
     webidl.assertBranded(this, GPUCanvasContextPrototype);
-    const prefix = "Failed to execute 'getCurrentTexture' on 'GPUCanvasContext'";
+    const prefix =
+      "Failed to execute 'getCurrentTexture' on 'GPUCanvasContext'";
 
     if (this[_configuration] === null) {
       throw new DOMException("context is not configured.", "InvalidStateError");
     }
+    const { createGPUTexture, assertDevice } = loadWebGPU();
 
     const device = assertDevice(this, { prefix, context: "this" });
 
@@ -89,7 +102,10 @@ class GPUCanvasContext {
       return this[_currentTexture];
     }
 
-    const { rid } = ops.op_webgpu_surface_get_current_texture(device.rid, this[_surfaceRid]);
+    const { rid } = op_webgpu_surface_get_current_texture(
+      device.rid,
+      this[_surfaceRid],
+    );
 
     const texture = createGPUTexture(
       {
@@ -112,102 +128,66 @@ class GPUCanvasContext {
     return texture;
   }
 
-  // Extended from spec. Required to present the texture; browser don't need this.
-  present() {
+  // Required to present the texture; browser don't need this.
+  [_present]() {
+    const { assertDevice } = loadWebGPU();
+
     webidl.assertBranded(this, GPUCanvasContextPrototype);
     const prefix = "Failed to execute 'present' on 'GPUCanvasContext'";
-    const device = assertDevice(this[_currentTexture], { prefix, context: "this" });
-    ops.op_webgpu_surface_present(device.rid, this[_surfaceRid]);
+    const device = assertDevice(this[_currentTexture], {
+      prefix,
+      context: "this",
+    });
+    op_webgpu_surface_present(device.rid, this[_surfaceRid]);
     this[_currentTexture].destroy();
     this[_currentTexture] = undefined;
   }
+
+  [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
+    return inspect(
+      createFilteredInspectProxy({
+        object: this,
+        evaluate: ObjectPrototypeIsPrototypeOf(GPUCanvasContextPrototype, this),
+        keys: [
+          "canvas",
+        ],
+      }),
+      inspectOptions,
+    );
+  }
 }
 const GPUCanvasContextPrototype = GPUCanvasContext.prototype;
 
 function createCanvasContext(options) {
+  // lazy load webgpu if needed
   const canvasContext = webidl.createBranded(GPUCanvasContext);
   canvasContext[_surfaceRid] = options.surfaceRid;
   canvasContext[_canvas] = options.canvas;
   return canvasContext;
 }
 
-// Converters
-
-// ENUM: GPUCanvasAlphaMode
-webidl.converters["GPUCanvasAlphaMode"] = webidl.createEnumConverter(
-  "GPUCanvasAlphaMode",
-  [
-    "opaque",
-    "premultiplied",
-  ],
-);
-
-// NON-SPEC: ENUM: GPUPresentMode
-webidl.converters["GPUPresentMode"] = webidl.createEnumConverter(
-  "GPUPresentMode",
-  [
-    "autoVsync",
-    "autoNoVsync",
-    "fifo",
-    "fifoRelaxed",
-    "immediate",
-    "mailbox",
-  ],
-);
-
-// DICT: GPUCanvasConfiguration
-const dictMembersGPUCanvasConfiguration = [
-  { key: "device", converter: webidl.converters.GPUDevice, required: true },
-  {
-    key: "format",
-    converter: webidl.converters.GPUTextureFormat,
-    required: true,
-  },
-  {
-    key: "usage",
-    converter: webidl.converters["GPUTextureUsageFlags"],
-    defaultValue: GPUTextureUsage.RENDER_ATTACHMENT,
-  },
-  {
-    key: "alphaMode",
-    converter: webidl.converters["GPUCanvasAlphaMode"],
-    defaultValue: "opaque",
-  },
-
-  // Extended from spec
-  {
-    key: "presentMode",
-    converter: webidl.converters["GPUPresentMode"],
-  },
-  {
-    key: "width",
-    converter: webidl.converters["long"],
-    required: true,
-  },
-  {
-    key: "height",
-    converter: webidl.converters["long"],
-    required: true,
-  },
-  {
-    key: "viewFormats",
-    converter: webidl.createSequenceConverter(
-      webidl.converters["GPUTextureFormat"],
-    ),
-    get defaultValue() {
-      return [];
-    },
-  },
-];
-webidl.converters["GPUCanvasConfiguration"] = webidl
-  .createDictionaryConverter(
-    "GPUCanvasConfiguration",
-    dictMembersGPUCanvasConfiguration,
-  );
-
-
-window.__bootstrap.webgpu = {
-  ...window.__bootstrap.webgpu,
-  GPUCanvasContext,
-  createCanvasContext,
-};
+// External webgpu surfaces
+
+// TODO(@littledivy): This will extend `OffscreenCanvas` when we add it.
+class UnsafeWindowSurface {
+  #ctx;
+  #surfaceRid;
+
+  constructor(system, win, display) {
+    this.#surfaceRid = op_webgpu_surface_create(system, win, display);
+  }
+
+  getContext(context) {
+    if (context !== "webgpu") {
+      throw new TypeError("Only 'webgpu' context is supported.");
+    }
+    this.#ctx = createCanvasContext({ surfaceRid: this.#surfaceRid });
+    return this.#ctx;
+  }
+
+  present() {
+    this.#ctx[_present]();
+  }
+}
+
+export { GPUCanvasContext, UnsafeWindowSurface };
diff --git a/deno_webgpu/Cargo.toml b/deno_webgpu/Cargo.toml
index 9f6d96a95..586eb90c8 100644
--- a/deno_webgpu/Cargo.toml
+++ b/deno_webgpu/Cargo.toml
@@ -1,8 +1,8 @@
-# Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+# Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 
 [package]
 name = "deno_webgpu"
-version = "0.85.0"
+version = "0.110.0"
 authors = ["the Deno authors"]
 edition.workspace = true
 license = "MIT"
@@ -13,9 +13,6 @@ description = "WebGPU implementation for Deno"
 [lib]
 path = "lib.rs"
 
-[features]
-surface = ["wgpu-core/raw-window-handle", "dep:raw-window-handle"]
-
 # We make all dependencies conditional on not being wasm,
 # so the whole workspace can built as wasm.
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
@@ -23,11 +20,11 @@ deno_core.workspace = true
 serde = { workspace = true, features = ["derive"] }
 tokio = { workspace = true, features = ["full"] }
 wgpu-types = { workspace = true, features = ["serde"] }
-raw-window-handle = { workspace = true, optional = true }
+raw-window-handle = { workspace = true }
 
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies.wgpu-core]
 workspace = true
-features = ["trace", "replay", "serde", "strict_asserts", "wgsl", "gles"]
+features = ["raw-window-handle", "trace", "replay", "serde", "strict_asserts", "wgsl", "gles"]
 
 # We want the wgpu-core Metal backend on macOS and iOS.
 [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies.wgpu-core]
diff --git a/deno_webgpu/LICENSE.md b/deno_webgpu/LICENSE.md
index aec557f3a..56753af36 100644
--- a/deno_webgpu/LICENSE.md
+++ b/deno_webgpu/LICENSE.md
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright 2018-2023 the Deno authors
+Copyright 2018-2024 the Deno authors
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
diff --git a/deno_webgpu/README.md b/deno_webgpu/README.md
index 1cf031cda..c419bfc60 100644
--- a/deno_webgpu/README.md
+++ b/deno_webgpu/README.md
@@ -2,8 +2,8 @@
 
 This op crate implements the WebGPU API as defined in
 https://gpuweb.github.io/gpuweb/ in Deno. The implementation targets the spec
-draft as of February 22, 2021. The spec is still very much in flux. This op
-crate tries to stay up to date with the spec, but is constrained by the features
+draft as of March 31, 2024. The spec is still very much in flux. This extension
+tries to stay up to date with the spec, but is constrained by the features
 implemented in our GPU backend library [wgpu](https://github.com/gfx-rs/wgpu).
 
 The spec is still very bare bones, and is still missing many details. As the
diff --git a/deno_webgpu/binding.rs b/deno_webgpu/binding.rs
index c0b9b5836..0efeb6716 100644
--- a/deno_webgpu/binding.rs
+++ b/deno_webgpu/binding.rs
@@ -1,4 +1,4 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 
 use deno_core::error::AnyError;
 use deno_core::op2;
@@ -112,25 +112,11 @@ impl From<GpuTextureSampleType> for wgpu_types::TextureSampleType {
 #[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
 struct GpuStorageTextureBindingLayout {
-    access: GpuStorageTextureAccess,
+    access: wgpu_types::StorageTextureAccess,
     format: wgpu_types::TextureFormat,
     view_dimension: wgpu_types::TextureViewDimension,
 }
 
-#[derive(Deserialize)]
-#[serde(rename_all = "kebab-case")]
-enum GpuStorageTextureAccess {
-    WriteOnly,
-}
-
-impl From<GpuStorageTextureAccess> for wgpu_types::StorageTextureAccess {
-    fn from(access: GpuStorageTextureAccess) -> Self {
-        match access {
-            GpuStorageTextureAccess::WriteOnly => wgpu_types::StorageTextureAccess::WriteOnly,
-        }
-    }
-}
-
 #[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub struct GpuBindGroupLayoutEntry {
@@ -165,7 +151,7 @@ impl From<GpuBindingType> for wgpu_types::BindingType {
             },
             GpuBindingType::StorageTexture(storage_texture) => {
                 wgpu_types::BindingType::StorageTexture {
-                    access: storage_texture.access.into(),
+                    access: storage_texture.access,
                     format: storage_texture.format,
                     view_dimension: storage_texture.view_dimension,
                 }
diff --git a/deno_webgpu/buffer.rs b/deno_webgpu/buffer.rs
index 9a2ebb003..5b7d20880 100644
--- a/deno_webgpu/buffer.rs
+++ b/deno_webgpu/buffer.rs
@@ -1,4 +1,4 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 
 use deno_core::error::type_error;
 use deno_core::error::AnyError;
@@ -163,6 +163,7 @@ pub fn op_webgpu_buffer_get_mapped_range(
     ))
     .map_err(|e| DomExceptionOperationError::new(&e.to_string()))?;
 
+    // SAFETY: guarantee to be safe from wgpu
     let slice = unsafe { std::slice::from_raw_parts_mut(slice_pointer, range_size as usize) };
     buf.copy_from_slice(slice);
 
@@ -189,6 +190,7 @@ pub fn op_webgpu_buffer_unmap(
     let buffer = buffer_resource.1;
 
     if let Some(buf) = buf {
+        // SAFETY: guarantee to be safe from wgpu
         let slice = unsafe { std::slice::from_raw_parts_mut(mapped_resource.0, mapped_resource.1) };
         slice.copy_from_slice(buf);
     }
diff --git a/deno_webgpu/bundle.rs b/deno_webgpu/bundle.rs
index d50359931..dfe5ccf49 100644
--- a/deno_webgpu/bundle.rs
+++ b/deno_webgpu/bundle.rs
@@ -1,4 +1,4 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 
 use deno_core::error::type_error;
 use deno_core::error::AnyError;
diff --git a/deno_webgpu/byow.rs b/deno_webgpu/byow.rs
new file mode 100644
index 000000000..3042f46ee
--- /dev/null
+++ b/deno_webgpu/byow.rs
@@ -0,0 +1,115 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::op2;
+use deno_core::OpState;
+use deno_core::ResourceId;
+use std::ffi::c_void;
+use std::ptr::NonNull;
+
+use crate::surface::WebGpuSurface;
+
+#[op2(fast)]
+#[smi]
+pub fn op_webgpu_surface_create(
+    state: &mut OpState,
+    #[string] system: &str,
+    p1: *const c_void,
+    p2: *const c_void,
+) -> Result<ResourceId, AnyError> {
+    let instance = state.borrow::<super::Instance>();
+    // Security note:
+    //
+    // The `p1` and `p2` parameters are pointers to platform-specific window
+    // handles.
+    //
+    // The code below works under the assumption that:
+    //
+    // - handles can only be created by the FFI interface which
+    // enforces --allow-ffi.
+    //
+    // - `*const c_void` deserizalizes null and v8::External.
+    //
+    // - Only FFI can export v8::External to user code.
+    if p1.is_null() {
+        return Err(type_error("Invalid parameters"));
+    }
+
+    let (win_handle, display_handle) = raw_window(system, p1, p2)?;
+    let surface = unsafe { instance.instance_create_surface(display_handle, win_handle, None)? };
+
+    let rid = state
+        .resource_table
+        .add(WebGpuSurface(instance.clone(), surface));
+    Ok(rid)
+}
+
+type RawHandles = (
+    raw_window_handle::RawWindowHandle,
+    raw_window_handle::RawDisplayHandle,
+);
+
+#[cfg(target_os = "macos")]
+fn raw_window(
+    system: &str,
+    _ns_window: *const c_void,
+    ns_view: *const c_void,
+) -> Result<RawHandles, AnyError> {
+    if system != "cocoa" {
+        return Err(type_error("Invalid system on macOS"));
+    }
+
+    let win_handle =
+        raw_window_handle::RawWindowHandle::AppKit(raw_window_handle::AppKitWindowHandle::new(
+            NonNull::new(ns_view as *mut c_void).ok_or(type_error("ns_view is null"))?,
+        ));
+
+    let display_handle =
+        raw_window_handle::RawDisplayHandle::AppKit(raw_window_handle::AppKitDisplayHandle::new());
+    Ok((win_handle, display_handle))
+}
+
+#[cfg(target_os = "windows")]
+fn raw_window(
+    system: &str,
+    window: *const c_void,
+    hinstance: *const c_void,
+) -> Result<RawHandles, AnyError> {
+    use raw_window_handle::WindowsDisplayHandle;
+    if system != "win32" {
+        return Err(type_error("Invalid system on Windows"));
+    }
+
+    let win_handle = {
+        let mut handle = raw_window_handle::Win32WindowHandle::new();
+        handle.hwnd = window as *mut c_void;
+        handle.hinstance = hinstance as *mut c_void;
+
+        raw_window_handle::RawWindowHandle::Win32(handle)
+    };
+
+    let display_handle = raw_window_handle::RawDisplayHandle::Windows(WindowsDisplayHandle::new());
+    Ok((win_handle, display_handle))
+}
+
+#[cfg(target_os = "linux")]
+fn raw_window(
+    system: &str,
+    window: *const c_void,
+    display: *const c_void,
+) -> Result<RawHandles, AnyError> {
+    if system != "x11" {
+        return Err(type_error("Invalid system on Linux"));
+    }
+
+    let win_handle = raw_window_handle::RawWindowHandle::Xlib(
+        raw_window_handle::XlibWindowHandle::new(window as *mut c_void as _),
+    );
+
+    let display_handle = raw_window_handle::RawDisplayHandle::Xlib(
+        raw_window_handle::XlibDisplayHandle::new(NonNull::new(display as *mut c_void), 0),
+    );
+
+    Ok((win_handle, display_handle))
+}
diff --git a/deno_webgpu/command_encoder.rs b/deno_webgpu/command_encoder.rs
index 679ac3cab..20dfe0db0 100644
--- a/deno_webgpu/command_encoder.rs
+++ b/deno_webgpu/command_encoder.rs
@@ -1,4 +1,4 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 
 use crate::WebGpuQuerySet;
 use deno_core::error::AnyError;
diff --git a/deno_webgpu/compute_pass.rs b/deno_webgpu/compute_pass.rs
index e1c9e2919..65ac93d63 100644
--- a/deno_webgpu/compute_pass.rs
+++ b/deno_webgpu/compute_pass.rs
@@ -1,4 +1,4 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 
 use deno_core::error::AnyError;
 use deno_core::op2;
diff --git a/deno_webgpu/error.rs b/deno_webgpu/error.rs
index 6c509a80d..bb8200899 100644
--- a/deno_webgpu/error.rs
+++ b/deno_webgpu/error.rs
@@ -1,4 +1,5 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
 use deno_core::error::AnyError;
 use deno_core::ResourceId;
 use serde::Serialize;
@@ -23,7 +24,6 @@ use wgpu_core::device::DeviceError;
 use wgpu_core::pipeline::CreateComputePipelineError;
 use wgpu_core::pipeline::CreateRenderPipelineError;
 use wgpu_core::pipeline::CreateShaderModuleError;
-#[cfg(feature = "surface")]
 use wgpu_core::present::ConfigureSurfaceError;
 use wgpu_core::resource::BufferAccessError;
 use wgpu_core::resource::CreateBufferError;
@@ -87,6 +87,7 @@ pub enum WebGpuError {
     Lost,
     OutOfMemory,
     Validation(String),
+    Internal,
 }
 
 impl From<CreateBufferError> for WebGpuError {
@@ -277,7 +278,6 @@ impl From<ClearError> for WebGpuError {
     }
 }
 
-#[cfg(feature = "surface")]
 impl From<ConfigureSurfaceError> for WebGpuError {
     fn from(err: ConfigureSurfaceError) -> Self {
         WebGpuError::Validation(fmt_err(&err))
diff --git a/deno_webgpu/lib.rs b/deno_webgpu/lib.rs
index 40e76e0fa..453d4ea7e 100644
--- a/deno_webgpu/lib.rs
+++ b/deno_webgpu/lib.rs
@@ -1,4 +1,4 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 #![cfg(not(target_arch = "wasm32"))]
 #![warn(unsafe_op_in_unsafe_fn)]
 
@@ -19,6 +19,8 @@ pub use wgpu_types;
 use error::DomExceptionOperationError;
 use error::WebGpuResult;
 
+pub const UNSTABLE_FEATURE_NAME: &str = "webgpu";
+
 #[macro_use]
 mod macros {
     macro_rules! gfx_select {
@@ -71,6 +73,7 @@ mod macros {
 pub mod binding;
 pub mod buffer;
 pub mod bundle;
+pub mod byow;
 pub mod command_encoder;
 pub mod compute_pass;
 pub mod error;
@@ -79,23 +82,9 @@ pub mod queue;
 pub mod render_pass;
 pub mod sampler;
 pub mod shader;
-#[cfg(feature = "surface")]
 pub mod surface;
 pub mod texture;
 
-pub struct Unstable(pub bool);
-
-fn check_unstable(state: &OpState, api_name: &str) {
-    let unstable = state.borrow::<Unstable>();
-    if !unstable.0 {
-        eprintln!(
-            "Unstable API '{}'. The --unstable flag must be provided.",
-            api_name
-        );
-        std::process::exit(70);
-    }
-}
-
 pub type Instance = std::sync::Arc<wgpu_core::global::Global>;
 
 struct WebGpuAdapter(Instance, wgpu_core::id::AdapterId);
@@ -224,12 +213,15 @@ deno_core::extension!(
         queue::op_webgpu_write_texture,
         // shader
         shader::op_webgpu_create_shader_module,
+        // surface
+        surface::op_webgpu_surface_configure,
+        surface::op_webgpu_surface_get_current_texture,
+        surface::op_webgpu_surface_present,
+        // byow
+        byow::op_webgpu_surface_create,
     ],
-    esm = ["01_webgpu.js"],
-    options = { unstable: bool },
-    state = |state, options| {
-        state.put(Unstable(options.unstable));
-    },
+    esm = ["00_init.js", "02_surface.js"],
+    lazy_loaded_esm = ["01_webgpu.js"],
 );
 
 fn deserialize_features(features: &wgpu_types::Features) -> Vec<&'static str> {
@@ -377,29 +369,45 @@ fn deserialize_features(features: &wgpu_types::Features) -> Vec<&'static str> {
 
 #[derive(Serialize)]
 #[serde(untagged)]
-pub enum GpuAdapterDeviceOrErr {
+pub enum GpuAdapterResOrErr {
     Error { err: String },
-    Features(GpuAdapterDevice),
+    Features(GpuAdapterRes),
 }
 
 #[derive(Serialize)]
 #[serde(rename_all = "camelCase")]
-pub struct GpuAdapterDevice {
+pub struct GpuAdapterRes {
     rid: ResourceId,
     limits: wgpu_types::Limits,
     features: Vec<&'static str>,
     is_software: bool,
 }
 
-#[op2(async)]
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct GpuDeviceRes {
+    rid: ResourceId,
+    queue_rid: ResourceId,
+    limits: wgpu_types::Limits,
+    features: Vec<&'static str>,
+    is_software: bool,
+}
+
+#[op2]
 #[serde]
-pub async fn op_webgpu_request_adapter(
+pub fn op_webgpu_request_adapter(
     state: Rc<RefCell<OpState>>,
     #[serde] power_preference: Option<wgpu_types::PowerPreference>,
     force_fallback_adapter: bool,
-) -> Result<GpuAdapterDeviceOrErr, AnyError> {
+) -> Result<GpuAdapterResOrErr, AnyError> {
     let mut state = state.borrow_mut();
-    check_unstable(&state, "navigator.gpu.requestAdapter");
+
+    // TODO(bartlomieju): replace with `state.feature_checker.check_or_exit`
+    // once we phase out `check_or_exit_with_legacy_fallback`
+    state
+        .feature_checker
+        .check_or_exit_with_legacy_fallback(UNSTABLE_FEATURE_NAME, "navigator.gpu.requestAdapter");
+
     let backends = std::env::var("DENO_WEBGPU_BACKEND").map_or_else(
         |_| wgpu_types::Backends::all(),
         |s| wgpu_core::instance::parse_backends_from_comma_list(&s),
@@ -432,7 +440,7 @@ pub async fn op_webgpu_request_adapter(
     let adapter = match res {
         Ok(adapter) => adapter,
         Err(err) => {
-            return Ok(GpuAdapterDeviceOrErr::Error {
+            return Ok(GpuAdapterResOrErr::Error {
                 err: err.to_string(),
             })
         }
@@ -445,7 +453,7 @@ pub async fn op_webgpu_request_adapter(
 
     let rid = state.resource_table.add(WebGpuAdapter(instance, adapter));
 
-    Ok(GpuAdapterDeviceOrErr::Features(GpuAdapterDevice {
+    Ok(GpuAdapterResOrErr::Features(GpuAdapterRes {
         rid,
         features,
         limits: adapter_limits,
@@ -649,15 +657,15 @@ impl From<GpuRequiredFeatures> for wgpu_types::Features {
     }
 }
 
-#[op2(async)]
+#[op2]
 #[serde]
-pub async fn op_webgpu_request_device(
+pub fn op_webgpu_request_device(
     state: Rc<RefCell<OpState>>,
     #[smi] adapter_rid: ResourceId,
     #[string] label: String,
     #[serde] required_features: GpuRequiredFeatures,
     #[serde] required_limits: Option<wgpu_types::Limits>,
-) -> Result<GpuAdapterDevice, AnyError> {
+) -> Result<GpuDeviceRes, AnyError> {
     let mut state = state.borrow_mut();
     let adapter_resource = state.resource_table.get::<WebGpuAdapter>(adapter_rid)?;
     let adapter = adapter_resource.1;
@@ -669,7 +677,7 @@ pub async fn op_webgpu_request_device(
         required_limits: required_limits.unwrap_or_default(),
     };
 
-    let (device, _queue, maybe_err) = gfx_select!(adapter => instance.adapter_request_device(
+    let (device, queue, maybe_err) = gfx_select!(adapter => instance.adapter_request_device(
       adapter,
       &descriptor,
       std::env::var("DENO_WEBGPU_TRACE").ok().as_ref().map(std::path::Path::new),
@@ -685,10 +693,15 @@ pub async fn op_webgpu_request_device(
     let limits = gfx_select!(device => instance.device_limits(device))?;
 
     let instance = instance.clone();
+    let instance2 = instance.clone();
     let rid = state.resource_table.add(WebGpuDevice(instance, device));
+    let queue_rid = state
+        .resource_table
+        .add(queue::WebGpuQueue(instance2, queue));
 
-    Ok(GpuAdapterDevice {
+    Ok(GpuDeviceRes {
         rid,
+        queue_rid,
         features,
         limits,
         // TODO(lucacasonato): report correctly from wgpu
@@ -705,9 +718,9 @@ pub struct GPUAdapterInfo {
     description: String,
 }
 
-#[op2(async)]
+#[op2]
 #[serde]
-pub async fn op_webgpu_request_adapter_info(
+pub fn op_webgpu_request_adapter_info(
     state: Rc<RefCell<OpState>>,
     #[smi] adapter_rid: ResourceId,
 ) -> Result<GPUAdapterInfo, AnyError> {
diff --git a/deno_webgpu/pipeline.rs b/deno_webgpu/pipeline.rs
index dcd4151eb..ab7cf42e7 100644
--- a/deno_webgpu/pipeline.rs
+++ b/deno_webgpu/pipeline.rs
@@ -1,4 +1,4 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 
 use deno_core::error::AnyError;
 use deno_core::op2;
@@ -74,7 +74,7 @@ pub enum GPUPipelineLayoutOrGPUAutoLayoutMode {
 #[serde(rename_all = "camelCase")]
 pub struct GpuProgrammableStage {
     module: ResourceId,
-    entry_point: String,
+    entry_point: Option<String>,
     // constants: HashMap<String, GPUPipelineConstantValue>
 }
 
@@ -110,7 +110,7 @@ pub fn op_webgpu_create_compute_pipeline(
         layout: pipeline_layout,
         stage: wgpu_core::pipeline::ProgrammableStageDescriptor {
             module: compute_shader_module_resource.1,
-            entry_point: Some(Cow::from(compute.entry_point)),
+            entry_point: compute.entry_point.map(Cow::from),
             // TODO(lucacasonato): support args.compute.constants
         },
     };
diff --git a/deno_webgpu/queue.rs b/deno_webgpu/queue.rs
index 61b56c758..264013445 100644
--- a/deno_webgpu/queue.rs
+++ b/deno_webgpu/queue.rs
@@ -1,16 +1,28 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 
 use crate::command_encoder::WebGpuCommandBuffer;
+use crate::Instance;
 use deno_core::error::AnyError;
 use deno_core::op2;
 use deno_core::OpState;
 use deno_core::Resource;
 use deno_core::ResourceId;
 use serde::Deserialize;
+use std::borrow::Cow;
+use std::rc::Rc;
 
 use super::error::WebGpuResult;
 
-type WebGpuQueue = super::WebGpuDevice;
+pub struct WebGpuQueue(pub Instance, pub wgpu_core::id::QueueId);
+impl Resource for WebGpuQueue {
+    fn name(&self) -> Cow<str> {
+        "webGPUQueue".into()
+    }
+
+    fn close(self: Rc<Self>) {
+        gfx_select!(self.1 => self.0.queue_drop(self.1));
+    }
+}
 
 #[op2]
 #[serde]
@@ -19,7 +31,7 @@ pub fn op_webgpu_queue_submit(
     #[smi] queue_rid: ResourceId,
     #[serde] command_buffers: Vec<ResourceId>,
 ) -> Result<WebGpuResult, AnyError> {
-    let instance = state.borrow::<super::Instance>();
+    let instance = state.borrow::<Instance>();
     let queue_resource = state.resource_table.get::<WebGpuQueue>(queue_rid)?;
     let queue = queue_resource.1;
 
@@ -32,7 +44,7 @@ pub fn op_webgpu_queue_submit(
         })
         .collect::<Result<Vec<_>, AnyError>>()?;
 
-    let maybe_err = gfx_select!(queue => instance.queue_submit(queue.transmute(), &ids)).err();
+    let maybe_err = gfx_select!(queue => instance.queue_submit(queue, &ids)).err();
 
     for rid in command_buffers {
         let resource = state.resource_table.take::<WebGpuCommandBuffer>(rid)?;
@@ -71,7 +83,7 @@ pub fn op_webgpu_write_buffer(
     #[number] size: Option<usize>,
     #[buffer] buf: &[u8],
 ) -> Result<WebGpuResult, AnyError> {
-    let instance = state.borrow::<super::Instance>();
+    let instance = state.borrow::<Instance>();
     let buffer_resource = state
         .resource_table
         .get::<super::buffer::WebGpuBuffer>(buffer)?;
@@ -84,7 +96,7 @@ pub fn op_webgpu_write_buffer(
         None => &buf[data_offset..],
     };
     let maybe_err = gfx_select!(queue => instance.queue_write_buffer(
-      queue.transmute(),
+      queue,
       buffer,
       buffer_offset,
       data
@@ -104,7 +116,7 @@ pub fn op_webgpu_write_texture(
     #[serde] size: wgpu_types::Extent3d,
     #[buffer] buf: &[u8],
 ) -> Result<WebGpuResult, AnyError> {
-    let instance = state.borrow::<super::Instance>();
+    let instance = state.borrow::<Instance>();
     let texture_resource = state
         .resource_table
         .get::<super::texture::WebGpuTexture>(destination.texture)?;
@@ -120,7 +132,7 @@ pub fn op_webgpu_write_texture(
     let data_layout = data_layout.into();
 
     gfx_ok!(queue => instance.queue_write_texture(
-      queue.transmute(),
+      queue,
       &destination,
       buf,
       &data_layout,
diff --git a/deno_webgpu/render_pass.rs b/deno_webgpu/render_pass.rs
index 47b98c91f..11b2f2286 100644
--- a/deno_webgpu/render_pass.rs
+++ b/deno_webgpu/render_pass.rs
@@ -1,4 +1,4 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 
 use deno_core::error::type_error;
 use deno_core::error::AnyError;
diff --git a/deno_webgpu/sampler.rs b/deno_webgpu/sampler.rs
index 0d65d727a..822c4bda1 100644
--- a/deno_webgpu/sampler.rs
+++ b/deno_webgpu/sampler.rs
@@ -1,4 +1,4 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 
 use deno_core::error::AnyError;
 use deno_core::op2;
diff --git a/deno_webgpu/shader.rs b/deno_webgpu/shader.rs
index f4604a04a..17cde4393 100644
--- a/deno_webgpu/shader.rs
+++ b/deno_webgpu/shader.rs
@@ -1,4 +1,4 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 
 use deno_core::error::AnyError;
 use deno_core::op2;
diff --git a/deno_webgpu/surface.rs b/deno_webgpu/surface.rs
index 4d8412999..a8b984eef 100644
--- a/deno_webgpu/surface.rs
+++ b/deno_webgpu/surface.rs
@@ -1,4 +1,4 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 
 use super::WebGpuResult;
 use deno_core::error::AnyError;
@@ -11,21 +11,6 @@ use std::borrow::Cow;
 use std::rc::Rc;
 use wgpu_types::SurfaceStatus;
 
-deno_core::extension!(
-    deno_webgpu_surface,
-    deps = [deno_webidl, deno_web, deno_webgpu],
-    ops = [
-        op_webgpu_surface_configure,
-        op_webgpu_surface_get_current_texture,
-        op_webgpu_surface_present,
-    ],
-    esm = ["02_surface.js"],
-    options = { unstable: bool },
-    state = |state, options| {
-        state.put(super::Unstable(options.unstable));
-    },
-);
-
 pub struct WebGpuSurface(pub crate::Instance, pub wgpu_core::id::SurfaceId);
 impl Resource for WebGpuSurface {
     fn name(&self) -> Cow<str> {
diff --git a/deno_webgpu/texture.rs b/deno_webgpu/texture.rs
index a9be7b991..2dc1a740a 100644
--- a/deno_webgpu/texture.rs
+++ b/deno_webgpu/texture.rs
@@ -1,4 +1,4 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 
 use deno_core::error::AnyError;
 use deno_core::op2;
diff --git a/deno_webgpu/webgpu.idl b/deno_webgpu/webgpu.idl
index 46d587874..bd709d117 100644
--- a/deno_webgpu/webgpu.idl
+++ b/deno_webgpu/webgpu.idl
@@ -6,7 +6,7 @@ dictionary GPUObjectDescriptorBase {
     USVString label = "";
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUSupportedLimits {
     readonly attribute unsigned long maxTextureDimension1D;
     readonly attribute unsigned long maxTextureDimension2D;
@@ -30,6 +30,8 @@ interface GPUSupportedLimits {
     readonly attribute unsigned long maxVertexAttributes;
     readonly attribute unsigned long maxVertexBufferArrayStride;
     readonly attribute unsigned long maxInterStageShaderComponents;
+    readonly attribute unsigned long maxColorAttachments;
+    readonly attribute unsigned long maxColorAttachmentBytesPerSample;
     readonly attribute unsigned long maxComputeWorkgroupStorageSize;
     readonly attribute unsigned long maxComputeInvocationsPerWorkgroup;
     readonly attribute unsigned long maxComputeWorkgroupSizeX;
@@ -38,12 +40,12 @@ interface GPUSupportedLimits {
     readonly attribute unsigned long maxComputeWorkgroupsPerDimension;
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUSupportedFeatures {
     readonly setlike<DOMString>;
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUAdapterInfo {
     readonly attribute DOMString vendor;
     readonly attribute DOMString architecture;
@@ -57,9 +59,10 @@ interface mixin NavigatorGPU {
 Navigator includes NavigatorGPU;
 WorkerNavigator includes NavigatorGPU;
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPU {
     Promise<GPUAdapter?> requestAdapter(optional GPURequestAdapterOptions options = {});
+    GPUTextureFormat getPreferredCanvasFormat();
 };
 
 dictionary GPURequestAdapterOptions {
@@ -72,14 +75,14 @@ enum GPUPowerPreference {
     "high-performance",
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUAdapter {
     [SameObject] readonly attribute GPUSupportedFeatures features;
     [SameObject] readonly attribute GPUSupportedLimits limits;
     readonly attribute boolean isFallbackAdapter;
 
     Promise<GPUDevice> requestDevice(optional GPUDeviceDescriptor descriptor = {});
-    Promise<GPUAdapterInfo> requestAdapterInfo(optional sequence<DOMString> unmaskHints = []);
+    Promise<GPUAdapterInfo> requestAdapterInfo();
 };
 
 dictionary GPUDeviceDescriptor
@@ -141,7 +144,7 @@ enum GPUFeatureName {
     "shader-early-depth-test",
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUDevice : EventTarget {
     [SameObject] readonly attribute GPUSupportedFeatures features;
     [SameObject] readonly attribute GPUSupportedLimits limits;
@@ -171,7 +174,7 @@ interface GPUDevice : EventTarget {
 };
 GPUDevice includes GPUObjectBase;
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUBuffer {
     readonly attribute GPUSize64Out size;
     readonly attribute GPUFlagsConstant usage;
@@ -200,7 +203,7 @@ dictionary GPUBufferDescriptor
 };
 
 typedef [EnforceRange] unsigned long GPUBufferUsageFlags;
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 namespace GPUBufferUsage {
     const GPUFlagsConstant MAP_READ      = 0x0001;
     const GPUFlagsConstant MAP_WRITE     = 0x0002;
@@ -215,13 +218,13 @@ namespace GPUBufferUsage {
 };
 
 typedef [EnforceRange] unsigned long GPUMapModeFlags;
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 namespace GPUMapMode {
     const GPUFlagsConstant READ  = 0x0001;
     const GPUFlagsConstant WRITE = 0x0002;
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUTexture {
     GPUTextureView createView(optional GPUTextureViewDescriptor descriptor = {});
 
@@ -256,7 +259,7 @@ enum GPUTextureDimension {
 };
 
 typedef [EnforceRange] unsigned long GPUTextureUsageFlags;
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 namespace GPUTextureUsage {
     const GPUFlagsConstant COPY_SRC          = 0x01;
     const GPUFlagsConstant COPY_DST          = 0x02;
@@ -265,7 +268,7 @@ namespace GPUTextureUsage {
     const GPUFlagsConstant RENDER_ATTACHMENT = 0x10;
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUTextureView {
 };
 GPUTextureView includes GPUObjectBase;
@@ -328,6 +331,7 @@ enum GPUTextureFormat {
     "bgra8unorm-srgb",
     // Packed 32-bit formats
     "rgb9e5ufloat",
+    "rgb10a2uint",
     "rgb10a2unorm",
     "rg11b10ufloat",
 
@@ -416,7 +420,7 @@ enum GPUTextureFormat {
     "astc-12x12-unorm-srgb",
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUSampler {
 };
 GPUSampler includes GPUObjectBase;
@@ -462,7 +466,7 @@ enum GPUCompareFunction {
     "always",
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUBindGroupLayout {
 };
 GPUBindGroupLayout includes GPUObjectBase;
@@ -483,7 +487,7 @@ dictionary GPUBindGroupLayoutEntry {
 };
 
 typedef [EnforceRange] unsigned long GPUShaderStageFlags;
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 namespace GPUShaderStage {
     const GPUFlagsConstant VERTEX   = 0x1;
     const GPUFlagsConstant FRAGMENT = 0x2;
@@ -528,6 +532,8 @@ dictionary GPUTextureBindingLayout {
 
 enum GPUStorageTextureAccess {
     "write-only",
+    "read-only",
+    "read-write",
 };
 
 dictionary GPUStorageTextureBindingLayout {
@@ -536,7 +542,7 @@ dictionary GPUStorageTextureBindingLayout {
     GPUTextureViewDimension viewDimension = "2d";
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUBindGroup {
 };
 GPUBindGroup includes GPUObjectBase;
@@ -560,7 +566,7 @@ dictionary GPUBufferBinding {
     GPUSize64 size;
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUPipelineLayout {
 };
 GPUPipelineLayout includes GPUObjectBase;
@@ -570,7 +576,7 @@ dictionary GPUPipelineLayoutDescriptor
     required sequence<GPUBindGroupLayout> bindGroupLayouts;
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUShaderModule {
 };
 GPUShaderModule includes GPUObjectBase;
@@ -586,7 +592,7 @@ enum GPUCompilationMessageType {
     "info",
 };
 
-[Exposed=(Window, DedicatedWorker), Serializable, SecureContext]
+[Exposed=(Window, Worker), Serializable, SecureContext]
 interface GPUCompilationMessage {
     readonly attribute DOMString message;
     readonly attribute GPUCompilationMessageType type;
@@ -596,11 +602,26 @@ interface GPUCompilationMessage {
     readonly attribute unsigned long long length;
 };
 
-[Exposed=(Window, DedicatedWorker), Serializable, SecureContext]
+[Exposed=(Window, Worker), Serializable, SecureContext]
 interface GPUCompilationInfo {
     readonly attribute FrozenArray<GPUCompilationMessage> messages;
 };
 
+[Exposed=(Window, Worker), SecureContext, Serializable]
+interface GPUPipelineError : DOMException {
+    constructor(optional DOMString message = "", GPUPipelineErrorInit options);
+    readonly attribute GPUPipelineErrorReason reason;
+};
+
+dictionary GPUPipelineErrorInit {
+    required GPUPipelineErrorReason reason;
+};
+
+enum GPUPipelineErrorReason {
+    "validation",
+    "internal",
+};
+
 enum GPUAutoLayoutMode {
     "auto",
 };
@@ -616,13 +637,13 @@ interface mixin GPUPipelineBase {
 
 dictionary GPUProgrammableStage {
     required GPUShaderModule module;
-    required USVString entryPoint;
+    USVString entryPoint;
     record<USVString, GPUPipelineConstantValue> constants;
 };
 
-typedef double GPUPipelineConstantValue; // May represent WGSL’s bool, f32, i32, u32, and f16 if enabled.
+typedef double GPUPipelineConstantValue; // May represent WGSL's bool, f32, i32, u32, and f16 if enabled.
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUComputePipeline {
 };
 GPUComputePipeline includes GPUObjectBase;
@@ -633,7 +654,7 @@ dictionary GPUComputePipelineDescriptor
     required GPUProgrammableStage compute;
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPURenderPipeline {
 };
 GPURenderPipeline includes GPUObjectBase;
@@ -701,7 +722,7 @@ dictionary GPUBlendState {
 };
 
 typedef [EnforceRange] unsigned long GPUColorWriteFlags;
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 namespace GPUColorWrite {
     const GPUFlagsConstant RED   = 0x1;
     const GPUFlagsConstant GREEN = 0x2;
@@ -854,7 +875,7 @@ dictionary GPUImageCopyTexture {
     GPUTextureAspect aspect = "all";
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUCommandBuffer {
 };
 GPUCommandBuffer includes GPUObjectBase;
@@ -866,7 +887,7 @@ dictionary GPUCommandBufferDescriptor
 interface mixin GPUCommandsMixin {
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUCommandEncoder {
     GPURenderPassEncoder beginRenderPass(GPURenderPassDescriptor descriptor);
     GPUComputePassEncoder beginComputePass(optional GPUComputePassDescriptor descriptor = {});
@@ -933,7 +954,7 @@ interface mixin GPUDebugCommandsMixin {
     undefined insertDebugMarker(USVString markerLabel);
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUComputePassEncoder {
     undefined setPipeline(GPUComputePipeline pipeline);
     undefined dispatchWorkgroups(GPUSize32 workgroupCountX, optional GPUSize32 workgroupCountY = 1, optional GPUSize32 workgroupCountZ = 1);
@@ -957,7 +978,7 @@ dictionary GPUComputePassDescriptor
     GPUComputePassTimestampWrites timestampWrites;
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPURenderPassEncoder {
     undefined setViewport(float x, float y,
         float width, float height,
@@ -1052,7 +1073,7 @@ interface mixin GPURenderCommandsMixin {
     undefined drawIndexedIndirect(GPUBuffer indirectBuffer, GPUSize64 indirectOffset);
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPURenderBundle {
 };
 GPURenderBundle includes GPUObjectBase;
@@ -1061,7 +1082,7 @@ dictionary GPURenderBundleDescriptor
          : GPUObjectDescriptorBase {
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPURenderBundleEncoder {
     GPURenderBundle finish(optional GPURenderBundleDescriptor descriptor = {});
 };
@@ -1077,7 +1098,7 @@ dictionary GPURenderBundleEncoderDescriptor
     boolean stencilReadOnly = false;
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUQueue {
     undefined submit(sequence<GPUCommandBuffer> commandBuffers);
 
@@ -1098,7 +1119,7 @@ interface GPUQueue {
 };
 GPUQueue includes GPUObjectBase;
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUQuerySet {
     undefined destroy();
 
@@ -1118,7 +1139,7 @@ enum GPUQueryType {
     "timestamp",
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUCanvasContext {
     readonly attribute (HTMLCanvasElement or OffscreenCanvas) canvas;
 
@@ -1146,7 +1167,7 @@ enum GPUDeviceLostReason {
     "destroyed",
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUDeviceLostInfo {
     readonly attribute GPUDeviceLostReason reason;
     readonly attribute DOMString message;
@@ -1156,27 +1177,33 @@ partial interface GPUDevice {
     readonly attribute Promise<GPUDeviceLostInfo> lost;
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUError {
     readonly attribute DOMString message;
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUValidationError
         : GPUError {
     constructor(DOMString message);
 };
 
-[Exposed=(Window, DedicatedWorker), SecureContext]
+[Exposed=(Window, Worker), SecureContext]
 interface GPUOutOfMemoryError
         : GPUError {
     constructor(DOMString message);
 };
 
+[Exposed=(Window, Worker), SecureContext]
+interface GPUInternalError
+        : GPUError {
+    constructor(DOMString message);
+};
+
 enum GPUErrorFilter {
     "validation",
     "out-of-memory",
-    "internal"
+    "internal",
 };
 
 partial interface GPUDevice {
@@ -1184,8 +1211,21 @@ partial interface GPUDevice {
     Promise<GPUError?> popErrorScope();
 };
 
+[Exposed=(Window, Worker), SecureContext]
+interface GPUUncapturedErrorEvent : Event {
+    constructor(
+        DOMString type,
+        GPUUncapturedErrorEventInit gpuUncapturedErrorEventInitDict
+    );
+    [SameObject] readonly attribute GPUError error;
+};
+
+dictionary GPUUncapturedErrorEventInit : EventInit {
+    required GPUError error;
+};
+
 partial interface GPUDevice {
-    [Exposed=(Window, DedicatedWorker)]
+    [Exposed=(Window, Worker)]
     attribute EventHandler onuncapturederror;
 };
 
diff --git a/naga/README.md b/naga/README.md
index b38f18d3b..0e07d4049 100644
--- a/naga/README.md
+++ b/naga/README.md
@@ -42,7 +42,7 @@ First, install `naga-cli` from crates.io or directly from GitHub.
 cargo install naga-cli
 
 # development version
-cargo install naga-cli --git https://github.com/gfx-rs/naga.git
+cargo install naga-cli --git https://github.com/gfx-rs/wgpu.git
 ```
 
 Then, you can run `naga` command.
diff --git a/naga/src/back/mod.rs b/naga/src/back/mod.rs
index 8100b930e..c8f091dec 100644
--- a/naga/src/back/mod.rs
+++ b/naga/src/back/mod.rs
@@ -16,14 +16,19 @@ pub mod spv;
 #[cfg(feature = "wgsl-out")]
 pub mod wgsl;
 
-const COMPONENTS: &[char] = &['x', 'y', 'z', 'w'];
-const INDENT: &str = "    ";
-const BAKE_PREFIX: &str = "_e";
+/// Names of vector components.
+pub const COMPONENTS: &[char] = &['x', 'y', 'z', 'w'];
+/// Indent for backends.
+pub const INDENT: &str = "    ";
+/// Prefix used for baking.
+pub const BAKE_PREFIX: &str = "_e";
 
-type NeedBakeExpressions = crate::FastHashSet<crate::Handle<crate::Expression>>;
+/// Expressions that need baking.
+pub type NeedBakeExpressions = crate::FastHashSet<crate::Handle<crate::Expression>>;
 
+/// Indentation level.
 #[derive(Clone, Copy)]
-struct Level(usize);
+pub struct Level(pub usize);
 
 impl Level {
     const fn next(&self) -> Self {
@@ -52,7 +57,7 @@ impl std::fmt::Display for Level {
 /// [`EntryPoint`]: crate::EntryPoint
 /// [`Module`]: crate::Module
 /// [`Module::entry_points`]: crate::Module::entry_points
-enum FunctionType {
+pub enum FunctionType {
     /// A regular function.
     Function(crate::Handle<crate::Function>),
     /// An [`EntryPoint`], and its index in [`Module::entry_points`].
@@ -63,7 +68,8 @@ enum FunctionType {
 }
 
 impl FunctionType {
-    fn is_compute_entry_point(&self, module: &crate::Module) -> bool {
+    /// Returns true if the function is an entry point for a compute shader.
+    pub fn is_compute_entry_point(&self, module: &crate::Module) -> bool {
         match *self {
             FunctionType::EntryPoint(index) => {
                 module.entry_points[index as usize].stage == crate::ShaderStage::Compute
@@ -74,19 +80,20 @@ impl FunctionType {
 }
 
 /// Helper structure that stores data needed when writing the function
-struct FunctionCtx<'a> {
+pub struct FunctionCtx<'a> {
     /// The current function being written
-    ty: FunctionType,
+    pub ty: FunctionType,
     /// Analysis about the function
-    info: &'a crate::valid::FunctionInfo,
+    pub info: &'a crate::valid::FunctionInfo,
     /// The expression arena of the current function being written
-    expressions: &'a crate::Arena<crate::Expression>,
+    pub expressions: &'a crate::Arena<crate::Expression>,
     /// Map of expressions that have associated variable names
-    named_expressions: &'a crate::NamedExpressions,
+    pub named_expressions: &'a crate::NamedExpressions,
 }
 
 impl FunctionCtx<'_> {
-    fn resolve_type<'a>(
+    /// Helper method that resolves a type of a given expression.
+    pub fn resolve_type<'a>(
         &'a self,
         handle: crate::Handle<crate::Expression>,
         types: &'a crate::UniqueArena<crate::Type>,
@@ -95,7 +102,10 @@ impl FunctionCtx<'_> {
     }
 
     /// Helper method that generates a [`NameKey`](crate::proc::NameKey) for a local in the current function
-    const fn name_key(&self, local: crate::Handle<crate::LocalVariable>) -> crate::proc::NameKey {
+    pub const fn name_key(
+        &self,
+        local: crate::Handle<crate::LocalVariable>,
+    ) -> crate::proc::NameKey {
         match self.ty {
             FunctionType::Function(handle) => crate::proc::NameKey::FunctionLocal(handle, local),
             FunctionType::EntryPoint(idx) => crate::proc::NameKey::EntryPointLocal(idx, local),
@@ -106,7 +116,7 @@ impl FunctionCtx<'_> {
     ///
     /// # Panics
     /// - If the function arguments are less or equal to `arg`
-    const fn argument_key(&self, arg: u32) -> crate::proc::NameKey {
+    pub const fn argument_key(&self, arg: u32) -> crate::proc::NameKey {
         match self.ty {
             FunctionType::Function(handle) => crate::proc::NameKey::FunctionArgument(handle, arg),
             FunctionType::EntryPoint(ep_index) => {
@@ -115,8 +125,8 @@ impl FunctionCtx<'_> {
         }
     }
 
-    // Returns true if the given expression points to a fixed-function pipeline input.
-    fn is_fixed_function_input(
+    /// Returns true if the given expression points to a fixed-function pipeline input.
+    pub fn is_fixed_function_input(
         &self,
         mut expression: crate::Handle<crate::Expression>,
         module: &crate::Module,
@@ -162,7 +172,7 @@ impl crate::Expression {
     /// See the [module-level documentation][emit] for details.
     ///
     /// [emit]: index.html#expression-evaluation-time
-    const fn bake_ref_count(&self) -> usize {
+    pub const fn bake_ref_count(&self) -> usize {
         match *self {
             // accesses are never cached, only loads are
             crate::Expression::Access { .. } | crate::Expression::AccessIndex { .. } => usize::MAX,
@@ -181,9 +191,7 @@ impl crate::Expression {
 }
 
 /// Helper function that returns the string corresponding to the [`BinaryOperator`](crate::BinaryOperator)
-/// # Notes
-/// Used by `glsl-out`, `msl-out`, `wgsl-out`, `hlsl-out`.
-const fn binary_operation_str(op: crate::BinaryOperator) -> &'static str {
+pub const fn binary_operation_str(op: crate::BinaryOperator) -> &'static str {
     use crate::BinaryOperator as Bo;
     match op {
         Bo::Add => "+",
@@ -208,8 +216,6 @@ const fn binary_operation_str(op: crate::BinaryOperator) -> &'static str {
 }
 
 /// Helper function that returns the string corresponding to the [`VectorSize`](crate::VectorSize)
-/// # Notes
-/// Used by `msl-out`, `wgsl-out`, `hlsl-out`.
 const fn vector_size_str(size: crate::VectorSize) -> &'static str {
     match size {
         crate::VectorSize::Bi => "2",
@@ -219,7 +225,8 @@ const fn vector_size_str(size: crate::VectorSize) -> &'static str {
 }
 
 impl crate::TypeInner {
-    const fn is_handle(&self) -> bool {
+    /// Returns true if this is a handle to a type rather than the type directly.
+    pub const fn is_handle(&self) -> bool {
         match *self {
             crate::TypeInner::Image { .. } | crate::TypeInner::Sampler { .. } => true,
             _ => false,
@@ -266,8 +273,9 @@ bitflags::bitflags! {
     }
 }
 
+/// The intersection test to use for ray queries.
 #[repr(u32)]
-enum RayIntersectionType {
+pub enum RayIntersectionType {
     Triangle = 1,
     BoundingBox = 4,
 }
diff --git a/naga/src/back/spv/index.rs b/naga/src/back/spv/index.rs
index 92e0f88d9..0effb568b 100644
--- a/naga/src/back/spv/index.rs
+++ b/naga/src/back/spv/index.rs
@@ -3,8 +3,9 @@ Bounds-checking for SPIR-V output.
 */
 
 use super::{
-    helpers::global_needs_wrapper, selection::Selection, Block, BlockContext, Error, IdGenerator,
-    Instruction, Word,
+    helpers::{global_needs_wrapper, map_storage_class},
+    selection::Selection,
+    Block, BlockContext, Error, IdGenerator, Instruction, Word,
 };
 use crate::{arena::Handle, proc::BoundsCheckPolicy};
 
@@ -42,32 +43,113 @@ impl<'w> BlockContext<'w> {
         array: Handle<crate::Expression>,
         block: &mut Block,
     ) -> Result<Word, Error> {
-        // Naga IR permits runtime-sized arrays as global variables or as the
-        // final member of a struct that is a global variable. SPIR-V permits
-        // only the latter, so this back end wraps bare runtime-sized arrays
-        // in a made-up struct; see `helpers::global_needs_wrapper` and its uses.
-        // This code must handle both cases.
-        let (structure_id, last_member_index) = match self.ir_function.expressions[array] {
+        // Naga IR permits runtime-sized arrays as global variables, or as the
+        // final member of a struct that is a global variable, or one of these
+        // inside a buffer that is itself an element in a buffer bindings array.
+        // SPIR-V requires that runtime-sized arrays are wrapped in structs.
+        // See `helpers::global_needs_wrapper` and its uses.
+        let (opt_array_index_id, global_handle, opt_last_member_index) = match self
+            .ir_function
+            .expressions[array]
+        {
             crate::Expression::AccessIndex { base, index } => {
                 match self.ir_function.expressions[base] {
-                    crate::Expression::GlobalVariable(handle) => (
-                        self.writer.global_variables[handle.index()].access_id,
-                        index,
-                    ),
-                    _ => return Err(Error::Validation("array length expression")),
+                    // The global variable is an array of buffer bindings of structs,
+                    // we are accessing one of them with a static index,
+                    // and the last member of it.
+                    crate::Expression::AccessIndex {
+                        base: base_outer,
+                        index: index_outer,
+                    } => match self.ir_function.expressions[base_outer] {
+                        crate::Expression::GlobalVariable(handle) => {
+                            let index_id = self.get_index_constant(index_outer);
+                            (Some(index_id), handle, Some(index))
+                        }
+                        _ => return Err(Error::Validation("array length expression case-1a")),
+                    },
+                    // The global variable is an array of buffer bindings of structs,
+                    // we are accessing one of them with a dynamic index,
+                    // and the last member of it.
+                    crate::Expression::Access {
+                        base: base_outer,
+                        index: index_outer,
+                    } => match self.ir_function.expressions[base_outer] {
+                        crate::Expression::GlobalVariable(handle) => {
+                            let index_id = self.cached[index_outer];
+                            (Some(index_id), handle, Some(index))
+                        }
+                        _ => return Err(Error::Validation("array length expression case-1b")),
+                    },
+                    // The global variable is a buffer, and we are accessing the last member.
+                    crate::Expression::GlobalVariable(handle) => {
+                        let global = &self.ir_module.global_variables[handle];
+                        match self.ir_module.types[global.ty].inner {
+                            // The global variable is an array of buffer bindings of run-time arrays.
+                            crate::TypeInner::BindingArray { .. } => (Some(index), handle, None),
+                            // The global variable is a struct, and we are accessing the last member
+                            _ => (None, handle, Some(index)),
+                        }
+                    }
+                    _ => return Err(Error::Validation("array length expression case-1c")),
                 }
             }
+            // The global variable is an array of buffer bindings of arrays.
+            crate::Expression::Access { base, index } => match self.ir_function.expressions[base] {
+                crate::Expression::GlobalVariable(handle) => {
+                    let index_id = self.cached[index];
+                    let global = &self.ir_module.global_variables[handle];
+                    match self.ir_module.types[global.ty].inner {
+                        crate::TypeInner::BindingArray { .. } => (Some(index_id), handle, None),
+                        _ => return Err(Error::Validation("array length expression case-2a")),
+                    }
+                }
+                _ => return Err(Error::Validation("array length expression case-2b")),
+            },
+            // The global variable is a run-time array.
             crate::Expression::GlobalVariable(handle) => {
                 let global = &self.ir_module.global_variables[handle];
                 if !global_needs_wrapper(self.ir_module, global) {
-                    return Err(Error::Validation("array length expression"));
+                    return Err(Error::Validation("array length expression case-3"));
                 }
-
-                (self.writer.global_variables[handle.index()].var_id, 0)
+                (None, handle, None)
             }
-            _ => return Err(Error::Validation("array length expression")),
+            _ => return Err(Error::Validation("array length expression case-4")),
         };
 
+        let gvar = self.writer.global_variables[global_handle.index()].clone();
+        let global = &self.ir_module.global_variables[global_handle];
+        let (last_member_index, gvar_id) = match opt_last_member_index {
+            Some(index) => (index, gvar.access_id),
+            None => {
+                if !global_needs_wrapper(self.ir_module, global) {
+                    return Err(Error::Validation(
+                        "pointer to a global that is not a wrapped array",
+                    ));
+                }
+                (0, gvar.var_id)
+            }
+        };
+        let structure_id = match opt_array_index_id {
+            // We are indexing inside a binding array, generate the access op.
+            Some(index_id) => {
+                let element_type_id = match self.ir_module.types[global.ty].inner {
+                    crate::TypeInner::BindingArray { base, size: _ } => {
+                        let class = map_storage_class(global.space);
+                        self.get_pointer_id(base, class)?
+                    }
+                    _ => return Err(Error::Validation("array length expression case-5")),
+                };
+                let structure_id = self.gen_id();
+                block.body.push(Instruction::access_chain(
+                    element_type_id,
+                    structure_id,
+                    gvar_id,
+                    &[index_id],
+                ));
+                structure_id
+            }
+            None => gvar_id,
+        };
         let length_id = self.gen_id();
         block.body.push(Instruction::array_length(
             self.writer.get_uint_type_id(),
diff --git a/naga/src/back/spv/mod.rs b/naga/src/back/spv/mod.rs
index b7d57be0d..eb29e3cd8 100644
--- a/naga/src/back/spv/mod.rs
+++ b/naga/src/back/spv/mod.rs
@@ -576,6 +576,15 @@ impl BlockContext<'_> {
         self.writer
             .get_constant_scalar(crate::Literal::I32(scope as _))
     }
+
+    fn get_pointer_id(
+        &mut self,
+        handle: Handle<crate::Type>,
+        class: spirv::StorageClass,
+    ) -> Result<Word, Error> {
+        self.writer
+            .get_pointer_id(&self.ir_module.types, handle, class)
+    }
 }
 
 #[derive(Clone, Copy, Default)]
diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs
index de3220bbd..a5065e062 100644
--- a/naga/src/back/spv/writer.rs
+++ b/naga/src/back/spv/writer.rs
@@ -565,36 +565,38 @@ impl Writer {
             // Handle globals are pre-emitted and should be loaded automatically.
             //
             // Any that are binding arrays we skip as we cannot load the array, we must load the result after indexing.
-            let is_binding_array = match ir_module.types[var.ty].inner {
-                crate::TypeInner::BindingArray { .. } => true,
-                _ => false,
-            };
-
-            if var.space == crate::AddressSpace::Handle && !is_binding_array {
-                let var_type_id = self.get_type_id(LookupType::Handle(var.ty));
-                let id = self.id_gen.next();
-                prelude
-                    .body
-                    .push(Instruction::load(var_type_id, id, gv.var_id, None));
-                gv.access_id = gv.var_id;
-                gv.handle_id = id;
-            } else if global_needs_wrapper(ir_module, var) {
-                let class = map_storage_class(var.space);
-                let pointer_type_id = self.get_pointer_id(&ir_module.types, var.ty, class)?;
-                let index_id = self.get_index_constant(0);
-
-                let id = self.id_gen.next();
-                prelude.body.push(Instruction::access_chain(
-                    pointer_type_id,
-                    id,
-                    gv.var_id,
-                    &[index_id],
-                ));
-                gv.access_id = id;
-            } else {
-                // by default, the variable ID is accessed as is
-                gv.access_id = gv.var_id;
-            };
+            match ir_module.types[var.ty].inner {
+                crate::TypeInner::BindingArray { .. } => {
+                    gv.access_id = gv.var_id;
+                }
+                _ => {
+                    if var.space == crate::AddressSpace::Handle {
+                        let var_type_id = self.get_type_id(LookupType::Handle(var.ty));
+                        let id = self.id_gen.next();
+                        prelude
+                            .body
+                            .push(Instruction::load(var_type_id, id, gv.var_id, None));
+                        gv.access_id = gv.var_id;
+                        gv.handle_id = id;
+                    } else if global_needs_wrapper(ir_module, var) {
+                        let class = map_storage_class(var.space);
+                        let pointer_type_id =
+                            self.get_pointer_id(&ir_module.types, var.ty, class)?;
+                        let index_id = self.get_index_constant(0);
+                        let id = self.id_gen.next();
+                        prelude.body.push(Instruction::access_chain(
+                            pointer_type_id,
+                            id,
+                            gv.var_id,
+                            &[index_id],
+                        ));
+                        gv.access_id = id;
+                    } else {
+                        // by default, the variable ID is accessed as is
+                        gv.access_id = gv.var_id;
+                    };
+                }
+            }
 
             // work around borrow checking in the presence of `self.xxx()` calls
             self.global_variables[handle.index()] = gv;
@@ -1858,9 +1860,15 @@ impl Writer {
             .iter()
             .flat_map(|entry| entry.function.arguments.iter())
             .any(|arg| has_view_index_check(ir_module, arg.binding.as_ref(), arg.ty));
-        let has_ray_query = ir_module.special_types.ray_desc.is_some()
+        let mut has_ray_query = ir_module.special_types.ray_desc.is_some()
             | ir_module.special_types.ray_intersection.is_some();
 
+        for (_, &crate::Type { ref inner, .. }) in ir_module.types.iter() {
+            if let &crate::TypeInner::AccelerationStructure | &crate::TypeInner::RayQuery = inner {
+                has_ray_query = true
+            }
+        }
+
         if self.physical_layout.version < 0x10300 && has_storage_buffers {
             // enable the storage buffer class on < SPV-1.3
             Instruction::extension("SPV_KHR_storage_buffer_storage_class")
diff --git a/naga/src/valid/type.rs b/naga/src/valid/type.rs
index d44a295b1..b8eb618ed 100644
--- a/naga/src/valid/type.rs
+++ b/naga/src/valid/type.rs
@@ -510,7 +510,6 @@ impl super::Validator {
                 ti.uniform_layout = Ok(Alignment::MIN_UNIFORM);
 
                 let mut min_offset = 0;
-
                 let mut prev_struct_data: Option<(u32, u32)> = None;
 
                 for (i, member) in members.iter().enumerate() {
@@ -662,6 +661,7 @@ impl super::Validator {
                     // Currently Naga only supports binding arrays of structs for non-handle types.
                     match gctx.types[base].inner {
                         crate::TypeInner::Struct { .. } => {}
+                        crate::TypeInner::Array { .. } => {}
                         _ => return Err(TypeError::BindingArrayBaseTypeNotStruct(base)),
                     };
                 }
diff --git a/wgpu-core/src/command/mod.rs b/wgpu-core/src/command/mod.rs
index febed4fc9..ab413db73 100644
--- a/wgpu-core/src/command/mod.rs
+++ b/wgpu-core/src/command/mod.rs
@@ -82,7 +82,7 @@ impl<A: HalApi> CommandEncoder<A> {
         }
     }
 
-    fn open(&mut self) -> Result<&mut A::CommandEncoder, DeviceError> {
+    pub(crate) fn open(&mut self) -> Result<&mut A::CommandEncoder, DeviceError> {
         if !self.is_open {
             self.is_open = true;
             let label = self.label.as_deref();
diff --git a/wgpu-core/src/device/global.rs b/wgpu-core/src/device/global.rs
index f52e9d0b6..0c97e1b50 100644
--- a/wgpu-core/src/device/global.rs
+++ b/wgpu-core/src/device/global.rs
@@ -2019,7 +2019,7 @@ impl Global {
                 let snatch_guard = device.snatchable_lock.read();
                 let fence = device.fence.read();
                 let fence = fence.as_ref().unwrap();
-                match device.maintain(fence, wgt::Maintain::Wait, &snatch_guard) {
+                match device.maintain(fence, wgt::Maintain::Wait, snatch_guard) {
                     Ok((closures, _)) => {
                         user_callbacks = closures;
                     }
@@ -2132,7 +2132,7 @@ impl Global {
         let snatch_guard = device.snatchable_lock.read();
         let fence = device.fence.read();
         let fence = fence.as_ref().unwrap();
-        let (closures, queue_empty) = device.maintain(fence, maintain, &snatch_guard)?;
+        let (closures, queue_empty) = device.maintain(fence, maintain, snatch_guard)?;
 
         // Some deferred destroys are scheduled in maintain so run this right after
         // to avoid holding on to them until the next device poll.
diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs
index ca63a6bb1..3cb5f695a 100644
--- a/wgpu-core/src/device/queue.rs
+++ b/wgpu-core/src/device/queue.rs
@@ -1545,7 +1545,7 @@ impl Global {
 
             // This will schedule destruction of all resources that are no longer needed
             // by the user but used in the command stream, among other things.
-            let (closures, _) = match device.maintain(fence, wgt::Maintain::Poll, &snatch_guard) {
+            let (closures, _) = match device.maintain(fence, wgt::Maintain::Poll, snatch_guard) {
                 Ok(closures) => closures,
                 Err(WaitIdleError::Device(err)) => return Err(QueueSubmitError::Queue(err)),
                 Err(WaitIdleError::StuckGpu) => return Err(QueueSubmitError::StuckGpu),
diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs
index 7de87c6fd..4892aecb7 100644
--- a/wgpu-core/src/device/resource.rs
+++ b/wgpu-core/src/device/resource.rs
@@ -394,7 +394,7 @@ impl<A: HalApi> Device<A> {
         &'this self,
         fence: &A::Fence,
         maintain: wgt::Maintain<queue::WrappedSubmissionIndex>,
-        snatch_guard: &SnatchGuard,
+        snatch_guard: SnatchGuard,
     ) -> Result<(UserClosures, bool), WaitIdleError> {
         profiling::scope!("Device::maintain");
         let last_done_index = if maintain.is_wait() {
@@ -449,7 +449,7 @@ impl<A: HalApi> Device<A> {
         }
 
         let mapping_closures =
-            life_tracker.handle_mapping(self.raw(), &self.trackers, snatch_guard);
+            life_tracker.handle_mapping(self.raw(), &self.trackers, &snatch_guard);
 
         let queue_empty = life_tracker.queue_empty();
 
@@ -476,8 +476,9 @@ impl<A: HalApi> Device<A> {
             }
         }
 
-        // Don't hold the lock while calling release_gpu_resources.
+        // Don't hold the locks while calling release_gpu_resources.
         drop(life_tracker);
+        drop(snatch_guard);
 
         if should_release_gpu_resource {
             self.release_gpu_resources();
diff --git a/wgpu-core/src/resource.rs b/wgpu-core/src/resource.rs
index aca077caa..8256b9553 100644
--- a/wgpu-core/src/resource.rs
+++ b/wgpu-core/src/resource.rs
@@ -8,7 +8,10 @@ use crate::{
     },
     global::Global,
     hal_api::HalApi,
-    id::{AdapterId, BufferId, DeviceId, Id, Marker, SurfaceId, TextureId},
+    id::{
+        AdapterId, BufferId, CommandEncoderId, DeviceId, Id, Marker, SurfaceId, TextureId,
+        TextureViewId,
+    },
     init_tracker::{BufferInitTracker, TextureInitTracker},
     resource, resource_log,
     snatch::{ExclusiveSnatchGuard, SnatchGuard, Snatchable},
@@ -924,11 +927,11 @@ impl Global {
     /// # Safety
     ///
     /// - The raw texture handle must not be manually destroyed
-    pub unsafe fn texture_as_hal<A: HalApi, F: FnOnce(Option<&A::Texture>)>(
+    pub unsafe fn texture_as_hal<A: HalApi, F: FnOnce(Option<&A::Texture>) -> R, R>(
         &self,
         id: TextureId,
         hal_texture_callback: F,
-    ) {
+    ) -> R {
         profiling::scope!("Texture::as_hal");
 
         let hub = A::hub(self);
@@ -937,7 +940,26 @@ impl Global {
         let snatch_guard = texture.device.snatchable_lock.read();
         let hal_texture = texture.raw(&snatch_guard);
 
-        hal_texture_callback(hal_texture);
+        hal_texture_callback(hal_texture)
+    }
+
+    /// # Safety
+    ///
+    /// - The raw texture view handle must not be manually destroyed
+    pub unsafe fn texture_view_as_hal<A: HalApi, F: FnOnce(Option<&A::TextureView>) -> R, R>(
+        &self,
+        id: TextureViewId,
+        hal_texture_view_callback: F,
+    ) -> R {
+        profiling::scope!("TextureView::as_hal");
+
+        let hub = A::hub(self);
+        let texture_view_opt = { hub.texture_views.try_get(id).ok().flatten() };
+        let texture_view = texture_view_opt.as_ref().unwrap();
+        let snatch_guard = texture_view.device.snatchable_lock.read();
+        let hal_texture_view = texture_view.raw(&snatch_guard);
+
+        hal_texture_view_callback(hal_texture_view)
     }
 
     /// # Safety
@@ -1005,6 +1027,29 @@ impl Global {
 
         hal_surface_callback(hal_surface)
     }
+
+    /// # Safety
+    ///
+    /// - The raw command encoder handle must not be manually destroyed
+    pub unsafe fn command_encoder_as_hal_mut<
+        A: HalApi,
+        F: FnOnce(Option<&mut A::CommandEncoder>) -> R,
+        R,
+    >(
+        &self,
+        id: CommandEncoderId,
+        hal_command_encoder_callback: F,
+    ) -> R {
+        profiling::scope!("CommandEncoder::as_hal");
+
+        let hub = A::hub(self);
+        let cmd_buf = hub.command_buffers.get(id.transmute()).unwrap();
+        let mut cmd_buf_data = cmd_buf.data.lock();
+        let cmd_buf_data = cmd_buf_data.as_mut().unwrap();
+        let cmd_buf_raw = cmd_buf_data.encoder.open().ok();
+
+        hal_command_encoder_callback(cmd_buf_raw)
+    }
 }
 
 /// A texture that has been marked as destroyed and is staged for actual deletion soon.
diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs
index 4a2c328fe..266546379 100644
--- a/wgpu-hal/src/vulkan/adapter.rs
+++ b/wgpu-hal/src/vulkan/adapter.rs
@@ -151,9 +151,32 @@ impl PhysicalDeviceFeatures {
         info
     }
 
-    /// Create a `PhysicalDeviceFeatures` that will be used to create a logical device.
+    /// Create a `PhysicalDeviceFeatures` that can be used to create a logical
+    /// device.
     ///
-    /// `requested_features` should be the same as what was used to generate `enabled_extensions`.
+    /// Return a `PhysicalDeviceFeatures` value capturing all the Vulkan
+    /// features needed for the given [`Features`], [`DownlevelFlags`], and
+    /// [`PrivateCapabilities`]. You can use the returned value's
+    /// [`add_to_device_create_builder`] method to configure a
+    /// [`DeviceCreateInfoBuilder`] to build a logical device providing those
+    /// features.
+    ///
+    /// To ensure that the returned value is able to select all the Vulkan
+    /// features needed to express `requested_features`, `downlevel_flags`, and
+    /// `private_caps`:
+    ///
+    /// - The given `enabled_extensions` set must include all the extensions
+    ///   selected by [`Adapter::required_device_extensions`] when passed
+    ///   `features`.
+    ///
+    /// - The given `device_api_version` must be the Vulkan API version of the
+    ///   physical device we will use to create the logical device.
+    ///
+    /// [`Features`]: wgt::Features
+    /// [`DownlevelFlags`]: wgt::DownlevelFlags
+    /// [`PrivateCapabilities`]: super::PrivateCapabilities
+    /// [`DeviceCreateInfoBuilder`]: vk::DeviceCreateInfoBuilder
+    /// [`Adapter::required_device_extensions`]: super::Adapter::required_device_extensions
     fn from_extensions_and_requested_features(
         device_api_version: u32,
         enabled_extensions: &[&'static CStr],
@@ -414,11 +437,16 @@ impl PhysicalDeviceFeatures {
         }
     }
 
+    /// Compute the wgpu [`Features`] and [`DownlevelFlags`] supported by a physical device.
+    ///
+    /// Given `self`, together with the instance and physical device it was
+    /// built from, and a `caps` also built from those, determine which wgpu
+    /// features and downlevel flags the device can support.
     fn to_wgpu(
         &self,
         instance: &ash::Instance,
         phd: vk::PhysicalDevice,
-        caps: &PhysicalDeviceCapabilities,
+        caps: &PhysicalDeviceProperties,
     ) -> (wgt::Features, wgt::DownlevelFlags) {
         use crate::auxil::db;
         use wgt::{DownlevelFlags as Df, Features as F};
@@ -699,11 +727,13 @@ impl PhysicalDeviceFeatures {
     }
 }
 
-/// Information gathered about a physical device.
+/// Vulkan "properties" structures gathered about a physical device.
 ///
-/// This structure holds the results from the queries we make about a
-/// [`vk::PhysicalDevice`], other than features: its device properties,
-/// supported extensions, and whatever properties those extensions provide.
+/// This structure holds the properties of a [`vk::PhysicalDevice`]:
+/// - the standard Vulkan device properties
+/// - the `VkExtensionProperties` structs for all available extensions, and
+/// - the per-extension properties structures for the available extensions that
+///   `wgpu` cares about.
 ///
 /// Generally, if you get it from any of these functions, it's stored
 /// here:
@@ -718,7 +748,7 @@ impl PhysicalDeviceFeatures {
 /// This does not include device features; for those, see
 /// [`PhysicalDeviceFeatures`].
 #[derive(Default, Debug)]
-pub struct PhysicalDeviceCapabilities {
+pub struct PhysicalDeviceProperties {
     /// Extensions supported by the `vk::PhysicalDevice`,
     /// as returned by `vkEnumerateDeviceExtensionProperties`.
     supported_extensions: Vec<vk::ExtensionProperties>,
@@ -752,10 +782,10 @@ pub struct PhysicalDeviceCapabilities {
 }
 
 // This is safe because the structs have `p_next: *mut c_void`, which we null out/never read.
-unsafe impl Send for PhysicalDeviceCapabilities {}
-unsafe impl Sync for PhysicalDeviceCapabilities {}
+unsafe impl Send for PhysicalDeviceProperties {}
+unsafe impl Sync for PhysicalDeviceProperties {}
 
-impl PhysicalDeviceCapabilities {
+impl PhysicalDeviceProperties {
     pub fn properties(&self) -> vk::PhysicalDeviceProperties {
         self.properties
     }
@@ -994,9 +1024,9 @@ impl super::InstanceShared {
     fn inspect(
         &self,
         phd: vk::PhysicalDevice,
-    ) -> (PhysicalDeviceCapabilities, PhysicalDeviceFeatures) {
+    ) -> (PhysicalDeviceProperties, PhysicalDeviceFeatures) {
         let capabilities = {
-            let mut capabilities = PhysicalDeviceCapabilities::default();
+            let mut capabilities = PhysicalDeviceProperties::default();
             capabilities.supported_extensions =
                 unsafe { self.raw.enumerate_device_extension_properties(phd).unwrap() };
             capabilities.properties = unsafe { self.raw.get_physical_device_properties(phd) };
@@ -1393,7 +1423,7 @@ impl super::Adapter {
         self.raw
     }
 
-    pub fn physical_device_capabilities(&self) -> &PhysicalDeviceCapabilities {
+    pub fn physical_device_capabilities(&self) -> &PhysicalDeviceProperties {
         &self.phd_capabilities
     }
 
@@ -1418,7 +1448,20 @@ impl super::Adapter {
         supported_extensions
     }
 
-    /// `features` must be the same features used to create `enabled_extensions`.
+    /// Create a `PhysicalDeviceFeatures` for opening a logical device with
+    /// `features` from this adapter.
+    ///
+    /// The given `enabled_extensions` set must include all the extensions
+    /// selected by [`required_device_extensions`] when passed `features`.
+    /// Otherwise, the `PhysicalDeviceFeatures` value may not be able to select
+    /// all the Vulkan features needed to represent `features` and this
+    /// adapter's characteristics.
+    ///
+    /// Typically, you'd simply call `required_device_extensions`, and then pass
+    /// its return value and the feature set you gave it directly to this
+    /// function. But it's fine to add more extensions to the list.
+    ///
+    /// [`required_device_extensions`]: Self::required_device_extensions
     pub fn physical_device_features(
         &self,
         enabled_extensions: &[&'static CStr],
diff --git a/wgpu-hal/src/vulkan/mod.rs b/wgpu-hal/src/vulkan/mod.rs
index 9e8ba0a74..d969c887d 100644
--- a/wgpu-hal/src/vulkan/mod.rs
+++ b/wgpu-hal/src/vulkan/mod.rs
@@ -189,7 +189,7 @@ pub struct Adapter {
     instance: Arc<InstanceShared>,
     //queue_families: Vec<vk::QueueFamilyProperties>,
     known_memory_flags: vk::MemoryPropertyFlags,
-    phd_capabilities: adapter::PhysicalDeviceCapabilities,
+    phd_capabilities: adapter::PhysicalDeviceProperties,
     //phd_features: adapter::PhysicalDeviceFeatures,
     downlevel_flags: wgt::DownlevelFlags,
     private_caps: PrivateCapabilities,
@@ -413,6 +413,15 @@ pub struct TextureView {
     attachment: FramebufferAttachment,
 }
 
+impl TextureView {
+    /// # Safety
+    ///
+    /// - The image view handle must not be manually destroyed
+    pub unsafe fn raw_handle(&self) -> vk::ImageView {
+        self.raw
+    }
+}
+
 #[derive(Debug)]
 pub struct Sampler {
     raw: vk::Sampler,
@@ -481,6 +490,15 @@ pub struct CommandEncoder {
     end_of_pass_timer_query: Option<(vk::QueryPool, u32)>,
 }
 
+impl CommandEncoder {
+    /// # Safety
+    ///
+    /// - The command buffer handle must not be manually destroyed
+    pub unsafe fn raw_handle(&self) -> vk::CommandBuffer {
+        self.active
+    }
+}
+
 impl fmt::Debug for CommandEncoder {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         f.debug_struct("CommandEncoder")
diff --git a/wgpu/src/backend/wgpu_core.rs b/wgpu/src/backend/wgpu_core.rs
index c73daba2a..98f1ca1de 100644
--- a/wgpu/src/backend/wgpu_core.rs
+++ b/wgpu/src/backend/wgpu_core.rs
@@ -22,8 +22,11 @@ use std::{
     slice,
     sync::Arc,
 };
-use wgc::command::{bundle_ffi::*, compute_ffi::*, render_ffi::*};
-use wgc::device::DeviceLostClosure;
+use wgc::{
+    command::{bundle_ffi::*, compute_ffi::*, render_ffi::*},
+    device::DeviceLostClosure,
+    id::{CommandEncoderId, TextureViewId},
+};
 use wgt::WasmNotSendSync;
 
 const LABEL: &str = "label";
@@ -207,14 +210,51 @@ impl ContextWgpuCore {
         }
     }
 
-    pub unsafe fn texture_as_hal<A: wgc::hal_api::HalApi, F: FnOnce(Option<&A::Texture>)>(
+    pub unsafe fn texture_as_hal<
+        A: wgc::hal_api::HalApi,
+        F: FnOnce(Option<&A::Texture>) -> R,
+        R,
+    >(
         &self,
         texture: &Texture,
         hal_texture_callback: F,
-    ) {
+    ) -> R {
+        unsafe {
+            self.0
+                .texture_as_hal::<A, F, R>(texture.id, hal_texture_callback)
+        }
+    }
+
+    pub unsafe fn texture_view_as_hal<
+        A: wgc::hal_api::HalApi,
+        F: FnOnce(Option<&A::TextureView>) -> R,
+        R,
+    >(
+        &self,
+        texture_view_id: TextureViewId,
+        hal_texture_view_callback: F,
+    ) -> R {
         unsafe {
             self.0
-                .texture_as_hal::<A, F>(texture.id, hal_texture_callback)
+                .texture_view_as_hal::<A, F, R>(texture_view_id, hal_texture_view_callback)
+        }
+    }
+
+    /// This method will start the wgpu_core level command recording.
+    pub unsafe fn command_encoder_as_hal_mut<
+        A: wgc::hal_api::HalApi,
+        F: FnOnce(Option<&mut A::CommandEncoder>) -> R,
+        R,
+    >(
+        &self,
+        command_encoder_id: CommandEncoderId,
+        hal_command_encoder_callback: F,
+    ) -> R {
+        unsafe {
+            self.0.command_encoder_as_hal_mut::<A, F, R>(
+                command_encoder_id,
+                hal_command_encoder_callback,
+            )
         }
     }
 
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index d9ca4b521..ecf5f88d8 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -3115,10 +3115,10 @@ impl Texture {
     ///
     /// - The raw handle obtained from the hal Texture must not be manually destroyed
     #[cfg(wgpu_core)]
-    pub unsafe fn as_hal<A: wgc::hal_api::HalApi, F: FnOnce(Option<&A::Texture>)>(
+    pub unsafe fn as_hal<A: wgc::hal_api::HalApi, F: FnOnce(Option<&A::Texture>) -> R, R>(
         &self,
         hal_texture_callback: F,
-    ) {
+    ) -> R {
         let texture = self.data.as_ref().downcast_ref().unwrap();
 
         if let Some(ctx) = self
@@ -3126,7 +3126,7 @@ impl Texture {
             .as_any()
             .downcast_ref::<crate::backend::ContextWgpuCore>()
         {
-            unsafe { ctx.texture_as_hal::<A, F>(texture, hal_texture_callback) }
+            unsafe { ctx.texture_as_hal::<A, F, R>(texture, hal_texture_callback) }
         } else {
             hal_texture_callback(None)
         }
@@ -3470,6 +3470,36 @@ impl CommandEncoder {
             destination_offset,
         )
     }
+
+    /// Returns the inner hal CommandEncoder using a callback. The hal command encoder will be `None` if the
+    /// backend type argument does not match with this wgpu CommandEncoder
+    ///
+    /// This method will start the wgpu_core level command recording.
+    ///
+    /// # Safety
+    ///
+    /// - The raw handle obtained from the hal CommandEncoder must not be manually destroyed
+    #[cfg(wgpu_core)]
+    pub unsafe fn as_hal_mut<
+        A: wgc::hal_api::HalApi,
+        F: FnOnce(Option<&mut A::CommandEncoder>) -> R,
+        R,
+    >(
+        &mut self,
+        hal_command_encoder_callback: F,
+    ) -> Option<R> {
+        use core::id::CommandEncoderId;
+
+        self.context
+            .as_any()
+            .downcast_ref::<crate::backend::ContextWgpuCore>()
+            .map(|ctx| unsafe {
+                ctx.command_encoder_as_hal_mut::<A, F, R>(
+                    CommandEncoderId::from(self.id.unwrap()),
+                    hal_command_encoder_callback,
+                )
+            })
+    }
 }
 
 /// [`Features::TIMESTAMP_QUERY_INSIDE_ENCODERS`] must be enabled on the device in order to call these functions.
@@ -5021,6 +5051,34 @@ impl TextureView {
     pub fn global_id(&self) -> Id<Self> {
         Id(self.id.global_id(), PhantomData)
     }
+
+    /// Returns the inner hal TextureView using a callback. The hal texture will be `None` if the
+    /// backend type argument does not match with this wgpu Texture
+    ///
+    /// # Safety
+    ///
+    /// - The raw handle obtained from the hal TextureView must not be manually destroyed
+    #[cfg(wgpu_core)]
+    pub unsafe fn as_hal<A: wgc::hal_api::HalApi, F: FnOnce(Option<&A::TextureView>) -> R, R>(
+        &self,
+        hal_texture_view_callback: F,
+    ) -> R {
+        use core::id::TextureViewId;
+
+        let texture_view_id = TextureViewId::from(self.id);
+
+        if let Some(ctx) = self
+            .context
+            .as_any()
+            .downcast_ref::<crate::backend::ContextWgpuCore>()
+        {
+            unsafe {
+                ctx.texture_view_as_hal::<A, F, R>(texture_view_id, hal_texture_view_callback)
+            }
+        } else {
+            hal_texture_view_callback(None)
+        }
+    }
 }
 
 impl Sampler {


Test patch:
diff --git a/naga/tests/in/binding-buffer-arrays.wgsl b/naga/tests/in/binding-buffer-arrays.wgsl
index a76d52c20..fb2562396 100644
--- a/naga/tests/in/binding-buffer-arrays.wgsl
+++ b/naga/tests/in/binding-buffer-arrays.wgsl
@@ -2,7 +2,7 @@ struct UniformIndex {
     index: u32
 }
 
-struct Foo { x: u32 }
+struct Foo { x: u32, far: array<i32> }
 @group(0) @binding(0)
 var<storage, read> storage_array: binding_array<Foo, 1>;
 @group(0) @binding(10)
@@ -23,5 +23,9 @@ fn main(fragment_in: FragmentIn) -> @location(0) u32 {
     u1 += storage_array[uniform_index].x;
     u1 += storage_array[non_uniform_index].x;
 
+    u1 += arrayLength(&storage_array[0].far);
+    u1 += arrayLength(&storage_array[uniform_index].far);
+    u1 += arrayLength(&storage_array[non_uniform_index].far);
+
     return u1;
 }
diff --git a/naga/tests/out/spv/binding-buffer-arrays.spvasm b/naga/tests/out/spv/binding-buffer-arrays.spvasm
index 050372036..8595962ce 100644
--- a/naga/tests/out/spv/binding-buffer-arrays.spvasm
+++ b/naga/tests/out/spv/binding-buffer-arrays.spvasm
@@ -1,99 +1,120 @@
 ; SPIR-V
 ; Version: 1.1
 ; Generator: rspirv
-; Bound: 61
+; Bound: 76
 OpCapability Shader
 OpCapability ShaderNonUniform
 OpExtension "SPV_KHR_storage_buffer_storage_class"
 OpExtension "SPV_EXT_descriptor_indexing"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %23 "main" %18 %21
-OpExecutionMode %23 OriginUpperLeft
+OpEntryPoint Fragment %25 "main" %20 %23
+OpExecutionMode %25 OriginUpperLeft
 OpMemberDecorate %4 0 Offset 0
-OpMemberDecorate %5 0 Offset 0
-OpMemberDecorate %8 0 Offset 0
-OpDecorate %9 NonWritable
-OpDecorate %9 DescriptorSet 0
-OpDecorate %9 Binding 0
-OpDecorate %5 Block
-OpDecorate %13 DescriptorSet 0
-OpDecorate %13 Binding 10
-OpDecorate %14 Block
-OpMemberDecorate %14 0 Offset 0
-OpDecorate %18 Location 0
-OpDecorate %18 Flat
-OpDecorate %21 Location 0
-OpDecorate %53 NonUniform
+OpDecorate %6 ArrayStride 4
+OpMemberDecorate %7 0 Offset 0
+OpMemberDecorate %7 1 Offset 4
+OpDecorate %7 Block
+OpMemberDecorate %10 0 Offset 0
+OpDecorate %11 NonWritable
+OpDecorate %11 DescriptorSet 0
+OpDecorate %11 Binding 0
+OpDecorate %7 Block
+OpDecorate %15 DescriptorSet 0
+OpDecorate %15 Binding 10
+OpDecorate %16 Block
+OpMemberDecorate %16 0 Offset 0
+OpDecorate %20 Location 0
+OpDecorate %20 Flat
+OpDecorate %23 Location 0
+OpDecorate %55 NonUniform
 %2 = OpTypeVoid
 %3 = OpTypeInt 32 0
 %4 = OpTypeStruct %3
-%5 = OpTypeStruct %3
-%7 = OpConstant  %3  1
-%6 = OpTypeArray %5 %7
-%8 = OpTypeStruct %3
-%12 = OpConstant  %3  10
-%11 = OpTypeArray %5 %12
-%10 = OpTypePointer StorageBuffer %11
-%9 = OpVariable  %10  StorageBuffer
-%14 = OpTypeStruct %4
-%15 = OpTypePointer Uniform %14
-%13 = OpVariable  %15  Uniform
-%19 = OpTypePointer Input %3
-%18 = OpVariable  %19  Input
-%22 = OpTypePointer Output %3
-%21 = OpVariable  %22  Output
-%24 = OpTypeFunction %2
-%25 = OpTypePointer Uniform %4
-%26 = OpConstant  %3  0
-%28 = OpTypePointer StorageBuffer %6
-%30 = OpTypePointer Function %3
-%32 = OpTypePointer Uniform %3
-%36 = OpTypePointer StorageBuffer %5
-%37 = OpTypePointer StorageBuffer %3
-%43 = OpTypeBool
-%45 = OpConstantNull  %3
-%23 = OpFunction  %2  None %24
-%16 = OpLabel
-%29 = OpVariable  %30  Function %26
-%20 = OpLoad  %3  %18
-%17 = OpCompositeConstruct  %8  %20
-%27 = OpAccessChain  %25  %13 %26
-OpBranch %31
-%31 = OpLabel
-%33 = OpAccessChain  %32  %27 %26
-%34 = OpLoad  %3  %33
-%35 = OpCompositeExtract  %3  %17 0
-%38 = OpAccessChain  %37  %9 %26 %26
-%39 = OpLoad  %3  %38
-%40 = OpLoad  %3  %29
-%41 = OpIAdd  %3  %40 %39
-OpStore %29 %41
-%42 = OpULessThan  %43  %34 %7
-OpSelectionMerge %46 None
-OpBranchConditional %42 %47 %46
-%47 = OpLabel
-%44 = OpAccessChain  %37  %9 %34 %26
-%48 = OpLoad  %3  %44
-OpBranch %46
-%46 = OpLabel
-%49 = OpPhi  %3  %45 %31 %48 %47
-%50 = OpLoad  %3  %29
-%51 = OpIAdd  %3  %50 %49
-OpStore %29 %51
-%52 = OpULessThan  %43  %35 %7
-OpSelectionMerge %54 None
-OpBranchConditional %52 %55 %54
-%55 = OpLabel
-%53 = OpAccessChain  %37  %9 %35 %26
-%56 = OpLoad  %3  %53
-OpBranch %54
-%54 = OpLabel
-%57 = OpPhi  %3  %45 %46 %56 %55
-%58 = OpLoad  %3  %29
-%59 = OpIAdd  %3  %58 %57
-OpStore %29 %59
-%60 = OpLoad  %3  %29
-OpStore %21 %60
+%5 = OpTypeInt 32 1
+%6 = OpTypeRuntimeArray %5
+%7 = OpTypeStruct %3 %6
+%9 = OpConstant  %3  1
+%8 = OpTypeArray %7 %9
+%10 = OpTypeStruct %3
+%14 = OpConstant  %3  10
+%13 = OpTypeArray %7 %14
+%12 = OpTypePointer StorageBuffer %13
+%11 = OpVariable  %12  StorageBuffer
+%16 = OpTypeStruct %4
+%17 = OpTypePointer Uniform %16
+%15 = OpVariable  %17  Uniform
+%21 = OpTypePointer Input %3
+%20 = OpVariable  %21  Input
+%24 = OpTypePointer Output %3
+%23 = OpVariable  %24  Output
+%26 = OpTypeFunction %2
+%27 = OpTypePointer Uniform %4
+%28 = OpConstant  %3  0
+%30 = OpTypePointer StorageBuffer %8
+%32 = OpTypePointer Function %3
+%34 = OpTypePointer Uniform %3
+%38 = OpTypePointer StorageBuffer %7
+%39 = OpTypePointer StorageBuffer %3
+%45 = OpTypeBool
+%47 = OpConstantNull  %3
+%62 = OpTypePointer StorageBuffer %6
+%25 = OpFunction  %2  None %26
+%18 = OpLabel
+%31 = OpVariable  %32  Function %28
+%22 = OpLoad  %3  %20
+%19 = OpCompositeConstruct  %10  %22
+%29 = OpAccessChain  %27  %15 %28
+OpBranch %33
+%33 = OpLabel
+%35 = OpAccessChain  %34  %29 %28
+%36 = OpLoad  %3  %35
+%37 = OpCompositeExtract  %3  %19 0
+%40 = OpAccessChain  %39  %11 %28 %28
+%41 = OpLoad  %3  %40
+%42 = OpLoad  %3  %31
+%43 = OpIAdd  %3  %42 %41
+OpStore %31 %43
+%44 = OpULessThan  %45  %36 %9
+OpSelectionMerge %48 None
+OpBranchConditional %44 %49 %48
+%49 = OpLabel
+%46 = OpAccessChain  %39  %11 %36 %28
+%50 = OpLoad  %3  %46
+OpBranch %48
+%48 = OpLabel
+%51 = OpPhi  %3  %47 %33 %50 %49
+%52 = OpLoad  %3  %31
+%53 = OpIAdd  %3  %52 %51
+OpStore %31 %53
+%54 = OpULessThan  %45  %37 %9
+OpSelectionMerge %56 None
+OpBranchConditional %54 %57 %56
+%57 = OpLabel
+%55 = OpAccessChain  %39  %11 %37 %28
+%58 = OpLoad  %3  %55
+OpBranch %56
+%56 = OpLabel
+%59 = OpPhi  %3  %47 %48 %58 %57
+%60 = OpLoad  %3  %31
+%61 = OpIAdd  %3  %60 %59
+OpStore %31 %61
+%63 = OpAccessChain  %38  %11 %28
+%64 = OpArrayLength  %3  %63 1
+%65 = OpLoad  %3  %31
+%66 = OpIAdd  %3  %65 %64
+OpStore %31 %66
+%67 = OpAccessChain  %38  %11 %36
+%68 = OpArrayLength  %3  %67 1
+%69 = OpLoad  %3  %31
+%70 = OpIAdd  %3  %69 %68
+OpStore %31 %70
+%71 = OpAccessChain  %38  %11 %37
+%72 = OpArrayLength  %3  %71 1
+%73 = OpLoad  %3  %31
+%74 = OpIAdd  %3  %73 %72
+OpStore %31 %74
+%75 = OpLoad  %3  %31
+OpStore %23 %75
 OpReturn
 OpFunctionEnd
\ No newline at end of file
diff --git a/naga/tests/out/wgsl/binding-buffer-arrays.wgsl b/naga/tests/out/wgsl/binding-buffer-arrays.wgsl
index 317a38623..a11ef3d0f 100644
--- a/naga/tests/out/wgsl/binding-buffer-arrays.wgsl
+++ b/naga/tests/out/wgsl/binding-buffer-arrays.wgsl
@@ -4,6 +4,7 @@ struct UniformIndex {
 
 struct Foo {
     x: u32,
+    far: array<i32>,
 }
 
 struct FragmentIn {
@@ -30,6 +31,12 @@ fn main(fragment_in: FragmentIn) -> @location(0) @interpolate(flat) u32 {
     let _e22 = storage_array[non_uniform_index].x;
     let _e23 = u1_;
     u1_ = (_e23 + _e22);
-    let _e25 = u1_;
-    return _e25;
+    let _e29 = u1_;
+    u1_ = (_e29 + arrayLength((&storage_array[0].far)));
+    let _e35 = u1_;
+    u1_ = (_e35 + arrayLength((&storage_array[uniform_index].far)));
+    let _e41 = u1_;
+    u1_ = (_e41 + arrayLength((&storage_array[non_uniform_index].far)));
+    let _e43 = u1_;
+    return _e43;
 }
diff --git a/tests/tests/device.rs b/tests/tests/device.rs
index fd393b63b..ff596d091 100644
--- a/tests/tests/device.rs
+++ b/tests/tests/device.rs
@@ -776,3 +776,42 @@ fn vs_main() -> @builtin(position) vec4<f32> {
             ],
         });
     });
+
+#[gpu_test]
+static DEVICE_DESTROY_THEN_BUFFER_CLEANUP: GpuTestConfiguration = GpuTestConfiguration::new()
+    .parameters(TestParameters::default())
+    .run_sync(|ctx| {
+        // When a device is destroyed, its resources should be released,
+        // without causing a deadlock.
+
+        // Create a buffer to be left around until the device is destroyed.
+        let _buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
+            label: None,
+            size: 256,
+            usage: wgpu::BufferUsages::MAP_READ,
+            mapped_at_creation: false,
+        });
+
+        // Create a texture to be left around until the device is destroyed.
+        let texture_extent = wgpu::Extent3d {
+            width: 512,
+            height: 512,
+            depth_or_array_layers: 1,
+        };
+        let _texture = ctx.device.create_texture(&wgpu::TextureDescriptor {
+            label: None,
+            size: texture_extent,
+            mip_level_count: 2,
+            sample_count: 1,
+            dimension: wgpu::TextureDimension::D2,
+            format: wgpu::TextureFormat::Rg8Uint,
+            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
+            view_formats: &[],
+        });
+
+        // Destroy the device.
+        ctx.device.destroy();
+
+        // Poll the device, which should try to clean up its resources.
+        ctx.instance.poll_all(true);
+    });