Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cts_runner/fail.lst
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,14 @@ webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoder
webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoderType="render%20bundle";state="destroyed";* // https://github.com/gfx-rs/wgpu/issues/7881
webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoderType="render%20pass";state="destroyed";* // https://github.com/gfx-rs/wgpu/issues/7881
webgpu:api,validation,encoding,cmds,setBindGroup:u32array_start_and_length:* // deno unwrap
webgpu:api,validation,encoding,cmds,setImmediates:* // 0%, feature not implemented
webgpu:api,validation,encoding,cmds,setImmediates:* // This should pass. Wait for upstream fix: https://github.com/gpuweb/cts/pull/4646
webgpu:api,validation,encoding,encoder_open_state:* // https://github.com/gfx-rs/wgpu/issues/7857
webgpu:api,validation,encoding,queries,resolveQuerySet:* // 93%, https://github.com/gfx-rs/wgpu/issues/7881
webgpu:api,validation,encoding,render_bundle:* // 81%, readonly flag normalization mismatch
webgpu:api,validation,image_copy,buffer_texture_copies:* // https://github.com/gfx-rs/wgpu/issues/7946
webgpu:api,validation,layout_shader_compat:pipeline_layout_shader_exact_match:* // dx12, https://bugzilla.mozilla.org/show_bug.cgi?id=2017725
webgpu:api,validation,non_filterable_texture:non_filterable_texture_with_filtering_sampler:* // 80%, depth textures with filtering samplers
webgpu:api,validation,pipeline,immediates:* // 0%, https://github.com/gfx-rs/wgpu/issues/8556
webgpu:api,validation,query_set,create:count:* // 0%, wgpu incorrectly rejects zero-count query sets
webgpu:api,validation,queue,buffer_mapped:* // ***, vulkan
webgpu:api,validation,queue,destroyed,* // 71%, writeBuffer/writeTexture return value, destroyed query set
Expand Down
3 changes: 0 additions & 3 deletions cts_runner/skip.lst
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@
// The `cts_runner` integration test `lst_files_are_sorted` verifies that test selectors
// appear in this file in order -- including ones in comments.

webgpu:api,validation,createPipelineLayout:immediate_data_size:* // immediates, https://github.com/gfx-rs/wgpu/issues/8556
webgpu:api,validation,dispatch:* // linear indexing, https://github.com/gfx-rs/wgpu/issues/9296
webgpu:api,validation,encoding,programmable,pipeline_immediate:* // immediates, https://github.com/gfx-rs/wgpu/issues/8556
webgpu:api,validation,gpu_external_texture_expiration:* // external texture
webgpu:api,validation,pipeline,immediates:pipeline_creation_immediate_size_mismatch:* // immediates, https://github.com/gfx-rs/wgpu/issues/8556
webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:* // external texture

