diff --git a/Cargo.lock b/Cargo.lock index 8ee690c25..7614ee09b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1079,7 +1079,7 @@ dependencies = [ [[package]] name = "deno_webgpu" -version = "0.110.0" +version = "0.118.0" dependencies = [ "deno_core", "raw-window-handle 0.6.1", diff --git a/Cargo.toml b/Cargo.toml index 1348caf2e..4341fe292 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.110.0", path = "./deno_webgpu" } +deno_webgpu = { version = "0.118.0", path = "./deno_webgpu" } tokio = "1.37.0" termcolor = "1.4.1" diff --git a/deno_webgpu/01_webgpu.js b/deno_webgpu/01_webgpu.js index 369d1cd9b..719a0f486 100644 --- a/deno_webgpu/01_webgpu.js +++ b/deno_webgpu/01_webgpu.js @@ -121,9 +121,10 @@ const { import * as webidl from "ext:deno_webidl/00_webidl.js"; import { + defineEventHandler, Event, EventTarget, - defineEventHandler, + setEventTargetData, } 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"; @@ -299,7 +300,6 @@ class GPUValidationError extends GPUError { this[_message] = message; } } -const GPUValidationErrorPrototype = GPUValidationError.prototype; class GPUOutOfMemoryError extends GPUError { name = "GPUOutOfMemoryError"; @@ -312,7 +312,6 @@ class GPUOutOfMemoryError extends GPUError { this[_message] = message; } } -const GPUOutOfMemoryErrorPrototype = GPUOutOfMemoryError.prototype; class GPUInternalError extends GPUError { name = "GPUInternalError"; @@ -321,7 +320,6 @@ class GPUInternalError extends GPUError { this[webidl.brand] = webidl.brand; } } -const GPUInternalErrorPrototype = GPUInternalError.prototype; class GPUUncapturedErrorEvent extends Event { #error; @@ -333,7 +331,7 @@ class GPUUncapturedErrorEvent extends Event { const prefix = "Failed to construct 'GPUUncapturedErrorEvent'"; webidl.requiredArguments(arguments.length, 2, prefix); gpuUncapturedErrorEventInitDict = webidl.converters - .gpuUncapturedErrorEventInitDict( + .GPUUncapturedErrorEventInit( gpuUncapturedErrorEventInitDict, prefix, "Argument 2", @@ -348,7 +346,6 @@ class GPUUncapturedErrorEvent extends Event { } } const GPUUncapturedErrorEventPrototype = GPUUncapturedErrorEvent.prototype; -defineEventHandler(GPUUncapturedErrorEvent.prototype, "uncapturederror"); class GPU { [webidl.brand] = webidl.brand; @@ -420,9 +417,12 @@ function createGPUAdapter(inner) { return adapter; } +const _invalid = Symbol("[[invalid]]"); class GPUAdapter { /** @type {InnerGPUAdapter} */ [_adapter]; + /** @type {bool} */ + [_invalid]; /** @returns {GPUSupportedFeatures} */ get features() { @@ -469,6 +469,12 @@ class GPUAdapter { } } + if (this[_invalid]) { + throw new TypeError( + "The adapter cannot be reused, as it has been invalidated by a device creation", + ); + } + const { rid, queueRid, features, limits } = op_webgpu_request_device( this[_adapter].rid, descriptor.label, @@ -476,16 +482,20 @@ class GPUAdapter { descriptor.requiredLimits, ); + this[_invalid] = true; + const inner = new InnerGPUDevice({ rid, adapter: this, features: createGPUSupportedFeatures(features), limits: createGPUSupportedLimits(limits), }); + const queue = createGPUQueue(descriptor.label, inner, queueRid); + inner.trackResource(queue); const device = createGPUDevice( descriptor.label, inner, - createGPUQueue(descriptor.label, inner, queueRid), + queue, ); inner.device = device; return device; @@ -497,6 +507,12 @@ class GPUAdapter { requestAdapterInfo() { webidl.assertBranded(this, GPUAdapterPrototype); + if (this[_invalid]) { + throw new TypeError( + "The adapter cannot be reused, as it has been invalidated by a device creation", + ); + } + const { vendor, architecture, @@ -977,7 +993,9 @@ class InnerGPUDevice { ); return; case "validation": - constructedError = new GPUValidationError(error.value ?? "validation error"); + constructedError = new GPUValidationError( + error.value ?? "validation error", + ); break; case "out-of-memory": constructedError = new GPUOutOfMemoryError(); @@ -996,11 +1014,13 @@ class InnerGPUDevice { ({ filter }) => filter === error.type, ); if (scope) { - scope.errors.push(constructedError); + ArrayPrototypePush(scope.errors, constructedError); } else { - this.device.dispatchEvent(new GPUUncapturedErrorEvent("uncapturederror", { - error: constructedError, - })); + this.device.dispatchEvent( + new GPUUncapturedErrorEvent("uncapturederror", { + error: constructedError, + }), + ); } } } @@ -1017,6 +1037,7 @@ function createGPUDevice(label, inner, queue) { device[_label] = label; device[_device] = inner; device[_queue] = queue; + setEventTargetData(device); return device; } @@ -1132,10 +1153,11 @@ class GPUDevice extends EventTarget { "Argument 1", ); const device = assertDevice(this, prefix, "this"); + // assign normalized size to descriptor due to createGPUTexture needs it + descriptor.size = normalizeGPUExtent3D(descriptor.size); const { rid, err } = op_webgpu_create_texture({ deviceRid: device.rid, ...descriptor, - size: normalizeGPUExtent3D(descriptor.size), }); device.pushError(err); @@ -1307,7 +1329,6 @@ class GPUDevice extends EventTarget { } else { // deno-lint-ignore prefer-primordials const rid = assertResource(resource.buffer, prefix, context); - // deno-lint-ignore prefer-primordials return { binding: entry.binding, kind: "GPUBufferBinding", @@ -1816,6 +1837,7 @@ class GPUDevice extends EventTarget { } GPUObjectBaseMixin("GPUDevice", GPUDevice); const GPUDevicePrototype = GPUDevice.prototype; +defineEventHandler(GPUDevice.prototype, "uncapturederror"); class GPUPipelineError extends DOMException { #reason; @@ -1861,6 +1883,15 @@ class GPUQueue { /** @type {number} */ [_rid]; + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + constructor() { webidl.illegalConstructor(); } @@ -5488,6 +5519,16 @@ webidl.converters["GPUExtent3D"] = (V, opts) => { if (typeof V === "object") { const method = V[SymbolIterator]; if (method !== undefined) { + // validate length of GPUExtent3D + const min = 1; + const max = 3; + if (V.length < min || V.length > max) { + throw webidl.makeException( + TypeError, + `A sequence of number used as a GPUExtent3D must have between ${min} and ${max} elements.`, + opts, + ); + } return webidl.converters["sequence"](V, opts); } return webidl.converters["GPUExtent3DDict"](V, opts); @@ -6823,6 +6864,15 @@ webidl.converters["GPUOrigin3D"] = (V, opts) => { if (typeof V === "object") { const method = V[SymbolIterator]; if (method !== undefined) { + // validate length of GPUOrigin3D + const length = 3; + if (V.length > length) { + throw webidl.makeException( + TypeError, + `A sequence of number used as a GPUOrigin3D must have at most ${length} elements.`, + opts, + ); + } return webidl.converters["sequence"](V, opts); } return webidl.converters["GPUOrigin3DDict"](V, opts); @@ -6891,6 +6941,15 @@ webidl.converters["GPUOrigin2D"] = (V, opts) => { if (typeof V === "object") { const method = V[SymbolIterator]; if (method !== undefined) { + // validate length of GPUOrigin2D + const length = 2; + if (V.length > length) { + throw webidl.makeException( + TypeError, + `A sequence of number used as a GPUOrigin2D must have at most ${length} elements.`, + opts, + ); + } return webidl.converters["sequence"](V, opts); } return webidl.converters["GPUOrigin2DDict"](V, opts); @@ -6976,6 +7035,15 @@ webidl.converters["GPUColor"] = (V, opts) => { if (typeof V === "object") { const method = V[SymbolIterator]; if (method !== undefined) { + // validate length of GPUColor + const length = 4; + if (V.length !== length) { + throw webidl.makeException( + TypeError, + `A sequence of number used as a GPUColor must have exactly ${length} elements.`, + opts, + ); + } return webidl.converters["sequence"](V, opts); } return webidl.converters["GPUColorDict"](V, opts); diff --git a/deno_webgpu/Cargo.toml b/deno_webgpu/Cargo.toml index cf05e00f9..8b56e324b 100644 --- a/deno_webgpu/Cargo.toml +++ b/deno_webgpu/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_webgpu" -version = "0.110.0" +version = "0.118.0" authors = ["the Deno authors"] edition.workspace = true license = "MIT" diff --git a/deno_webgpu/byow.rs b/deno_webgpu/byow.rs index 3042f46ee..49944ea1e 100644 --- a/deno_webgpu/byow.rs +++ b/deno_webgpu/byow.rs @@ -6,6 +6,7 @@ use deno_core::op2; use deno_core::OpState; use deno_core::ResourceId; use std::ffi::c_void; +#[cfg(any(target_os = "linux", target_os = "macos"))] use std::ptr::NonNull; use crate::surface::WebGpuSurface; @@ -37,6 +38,7 @@ pub fn op_webgpu_surface_create( } let (win_handle, display_handle) = raw_window(system, p1, p2)?; + // SAFETY: see above comment let surface = unsafe { instance.instance_create_surface(display_handle, win_handle, None)? }; let rid = state @@ -82,9 +84,10 @@ fn raw_window( } 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; + let mut handle = raw_window_handle::Win32WindowHandle::new( + std::num::NonZeroIsize::new(window as isize).ok_or(type_error("window is null"))?, + ); + handle.hinstance = std::num::NonZeroIsize::new(hinstance as isize); raw_window_handle::RawWindowHandle::Win32(handle) }; @@ -99,17 +102,30 @@ fn raw_window( window: *const c_void, display: *const c_void, ) -> Result { - if system != "x11" { + let (win_handle, display_handle); + if system == "x11" { + win_handle = raw_window_handle::RawWindowHandle::Xlib( + raw_window_handle::XlibWindowHandle::new(window as *mut c_void as _), + ); + + display_handle = raw_window_handle::RawDisplayHandle::Xlib( + raw_window_handle::XlibDisplayHandle::new(NonNull::new(display as *mut c_void), 0), + ); + } else if system == "wayland" { + win_handle = raw_window_handle::RawWindowHandle::Wayland( + raw_window_handle::WaylandWindowHandle::new( + NonNull::new(window as *mut c_void).ok_or(type_error("window is null"))?, + ), + ); + + display_handle = raw_window_handle::RawDisplayHandle::Wayland( + raw_window_handle::WaylandDisplayHandle::new( + NonNull::new(display as *mut c_void).ok_or(type_error("display is null"))?, + ), + ); + } else { 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/lib.rs b/deno_webgpu/lib.rs index 453d4ea7e..a9d36afdc 100644 --- a/deno_webgpu/lib.rs +++ b/deno_webgpu/lib.rs @@ -667,7 +667,7 @@ pub fn op_webgpu_request_device( #[serde] required_limits: Option, ) -> Result { let mut state = state.borrow_mut(); - let adapter_resource = state.resource_table.get::(adapter_rid)?; + let adapter_resource = state.resource_table.take::(adapter_rid)?; let adapter = adapter_resource.1; let instance = state.borrow::(); @@ -684,6 +684,7 @@ pub fn op_webgpu_request_device( None, None )); + adapter_resource.close(); if let Some(err) = maybe_err { return Err(DomExceptionOperationError::new(&err.to_string()).into()); } @@ -724,12 +725,13 @@ pub fn op_webgpu_request_adapter_info( state: Rc>, #[smi] adapter_rid: ResourceId, ) -> Result { - let state = state.borrow_mut(); - let adapter_resource = state.resource_table.get::(adapter_rid)?; + let mut state = state.borrow_mut(); + let adapter_resource = state.resource_table.take::(adapter_rid)?; let adapter = adapter_resource.1; let instance = state.borrow::(); let info = gfx_select!(adapter => instance.adapter_get_info(adapter))?; + adapter_resource.close(); Ok(GPUAdapterInfo { vendor: info.vendor.to_string(), diff --git a/deno_webgpu/pipeline.rs b/deno_webgpu/pipeline.rs index e8b5a71cf..fc3a92bfc 100644 --- a/deno_webgpu/pipeline.rs +++ b/deno_webgpu/pipeline.rs @@ -76,7 +76,7 @@ pub enum GPUPipelineLayoutOrGPUAutoLayoutMode { pub struct GpuProgrammableStage { module: ResourceId, entry_point: Option, - constants: HashMap, + constants: Option>, } #[op2] @@ -112,7 +112,7 @@ pub fn op_webgpu_create_compute_pipeline( stage: wgpu_core::pipeline::ProgrammableStageDescriptor { module: compute_shader_module_resource.1, entry_point: compute.entry_point.map(Cow::from), - constants: Cow::Owned(compute.constants), + constants: Cow::Owned(compute.constants.unwrap_or_default()), zero_initialize_workgroup_memory: true, }, }; @@ -280,8 +280,8 @@ impl<'a> From for wgpu_core::pipeline::VertexBufferLayout #[serde(rename_all = "camelCase")] struct GpuVertexState { module: ResourceId, - entry_point: String, - constants: HashMap, + entry_point: Option, + constants: Option>, buffers: Vec>, } @@ -308,8 +308,8 @@ impl From for wgpu_types::MultisampleState { struct GpuFragmentState { targets: Vec>, module: u32, - entry_point: String, - constants: HashMap, + entry_point: Option, + constants: Option>, } #[derive(Deserialize)] @@ -358,8 +358,8 @@ pub fn op_webgpu_create_render_pipeline( Some(wgpu_core::pipeline::FragmentState { stage: wgpu_core::pipeline::ProgrammableStageDescriptor { module: fragment_shader_module_resource.1, - entry_point: Some(Cow::from(fragment.entry_point)), - constants: Cow::Owned(fragment.constants), + entry_point: fragment.entry_point.map(Cow::from), + constants: Cow::Owned(fragment.constants.unwrap_or_default()), // Required to be true for WebGPU zero_initialize_workgroup_memory: true, }, @@ -383,8 +383,8 @@ pub fn op_webgpu_create_render_pipeline( vertex: wgpu_core::pipeline::VertexState { stage: wgpu_core::pipeline::ProgrammableStageDescriptor { module: vertex_shader_module_resource.1, - entry_point: Some(Cow::Owned(args.vertex.entry_point)), - constants: Cow::Owned(args.vertex.constants), + entry_point: args.vertex.entry_point.map(Cow::Owned), + constants: Cow::Owned(args.vertex.constants.unwrap_or_default()), // Required to be true for WebGPU zero_initialize_workgroup_memory: true, },