use std::sync::atomic::AtomicBool; use wgpu_test::{ fail, gpu_test, FailureCase, GpuTestConfiguration, TestParameters, TestingContext, }; #[gpu_test] static CROSS_DEVICE_BIND_GROUP_USAGE: GpuTestConfiguration = GpuTestConfiguration::new() .parameters(TestParameters::default().expect_fail(FailureCase::always())) .run_async(|ctx| async move { // Create a bind group using a layout from another device. This should be a validation // error but currently crashes. let (device2, _) = pollster::block_on(ctx.adapter.request_device(&Default::default())).unwrap(); { let bind_group_layout = device2.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[], }); let _bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bind_group_layout, entries: &[], }); } ctx.async_poll(wgpu::PollType::Poll).await.unwrap(); }); #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] #[gpu_test] static DEVICE_LIFETIME_CHECK: GpuTestConfiguration = GpuTestConfiguration::new() .parameters(TestParameters::default()) .run_sync(|ctx| { ctx.instance.poll_all(false); let pre_report = ctx.instance.generate_report().unwrap(); let TestingContext { instance, device, queue, .. } = ctx; drop(queue); drop(device); let post_report = instance.generate_report().unwrap(); assert_ne!( pre_report, post_report, "Queue and Device has not been dropped as expected" ); }); #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] #[gpu_test] static MULTIPLE_DEVICES: GpuTestConfiguration = GpuTestConfiguration::new() .parameters(TestParameters::default()) .run_sync(|ctx| { use pollster::FutureExt as _; ctx.adapter .request_device(&wgpu::DeviceDescriptor::default()) .block_on() .expect("failed to create device"); ctx.adapter .request_device(&wgpu::DeviceDescriptor::default()) .block_on() .expect("failed to create device"); }); #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] #[gpu_test] static REQUEST_DEVICE_ERROR_MESSAGE_NATIVE: GpuTestConfiguration = GpuTestConfiguration::new() .parameters({ let default = TestParameters::default(); // On CI, we have the vulkan SDK installed and this test initializes the Vulkan backend when all // normal tests do not, so we get these weird error messages. This only actually gets hooked up // on Windows, because that's the only platform where the actual runtime gets hooked up and there // are no drivers. if std::env::var("WGPU_CI").is_ok() && cfg!(windows) { default.expect_fail( FailureCase::always() .validation_error("Registry lookup failed to get ICD manifest files. Possibly missing Vulkan driver?") .validation_error("vkCreateInstance: Found no drivers!") ) } else { default } }) .run_async(|_ctx| request_device_error_message()); /// Check that `RequestDeviceError`s produced have some diagnostic information. /// /// Note: this is a wasm *and* native test. On wasm it is run directly; on native, indirectly #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] async fn request_device_error_message() { // Not using initialize_test() because that doesn't let us catch the error // nor .await anything let (_instance, adapter, _surface_guard) = wgpu_test::initialize_adapter(None, false).await; let device_error = adapter .request_device(&wgpu::DeviceDescriptor { // Force a failure by requesting absurd limits. required_features: wgpu::Features::all(), required_limits: wgpu::Limits { max_texture_dimension_1d: u32::MAX, max_texture_dimension_2d: u32::MAX, max_texture_dimension_3d: u32::MAX, max_bind_groups: u32::MAX, max_push_constant_size: u32::MAX, ..Default::default() }, ..Default::default() }) .await .unwrap_err(); let device_error = device_error.to_string(); cfg_if::cfg_if! { if #[cfg(all(target_arch = "wasm32", not(feature = "webgl")))] { // On WebGPU, so the error we get will be from the browser WebGPU API. // Per the WebGPU specification this should be a `TypeError` when features are not // available, , // and the stringification it goes through for Rust should put that in the message. let expected = "TypeError"; } else { // This message appears whenever wgpu-core is used as the implementation. let expected = "Unsupported features were requested: Features {"; } } assert!(device_error.contains(expected), "{device_error}"); } // This is a test of device behavior after device.destroy. Specifically, all operations // should trigger errors since the device is lost. #[gpu_test] static DEVICE_DESTROY_THEN_MORE: GpuTestConfiguration = GpuTestConfiguration::new() .parameters(TestParameters::default().features(wgpu::Features::CLEAR_TEXTURE)) .run_sync(|ctx| { // Create some resources on the device that we will attempt to use *after* losing // the device. // Create some 512 x 512 2D textures. let texture_extent = wgpu::Extent3d { width: 512, height: 512, depth_or_array_layers: 1, }; let texture_for_view = 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: &[], }); let target_view = texture_for_view.create_view(&wgpu::TextureViewDescriptor::default()); let texture_for_read = 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::COPY_SRC, view_formats: &[], }); let texture_for_write = 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::COPY_DST, view_formats: &[], }); // Create some buffers. let buffer_source = ctx.device.create_buffer(&wgpu::BufferDescriptor { label: None, size: 256, usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, mapped_at_creation: false, }); let buffer_dest = ctx.device.create_buffer(&wgpu::BufferDescriptor { label: None, size: 256, usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); let buffer_for_map = ctx.device.create_buffer(&wgpu::BufferDescriptor { label: None, size: 256, usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, mapped_at_creation: false, }); let buffer_for_unmap = ctx.device.create_buffer(&wgpu::BufferDescriptor { label: None, size: 256, usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, mapped_at_creation: true, }); // Create a bind group layout. let bind_group_layout = ctx.device .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[], }); // Create a shader module. let shader_module = ctx .device .create_shader_module(wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed("")), }); // Create some command encoders. let mut encoder_for_clear = ctx .device .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); let mut encoder_for_compute_pass = ctx .device .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); let mut encoder_for_render_pass = ctx .device .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); let mut encoder_for_buffer_buffer_copy = ctx .device .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); let mut encoder_for_buffer_texture_copy = ctx .device .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); let mut encoder_for_texture_buffer_copy = ctx .device .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); let mut encoder_for_texture_texture_copy = ctx .device .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); // Destroy the device. This will cause all other requests to return some variation of // a device invalid error. ctx.device.destroy(); // TODO: verify the following operations will return an invalid device error: // * Run a compute or render pass // * Finish a render bundle encoder // * Create a texture from HAL // * Create a buffer from HAL // * Create a sampler // * Validate a surface configuration // * Start or stop capture // * Get or set buffer sub data // TODO: figure out how to structure a test around these operations which panic when // the device is invalid: // * device.features() // * device.limits() // * device.downlevel_properties() // * device.create_query_set() // TODO: change these fail calls to check for the specific errors which indicate that // the device is not valid. // Creating a command encoder should fail. fail( &ctx.device, || { let _ = ctx .device .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); }, Some("device with '' label is invalid"), ); // Creating a buffer should fail. fail( &ctx.device, || { let _ = ctx.device.create_buffer(&wgpu::BufferDescriptor { label: None, size: 256, usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, mapped_at_creation: false, }); }, Some("device with '' label is invalid"), ); // Creating a texture should fail. fail( &ctx.device, || { let _ = ctx.device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: 512, height: 512, depth_or_array_layers: 1, }, mip_level_count: 2, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rg8Uint, usage: wgpu::TextureUsages::COPY_SRC, view_formats: &[], }); }, Some("device with '' label is invalid"), ); // Texture clear should fail. fail( &ctx.device, || { encoder_for_clear.clear_texture( &texture_for_write, &wgpu::ImageSubresourceRange { aspect: wgpu::TextureAspect::All, base_mip_level: 0, mip_level_count: None, base_array_layer: 0, array_layer_count: None, }, ); }, Some("device with '' label is invalid"), ); // Creating a compute pass should fail. fail( &ctx.device, || { encoder_for_compute_pass.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None, }); }, Some("device with '' label is invalid"), ); // Creating a render pass should fail. fail( &ctx.device, || { encoder_for_render_pass.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { ops: wgpu::Operations::default(), resolve_target: None, view: &target_view, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, }); }, Some("device with '' label is invalid"), ); // Copying a buffer to a buffer should fail. fail( &ctx.device, || { encoder_for_buffer_buffer_copy.copy_buffer_to_buffer( &buffer_source, 0, &buffer_dest, 0, 256, ); }, Some("device with '' label is invalid"), ); // Copying a buffer to a texture should fail. fail( &ctx.device, || { encoder_for_buffer_texture_copy.copy_buffer_to_texture( wgpu::TexelCopyBufferInfo { buffer: &buffer_source, layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(4), rows_per_image: None, }, }, texture_for_write.as_image_copy(), texture_extent, ); }, Some("device with '' label is invalid"), ); // Copying a texture to a buffer should fail. fail( &ctx.device, || { encoder_for_texture_buffer_copy.copy_texture_to_buffer( texture_for_read.as_image_copy(), wgpu::TexelCopyBufferInfo { buffer: &buffer_source, layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(4), rows_per_image: None, }, }, texture_extent, ); }, Some("device with '' label is invalid"), ); // Copying a texture to a texture should fail. fail( &ctx.device, || { encoder_for_texture_texture_copy.copy_texture_to_texture( texture_for_read.as_image_copy(), texture_for_write.as_image_copy(), texture_extent, ); }, Some("device with '' label is invalid"), ); // Creating a bind group layout should fail. fail( &ctx.device, || { let _ = ctx .device .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[], }); }, Some("device with '' label is invalid"), ); // Creating a bind group should fail. fail( &ctx.device, || { let _ = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer( buffer_source.as_entire_buffer_binding(), ), }], }); }, Some("device with '' label is invalid"), ); // Creating a pipeline layout should fail. fail( &ctx.device, || { let _ = ctx .device .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[], push_constant_ranges: &[], }); }, Some("device with '' label is invalid"), ); // Creating a shader module should fail. fail( &ctx.device, || { let _ = ctx .device .create_shader_module(wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed("")), }); }, Some("device with '' label is invalid"), ); // Creating a shader module spirv should fail. fail( &ctx.device, || unsafe { let _ = ctx .device .create_shader_module_spirv(&wgpu::ShaderModuleDescriptorSpirV { label: None, source: std::borrow::Cow::Borrowed(&[]), }); }, Some("device with '' label is invalid"), ); // Creating a render pipeline should fail. fail( &ctx.device, || { let _ = ctx .device .create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: None, vertex: wgpu::VertexState { module: &shader_module, entry_point: Some(""), compilation_options: Default::default(), buffers: &[], }, primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), fragment: None, multiview: None, cache: None, }); }, Some("device with '' label is invalid"), ); // Creating a compute pipeline should fail. fail( &ctx.device, || { let _ = ctx .device .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: None, layout: None, module: &shader_module, entry_point: None, compilation_options: Default::default(), cache: None, }); }, Some("device with '' label is invalid"), ); // Creating a compute pipeline should fail. fail( &ctx.device, || { let _ = ctx .device .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: None, layout: None, module: &shader_module, entry_point: None, compilation_options: Default::default(), cache: None, }); }, Some("device with '' label is invalid"), ); // Buffer map should fail. fail( &ctx.device, || { buffer_for_map .slice(..) .map_async(wgpu::MapMode::Write, |_| ()); }, Some("device with '' label is invalid"), ); // Buffer unmap should fail. fail( &ctx.device, || { buffer_for_unmap.unmap(); }, Some("device with '' label is invalid"), ); }); #[gpu_test] static DEVICE_DESTROY_THEN_LOST: GpuTestConfiguration = GpuTestConfiguration::new() .parameters(TestParameters::default()) .run_async(|ctx| async move { // This test checks that when device.destroy is called, the provided // DeviceLostClosure is called with reason DeviceLostReason::Destroyed. static WAS_CALLED: AtomicBool = AtomicBool::new(false); // Set a LoseDeviceCallback on the device. let callback = Box::new(|reason, _m| { WAS_CALLED.store(true, std::sync::atomic::Ordering::SeqCst); assert!( matches!(reason, wgpu::DeviceLostReason::Destroyed), "Device lost info reason should match DeviceLostReason::Destroyed." ); }); ctx.device.set_device_lost_callback(callback); // Destroy the device. ctx.device.destroy(); // Make sure the device queues are empty, which ensures that the closure // has been called. assert!(ctx .async_poll(wgpu::PollType::wait()) .await .unwrap() .is_queue_empty()); assert!( WAS_CALLED.load(std::sync::atomic::Ordering::SeqCst), "Device lost callback should have been called." ); }); #[gpu_test] static DIFFERENT_BGL_ORDER_BW_SHADER_AND_API: GpuTestConfiguration = GpuTestConfiguration::new() .parameters(TestParameters::default()) .run_sync(|ctx| { // This test addresses a bug found in multiple backends where `wgpu_core` and `wgpu_hal` // backends made different assumptions about the element order of vectors of bind group // layout entries and bind group resource bindings. // // Said bug was exposed originally by: // // 1. Shader-declared bindings having a different order than resource bindings provided to // `Device::create_bind_group`. // 2. Having more of one type of resource in the bind group than another. // // …such that internals would accidentally attempt to use an out-of-bounds index (of one // resource type) in the wrong list of a different resource type. Let's reproduce that // here. let trivial_shaders_with_some_reversed_bindings = concat!( "@group(0) @binding(3) var myTexture2: texture_2d;\n", "@group(0) @binding(2) var myTexture1: texture_2d;\n", "@group(0) @binding(1) var mySampler: sampler;\n", "\n", "@fragment\n", "fn fs_main(@builtin(position) pos: vec4) -> @location(0) vec4f {\n", " return textureSample(myTexture1, mySampler, pos.xy) \n", " + textureSample(myTexture2, mySampler, pos.xy);\n", "}\n", "\n", "@vertex\n", "fn vs_main() -> @builtin(position) vec4 {\n", " return vec4(0.0, 0.0, 0.0, 1.0);\n", "}\n", ); let trivial_shaders_with_some_reversed_bindings = ctx.device .create_shader_module(wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl( trivial_shaders_with_some_reversed_bindings.into(), ), }); let my_texture = ctx.device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: 1024, height: 512, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }); let my_texture_view = my_texture.create_view(&wgpu::TextureViewDescriptor { label: None, format: None, dimension: None, usage: None, aspect: wgpu::TextureAspect::All, base_mip_level: 0, mip_level_count: None, base_array_layer: 0, array_layer_count: None, }); let my_sampler = ctx .device .create_sampler(&wgpu::SamplerDescriptor::default()); let render_pipeline = ctx .device .create_render_pipeline(&wgpu::RenderPipelineDescriptor { fragment: Some(wgpu::FragmentState { module: &trivial_shaders_with_some_reversed_bindings, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(wgpu::ColorTargetState { format: wgpu::TextureFormat::Bgra8Unorm, blend: None, write_mask: wgpu::ColorWrites::ALL, })], }), layout: None, // Other fields below aren't interesting for this text. label: None, vertex: wgpu::VertexState { module: &trivial_shaders_with_some_reversed_bindings, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview: None, cache: None, }); // fail(&ctx.device, || { // }, ""); let _ = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &render_pipeline.get_bind_group_layout(0), entries: &[ wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&my_sampler), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&my_texture_view), }, wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&my_texture_view), }, ], }); }); #[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); }); #[gpu_test] static DEVICE_AND_QUEUE_HAVE_DIFFERENT_IDS: GpuTestConfiguration = GpuTestConfiguration::new() .parameters(TestParameters::default()) .run_async(|ctx| async move { let TestingContext { adapter, device_features, device_limits, device, queue, .. } = ctx; drop(device); let (device2, queue2) = wgpu_test::initialize_device(&adapter, device_features, device_limits).await; drop(queue); drop(device2); drop(queue2); // this would previously panic since we would try to use the Device ID to drop the Queue });