webgpu:shader,validation,expression,call,builtin,quadBroadcast:* // subgroups, https://github.com/gfx-rs/wgpu/issues/8722
Expand Down
40 changes: 38 additions & 2 deletions cts_runner/test.lst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ webgpu:api,operation,command_buffer,image_copy:undefined_params:initMethod="Writ
webgpu:api,operation,command_buffer,image_copy:undefined_params:initMethod="WriteTexture";checkMethod="PartialCopyT2B";dimension="1d"
fails-if(dx12) webgpu:api,operation,command_buffer,image_copy:undefined_params:initMethod="WriteTexture";checkMethod="PartialCopyT2B";dimension="2d"
webgpu:api,operation,command_buffer,image_copy:undefined_params:initMethod="WriteTexture";checkMethod="PartialCopyT2B";dimension="3d"
fails-if(dx12) webgpu:api,operation,command_buffer,programmable,immediate:*
webgpu:api,operation,compute_pipeline,overrides:*
//FAIL: webgpu:api,operation,compute,basic:large_dispatch:*
webgpu:api,operation,compute,basic:memcpy:*
Expand Down Expand Up @@ -196,13 +197,48 @@ webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoder
webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoderType="render%20pass";state="invalid";*
webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoderType="render%20pass";state="valid";*
webgpu:api,validation,encoding,createRenderBundleEncoder:*
webgpu:api,validation,encoding,encoder_open_state:compute_pass_commands:*
webgpu:api,validation,encoding,encoder_open_state:compute_pass_commands:command="dispatchWorkgroups"
webgpu:api,validation,encoding,encoder_open_state:compute_pass_commands:command="dispatchWorkgroupsIndirect"
webgpu:api,validation,encoding,encoder_open_state:compute_pass_commands:command="insertDebugMarker"
webgpu:api,validation,encoding,encoder_open_state:compute_pass_commands:command="popDebugGroup"
webgpu:api,validation,encoding,encoder_open_state:compute_pass_commands:command="pushDebugGroup"
webgpu:api,validation,encoding,encoder_open_state:compute_pass_commands:command="setBindGroup"
//FAIL: webgpu:api,validation,encoding,encoder_open_state:compute_pass_commands:command="setImmediates"
// https://github.com/gfx-rs/wgpu/issues/8556
webgpu:api,validation,encoding,encoder_open_state:compute_pass_commands:command="setPipeline"
webgpu:api,validation,encoding,encoder_open_state:non_pass_commands:*
//FAIL: webgpu:api,validation,encoding,encoder_open_state:render_bundle_commands:*
// https://github.com/gfx-rs/wgpu/issues/7857
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:*
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="beginOcclusionQuery"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="draw"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="drawIndexed"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="drawIndexedIndirect"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="drawIndirect"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="endOcclusionQuery"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="executeBundles"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="insertDebugMarker"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="multiDrawIndexedIndirect"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="multiDrawIndirect"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="popDebugGroup"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="pushDebugGroup"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="setBindGroup"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="setBlendConstant"
//FAIL: webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="setImmediates"
// https://github.com/gfx-rs/wgpu/issues/8556
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="setIndexBuffer"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="setPipeline"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="setScissorRect"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="setStencilReference"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="setVertexBuffer"
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:command="setViewport"
webgpu:api,validation,encoding,encoder_state:*
webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:*
fails-if(dx12) webgpu:api,validation,encoding,programmable,pipeline_immediate:overprovisioned_immediate_data:*
fails-if(dx12) webgpu:api,validation,encoding,programmable,pipeline_immediate:render_bundle_execution_state_invalidation:*
//FAIL: webgpu:api,validation,encoding,programmable,pipeline_immediate:required_slots_set:*
// 97%, scenario="multiple_variables";usage="partial" fails.
// https://github.com/gfx-rs/wgpu/issues/8556
fails-if(dx12) webgpu:api,validation,encoding,programmable,pipeline_immediate:unused_variable:*
webgpu:api,validation,encoding,queries,begin_end:nesting:*
webgpu:api,validation,encoding,queries,begin_end:occlusion_query,*
webgpu:api,validation,encoding,queries,general:occlusion_query,*
Expand Down
5 changes: 5 additions & 0 deletions deno_webgpu/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,11 @@ impl GPUSupportedLimits {
fn maxComputeWorkgroupsPerDimension(&self) -> u32 {
self.0.max_compute_workgroups_per_dimension
}

#[getter]
fn maxImmediateSize(&self) -> u32 {
self.0.max_immediate_size
}
}

pub struct GPUSupportedFeatures(v8::Global<v8::Value>);
Expand Down
26 changes: 26 additions & 0 deletions deno_webgpu/compute_pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ use deno_core::webidl::WebIdlConverter;
use deno_core::webidl::WebIdlError;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use deno_error::JsErrorBox;

use crate::error::GPUGenericError;
use crate::get_data_slice;
use crate::Instance;

pub struct GPUComputePassEncoder {
Expand Down Expand Up @@ -230,6 +232,30 @@ impl GPUComputePassEncoder {

Ok(())
}

#[required(2)]
#[undefined]
fn set_immediates<'a>(
&self,
scope: &mut v8::HandleScope<'a>,
#[webidl(options(enforce_range = true))] offset: u32,
data_arg: v8::Local<'a, v8::Value>,
#[webidl(default = 0, options(enforce_range = true))] data_offset: u64,
#[webidl(options(enforce_range = true))] data_size: Option<u64>,
) -> Result<(), JsErrorBox> {
let data = get_data_slice(scope, data_arg, data_offset, data_size)?;

let err = self
.instance
.compute_pass_set_immediates(
&mut self.compute_pass.borrow_mut(),
offset,
data,
)
.err();
self.error_handler.push_error(err);
Ok(())
}
}

#[derive(WebIDL)]
Expand Down
2 changes: 1 addition & 1 deletion deno_webgpu/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ impl GPUDevice {
let wgpu_descriptor = wgpu_core::binding_model::PipelineLayoutDescriptor {
label: crate::transform_label(descriptor.label.clone()),
bind_group_layouts: Cow::Owned(bind_group_layouts),
immediate_size: 0,
immediate_size: descriptor.immediate_size,
};

let (id, err) = self.instance.device_create_pipeline_layout(
Expand Down
116 changes: 116 additions & 0 deletions deno_webgpu/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use deno_core::op2;
use deno_core::v8;
use deno_core::GarbageCollected;
use deno_core::OpState;
use deno_error::JsErrorBox;
use serde::de::IntoDeserializer;
use serde::Deserialize as _;
pub use wgpu_core;
Expand Down Expand Up @@ -308,3 +309,118 @@ fn transform_label<'a>(label: String) -> Option<std::borrow::Cow<'a, str>> {
Some(std::borrow::Cow::Owned(label))
}
}

fn operation_error(
message: impl Into<std::borrow::Cow<'static, str>>,
) -> JsErrorBox {
JsErrorBox::new("DOMExceptionOperationError", message)
}

/// Validate the input data (AllowSharedBufferSource) and return the slice that applied the offset and size,
/// or return `Err` if validation fails.
///
/// See also the content timeline requirements of <https://gpuweb.github.io/gpuweb/#dom-gpuqueue-writebuffer>
/// and <https://gpuweb.github.io/gpuweb/#dom-gpubindingcommandsmixin-setimmediates>
fn get_data_slice<'a>(
scope: &mut v8::HandleScope,
data_arg: v8::Local<'a, v8::Value>,
data_offset: u64,
data_size: Option<u64>,
) -> Result<&'a [u8], JsErrorBox> {
const EMPTY: &[u8] = &[];
// Per the WebGPU spec, dataOffset and size are in elements (not bytes)
// when data is a TypedArray, and in bytes otherwise.
let (buf, bytes_per_element) = if let Ok(typed_array) =
v8::Local::<v8::TypedArray>::try_from(data_arg)
{
let len = typed_array.length();
// Avoid panicking as data of zero length array is `None`.
if len == 0 {
(EMPTY, 1)
} else {
let bpe = typed_array.byte_length() / len;
let byte_offset = typed_array.byte_offset();
let byte_len = typed_array.byte_length();
let ab = typed_array.buffer(scope).unwrap();
// SAFETY: Pointer is non-null, and V8 guarantees that the
// byte_offset is within the buffer backing store.
let ptr = unsafe { ab.data().unwrap().as_ptr().add(byte_offset) };
let buf =
// SAFETY: the slice is within the bounds of the backing store
unsafe { std::slice::from_raw_parts(ptr as *const u8, byte_len) };
(buf, bpe)
}
} else if let Ok(ab) = v8::Local::<v8::ArrayBuffer>::try_from(data_arg) {
let byte_len = ab.byte_length();
// Avoid panicking as data of zero length array is `None`.
if byte_len == 0 {
(EMPTY, 1)
} else {
let ptr = ab.data().unwrap().as_ptr();
let buf =
// SAFETY: Pointer is non-null and byte_len is within the backing store.
unsafe { std::slice::from_raw_parts(ptr as *const u8, byte_len) };
(buf, 1)
}
} else if let Ok(ab) = v8::Local::<v8::SharedArrayBuffer>::try_from(data_arg)
{
let byte_len = ab.byte_length();
// Avoid panicking as data of zero length array is `None`.
if byte_len == 0 {
(EMPTY, 1)
} else {
let ptr = ab.get_backing_store().data().unwrap().as_ptr();
let buf =
// SAFETY: Pointer is non-null and byte_len is within the backing store.
unsafe { std::slice::from_raw_parts(ptr as *const u8, byte_len) };
(buf, 1)
}
} else if let Ok(view) = v8::Local::<v8::ArrayBufferView>::try_from(data_arg)
{
let byte_offset = view.byte_offset();
let byte_len = view.byte_length();
if byte_len == 0 {
(EMPTY, 1)
} else {
let ab = view.buffer(scope).unwrap();
// SAFETY: Pointer is non-null, and V8 guarantees that the
// byte_offset is within the buffer backing store.
let ptr = unsafe { ab.data().unwrap().as_ptr().add(byte_offset) };
// SAFETY: the slice is within the bounds of the backing store
let buf =
unsafe { std::slice::from_raw_parts(ptr as *const u8, byte_len) };
(buf, 1)
}
} else {
return Err(JsErrorBox::type_error(
"data must be an ArrayBuffer, SharedArrayBuffer or ArrayBufferView",
));
};

let data_offset_bytes = data_offset * bytes_per_element as u64;
let content_size_bytes = if let Some(data_size) = data_size {
data_size * bytes_per_element as u64
} else {
(buf.len() as u64)
.checked_sub(data_offset_bytes)
.ok_or(operation_error("data offset is out of bounds"))?
};
if data_offset_bytes + content_size_bytes > buf.len() as u64 {
return Err(operation_error("The end index of data is out of bounds"));
}

// Both `Queue::write_buffer` and `set_immediates` require content size to be a multiple of 4
const {
assert!(wgpu_types::COPY_BUFFER_ALIGNMENT == 4);
assert!(wgpu_types::IMMEDIATE_DATA_ALIGNMENT == 4);
}
if !content_size_bytes.is_multiple_of(4) {
return Err(operation_error("content size is not a multiple of 4"));
}

// We have validated data offset and content size are within the bounds.
let data = &buf[(data_offset_bytes as usize)
..((data_offset_bytes + content_size_bytes) as usize)];

Ok(data)
}
2 changes: 2 additions & 0 deletions deno_webgpu/pipeline_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,6 @@ pub(crate) struct GPUPipelineLayoutDescriptor {

pub bind_group_layouts:
Vec<Nullable<Ptr<super::bind_group_layout::GPUBindGroupLayout>>>,
#[webidl(default = 0)]
pub immediate_size: u32,
}
18 changes: 9 additions & 9 deletions deno_webgpu/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ use std::time::Duration;
use deno_core::cppgc::Ptr;
use deno_core::futures::channel::oneshot;
use deno_core::op2;
use deno_core::v8;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use deno_error::JsErrorBox;

use crate::buffer::GPUBuffer;
use crate::command_buffer::GPUCommandBuffer;
use crate::error::GPUGenericError;
use crate::get_data_slice;
use crate::texture::GPUTexture;
use crate::texture::GPUTextureAspect;
use crate::webidl::GPUExtent3D;
Expand Down Expand Up @@ -127,27 +129,25 @@ impl GPUQueue {

#[required(3)]
#[undefined]
fn write_buffer(
fn write_buffer<'a>(
&self,
scope: &mut v8::HandleScope<'a>,
#[webidl] buffer: Ptr<GPUBuffer>,
#[webidl(options(enforce_range = true))] buffer_offset: u64,
#[anybuffer] buf: &[u8],
data_arg: v8::Local<'a, v8::Value>,
#[webidl(default = 0, options(enforce_range = true))] data_offset: u64,
#[webidl(options(enforce_range = true))] size: Option<u64>,
) {
let data = match size {
Some(size) => {
&buf[(data_offset as usize)..((data_offset + size) as usize)]
}
None => &buf[(data_offset as usize)..],
};
) -> Result<(), JsErrorBox> {
let data = get_data_slice(scope, data_arg, data_offset, size)?;

let err = self
.instance
.queue_write_buffer(self.id, buffer.id, buffer_offset, data)
.err();

self.error_handler.push_error(err);

Ok(())
}

#[required(4)]
Expand Down
29 changes: 29 additions & 0 deletions deno_webgpu/render_bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use deno_error::JsErrorBox;

use crate::buffer::GPUBuffer;
use crate::error::GPUGenericError;
use crate::get_data_slice;
use crate::texture::GPUTextureFormat;
use crate::Instance;

Expand Down Expand Up @@ -383,6 +384,34 @@ impl GPURenderBundleEncoder {
);
Ok(())
}

#[required(2)]
#[undefined]
fn set_immediates<'a>(
&self,
scope: &mut v8::HandleScope<'a>,
#[webidl(options(enforce_range = true))] offset: u32,
data_arg: v8::Local<'a, v8::Value>,
#[webidl(default = 0, options(enforce_range = true))] data_offset: u64,
#[webidl(options(enforce_range = true))] data_size: Option<u64>,
) -> Result<(), JsErrorBox> {
let data = get_data_slice(scope, data_arg, data_offset, data_size)?;

let mut encoder = self.encoder.borrow_mut();
let encoder = encoder.as_mut().ok_or_else(|| {
JsErrorBox::generic("Encoder has already been finished")
})?;

unsafe {
wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_immediates(
encoder,
offset,
data.len().try_into().unwrap(),
data.as_ptr(),
);
}
Ok(())
}
}

#[derive(WebIDL)]
Expand Down
Loading
Loading