diff --git a/examples/standalone/custom_backend/src/custom.rs b/examples/standalone/custom_backend/src/custom.rs index ec57413ece8..8ca7df80bdb 100644 --- a/examples/standalone/custom_backend/src/custom.rs +++ b/examples/standalone/custom_backend/src/custom.rs @@ -184,6 +184,14 @@ impl DeviceInterface for CustomDevice { unimplemented!() } + fn create_external_texture( + &self, + _desc: &wgpu::ExternalTextureDescriptor<'_>, + _planes: &[&wgpu::TextureView], + ) -> wgpu::custom::DispatchExternalTexture { + unimplemented!() + } + fn create_blas( &self, _desc: &wgpu::CreateBlasDescriptor<'_>, diff --git a/naga-cli/src/bin/naga.rs b/naga-cli/src/bin/naga.rs index a1e2b4a0bd1..495c418f6e8 100644 --- a/naga-cli/src/bin/naga.rs +++ b/naga-cli/src/bin/naga.rs @@ -508,8 +508,8 @@ fn run() -> anyhow::Result<()> { use naga::valid::Capabilities as C; let missing = match Path::new(path).extension().and_then(|ex| ex.to_str()) { Some("wgsl") => C::CLIP_DISTANCE | C::CULL_DISTANCE, - Some("metal") => C::CULL_DISTANCE, - _ => C::empty(), + Some("metal") => C::CULL_DISTANCE | C::TEXTURE_EXTERNAL, + _ => C::TEXTURE_EXTERNAL, }; caps & !missing }); diff --git a/naga/src/back/glsl/features.rs b/naga/src/back/glsl/features.rs index 1eb3df65f38..a6dfe4e3100 100644 --- a/naga/src/back/glsl/features.rs +++ b/naga/src/back/glsl/features.rs @@ -421,7 +421,8 @@ impl Writer<'_, W> { _ => {} }, ImageClass::Sampled { multi: false, .. } - | ImageClass::Depth { multi: false } => {} + | ImageClass::Depth { multi: false } + | ImageClass::External => {} } } _ => {} diff --git a/naga/src/back/glsl/mod.rs b/naga/src/back/glsl/mod.rs index ee0daa89c6c..d4ade3830e6 100644 --- a/naga/src/back/glsl/mod.rs +++ b/naga/src/back/glsl/mod.rs @@ -1176,6 +1176,7 @@ impl<'a, W: Write> Writer<'a, W> { Ic::Depth { multi: true } => ("sampler", float, "MS", ""), Ic::Depth { multi: false } => ("sampler", float, "", "Shadow"), Ic::Storage { format, .. } => ("image", format.into(), "", ""), + Ic::External => unimplemented!(), }; let precision = if self.options.version.is_es() { @@ -3302,6 +3303,7 @@ impl<'a, W: Write> Writer<'a, W> { write!(self.out, "imageSize(")?; self.write_expr(image, ctx)?; } + ImageClass::External => unimplemented!(), } write!(self.out, ")")?; if components != 1 || self.options.version.is_es() { @@ -3317,6 +3319,7 @@ impl<'a, W: Write> Writer<'a, W> { let fun_name = match class { ImageClass::Sampled { .. } | ImageClass::Depth { .. } => "textureSize", ImageClass::Storage { .. } => "imageSize", + ImageClass::External => unimplemented!(), }; write!(self.out, "{fun_name}(")?; self.write_expr(image, ctx)?; @@ -3336,6 +3339,7 @@ impl<'a, W: Write> Writer<'a, W> { "textureSamples" } ImageClass::Storage { .. } => "imageSamples", + ImageClass::External => unimplemented!(), }; write!(self.out, "{fun_name}(")?; self.write_expr(image, ctx)?; @@ -4618,6 +4622,7 @@ impl<'a, W: Write> Writer<'a, W> { "WGSL `textureLoad` from depth textures is not supported in GLSL".to_string(), )) } + crate::ImageClass::External => unimplemented!(), }; // openGL es doesn't have 1D images so we need workaround it diff --git a/naga/src/back/hlsl/help.rs b/naga/src/back/hlsl/help.rs index f5c9d4b3b97..bea917547b5 100644 --- a/naga/src/back/hlsl/help.rs +++ b/naga/src/back/hlsl/help.rs @@ -195,6 +195,7 @@ impl super::Writer<'_, W> { let storage_format_str = format.to_hlsl_str(); write!(self.out, "<{storage_format_str}>")? } + crate::ImageClass::External => unimplemented!(), } Ok(()) } @@ -290,6 +291,7 @@ impl super::Writer<'_, W> { crate::ImageClass::Depth { multi: false } => "Depth", crate::ImageClass::Sampled { multi: false, .. } => "", crate::ImageClass::Storage { .. } => "RW", + crate::ImageClass::External => unimplemented!(), }; let arrayed_str = if query.arrayed { "Array" } else { "" }; let query_str = match query.query { @@ -349,6 +351,7 @@ impl super::Writer<'_, W> { let extra_coords = match wiq.class { crate::ImageClass::Storage { .. } => 0, crate::ImageClass::Sampled { .. } | crate::ImageClass::Depth { .. } => 1, + crate::ImageClass::External => unimplemented!(), }; // GetDimensions Overloaded Methods diff --git a/naga/src/back/msl/writer.rs b/naga/src/back/msl/writer.rs index 3cb1ab2c39b..1ec87c9be6a 100644 --- a/naga/src/back/msl/writer.rs +++ b/naga/src/back/msl/writer.rs @@ -321,6 +321,7 @@ impl Display for TypeContext<'_> { }; ("texture", "", format.into(), access) } + crate::ImageClass::External => unimplemented!(), }; let base_name = scalar.to_msl_name(); let array_str = if arrayed { "_array" } else { "" }; @@ -6637,6 +6638,7 @@ template "read-write textures".to_string(), )); } + crate::ImageClass::External => unimplemented!(), }, _ => { return Err(Error::UnsupportedArrayOfType(base)); diff --git a/naga/src/back/spv/image.rs b/naga/src/back/spv/image.rs index f485014530e..6c3313992c9 100644 --- a/naga/src/back/spv/image.rs +++ b/naga/src/back/spv/image.rs @@ -118,6 +118,7 @@ impl Load { crate::ImageClass::Depth { .. } | crate::ImageClass::Sampled { .. } => { spirv::Op::ImageFetch } + crate::ImageClass::External => unimplemented!(), }; // `OpImageRead` and `OpImageFetch` instructions produce vec4 diff --git a/naga/src/back/spv/mod.rs b/naga/src/back/spv/mod.rs index 986bee57d31..5bddb87cf79 100644 --- a/naga/src/back/spv/mod.rs +++ b/naga/src/back/spv/mod.rs @@ -275,6 +275,7 @@ impl LocalImageType { flags: make_flags(false, ImageTypeFlags::empty()), image_format: format.into(), }, + crate::ImageClass::External => unimplemented!(), } } } diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index 9f4422345b5..09b1deb8b41 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -1249,6 +1249,7 @@ impl Writer { self.request_image_format_capabilities(format.into())?; false } + crate::ImageClass::External => unimplemented!(), }; match dim { diff --git a/naga/src/common/wgsl/types.rs b/naga/src/common/wgsl/types.rs index c118feeace7..7588b9aafba 100644 --- a/naga/src/common/wgsl/types.rs +++ b/naga/src/common/wgsl/types.rs @@ -250,6 +250,9 @@ where "texture_storage_{dim_str}{arrayed_str}<{format_str}{access_str}>" )?; } + Ic::External => { + write!(out, "texture_external")?; + } } } TypeInner::Scalar(scalar) => { diff --git a/naga/src/front/glsl/builtins.rs b/naga/src/front/glsl/builtins.rs index 6dcddda44e7..3d7588d27ba 100644 --- a/naga/src/front/glsl/builtins.rs +++ b/naga/src/front/glsl/builtins.rs @@ -2138,6 +2138,7 @@ impl Frontend { ImageClass::Depth { .. } => (true, false), ImageClass::Storage { .. } => (false, true), ImageClass::Sampled { .. } => (false, false), + ImageClass::External => unreachable!(), }; let coordinate = match (image_size, coord_size) { @@ -2259,6 +2260,7 @@ pub fn sampled_to_depth( kind: ErrorKind::SemanticError("Not a texture".into()), meta, }), + ImageClass::External => unreachable!(), }, _ => errors.push(Error { kind: ErrorKind::SemanticError("Not a texture".into()), diff --git a/naga/src/front/wgsl/lower/mod.rs b/naga/src/front/wgsl/lower/mod.rs index 1f994d753f0..3f4fe4a12cb 100644 --- a/naga/src/front/wgsl/lower/mod.rs +++ b/naga/src/front/wgsl/lower/mod.rs @@ -3587,9 +3587,12 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)? } - // Sampling `Storage` textures isn't allowed at all. Let the - // validator report the error. - ir::ImageClass::Storage { .. } => self.expression(args.next()?, ctx)?, + // Sampling `External` textures with a specified level isn't + // allowed, and sampling `Storage` textures isn't allowed at + // all. Let the validator report the error. + ir::ImageClass::Storage { .. } | ir::ImageClass::External => { + self.expression(args.next()?, ctx)? + } }; level = ir::SampleLevel::Exact(exact); depth_ref = None; diff --git a/naga/src/front/wgsl/parse/mod.rs b/naga/src/front/wgsl/parse/mod.rs index 892930b4301..cf4dd4d4bb6 100644 --- a/naga/src/front/wgsl/parse/mod.rs +++ b/naga/src/front/wgsl/parse/mod.rs @@ -676,6 +676,7 @@ impl Parser { | "texture_depth_cube" | "texture_depth_cube_array" | "texture_depth_multisampled_2d" + | "texture_external" | "texture_storage_1d" | "texture_storage_1d_array" | "texture_storage_2d" @@ -1867,6 +1868,11 @@ impl Parser { arrayed: false, class: crate::ImageClass::Depth { multi: true }, }, + "texture_external" => ast::Type::Image { + dim: crate::ImageDimension::D2, + arrayed: false, + class: crate::ImageClass::External, + }, "texture_storage_1d" => { let (format, access) = lexer.next_format_generic()?; ast::Type::Image { diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index 943dbc2458b..35080bf3292 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -640,6 +640,8 @@ pub enum ImageClass { /// Multi-sampled depth image. multi: bool, }, + /// External texture. + External, /// Storage image. Storage { format: StorageFormat, diff --git a/naga/src/proc/mod.rs b/naga/src/proc/mod.rs index 0843e709b5d..f2584c64b3d 100644 --- a/naga/src/proc/mod.rs +++ b/naga/src/proc/mod.rs @@ -383,6 +383,7 @@ impl super::ImageClass { match self { crate::ImageClass::Sampled { multi, .. } | crate::ImageClass::Depth { multi } => multi, crate::ImageClass::Storage { .. } => false, + crate::ImageClass::External => false, } } @@ -390,6 +391,7 @@ impl super::ImageClass { match self { crate::ImageClass::Sampled { multi, .. } | crate::ImageClass::Depth { multi } => !multi, crate::ImageClass::Storage { .. } => false, + crate::ImageClass::External => false, } } diff --git a/naga/src/proc/typifier.rs b/naga/src/proc/typifier.rs index f7d1ea48390..8f48a69c041 100644 --- a/naga/src/proc/typifier.rs +++ b/naga/src/proc/typifier.rs @@ -512,6 +512,10 @@ impl<'a> ResolveContext<'a> { scalar: format.into(), size: crate::VectorSize::Quad, }, + crate::ImageClass::External => Ti::Vector { + scalar: crate::Scalar::F32, + size: crate::VectorSize::Quad, + }, }), ref other => { log::error!("Image type {:?}", other); diff --git a/naga/src/valid/expression.rs b/naga/src/valid/expression.rs index 8b73486b1fe..d2d67eb4923 100644 --- a/naga/src/valid/expression.rs +++ b/naga/src/valid/expression.rs @@ -460,6 +460,7 @@ impl super::Validator { kind: crate::ScalarKind::Uint | crate::ScalarKind::Sint, multi: false, } if gather.is_some() => false, + crate::ImageClass::External => false, crate::ImageClass::Depth { multi: false } => true, _ => return Err(ExpressionError::InvalidImageClass(class)), }; @@ -551,7 +552,7 @@ impl super::Validator { crate::ImageClass::Sampled { kind: crate::ScalarKind::Float, multi: false - } + } | crate::ImageClass::External ) { return Err(ExpressionError::InvalidSampleClampCoordinateToEdge( alloc::format!("image class `{class:?}`"), diff --git a/naga/src/valid/mod.rs b/naga/src/valid/mod.rs index aef6a241646..2ec44a54ec9 100644 --- a/naga/src/valid/mod.rs +++ b/naga/src/valid/mod.rs @@ -165,6 +165,8 @@ bitflags::bitflags! { const RAY_HIT_VERTEX_POSITION = 1 << 25; /// Support for 16-bit floating-point types. const SHADER_FLOAT16 = 1 << 26; + /// Support for [`ImageClass::External`] + const TEXTURE_EXTERNAL = 1 << 27; } } diff --git a/naga/src/valid/type.rs b/naga/src/valid/type.rs index b3ae13b7d4a..43323a08f37 100644 --- a/naga/src/valid/type.rs +++ b/naga/src/valid/type.rs @@ -732,6 +732,9 @@ impl super::Validator { if arrayed && matches!(dim, crate::ImageDimension::Cube) { self.require_type_capability(Capabilities::CUBE_ARRAY_TEXTURES)?; } + if matches!(class, crate::ImageClass::External) { + self.require_type_capability(Capabilities::TEXTURE_EXTERNAL)?; + } TypeInfo::new( TypeFlags::ARGUMENT | TypeFlags::CREATION_RESOLVED, Alignment::ONE, diff --git a/naga/tests/in/wgsl/texture-external.toml b/naga/tests/in/wgsl/texture-external.toml new file mode 100644 index 00000000000..f8f46d4223e --- /dev/null +++ b/naga/tests/in/wgsl/texture-external.toml @@ -0,0 +1,2 @@ +god_mode = true +targets = "IR | WGSL" diff --git a/naga/tests/in/wgsl/texture-external.wgsl b/naga/tests/in/wgsl/texture-external.wgsl new file mode 100644 index 00000000000..6295980a447 --- /dev/null +++ b/naga/tests/in/wgsl/texture-external.wgsl @@ -0,0 +1,17 @@ +@group(0) @binding(0) +var tex: texture_external; +@group(0) @binding(1) +var samp: sampler; + +fn test(t: texture_external) -> vec4 { + var a = textureSampleBaseClampToEdge(t, samp, vec2(0.0f)); + var b = textureLoad(t, vec2(0u)); + var c = textureDimensions(t); + + return a + b + vec2f(c).xyxy; +} + +@fragment +fn main() -> @location(0) vec4 { + return test(tex); +} diff --git a/naga/tests/out/ir/wgsl-texture-external.compact.ron b/naga/tests/out/ir/wgsl-texture-external.compact.ron new file mode 100644 index 00000000000..432298419d4 --- /dev/null +++ b/naga/tests/out/ir/wgsl-texture-external.compact.ron @@ -0,0 +1,253 @@ +( + types: [ + ( + name: None, + inner: Image( + dim: D2, + arrayed: false, + class: External, + ), + ), + ( + name: None, + inner: Sampler( + comparison: false, + ), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Vector( + size: Bi, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ], + special_types: ( + ray_desc: None, + ray_intersection: None, + ray_vertex_return: None, + predeclared_types: {}, + ), + constants: [], + overrides: [], + global_variables: [ + ( + name: Some("tex"), + space: Handle, + binding: Some(( + group: 0, + binding: 0, + )), + ty: 0, + init: None, + ), + ( + name: Some("samp"), + space: Handle, + binding: Some(( + group: 0, + binding: 1, + )), + ty: 1, + init: None, + ), + ], + global_expressions: [], + functions: [ + ( + name: Some("test"), + arguments: [ + ( + name: Some("t"), + ty: 0, + binding: None, + ), + ], + result: Some(( + ty: 2, + binding: None, + )), + local_variables: [ + ( + name: Some("a"), + ty: 2, + init: None, + ), + ( + name: Some("b"), + ty: 2, + init: None, + ), + ( + name: Some("c"), + ty: 3, + init: None, + ), + ], + expressions: [ + FunctionArgument(0), + GlobalVariable(1), + Literal(F32(0.0)), + Splat( + size: Bi, + value: 2, + ), + ImageSample( + image: 0, + sampler: 1, + gather: None, + coordinate: 3, + array_index: None, + offset: None, + level: Zero, + depth_ref: None, + clamp_to_edge: true, + ), + LocalVariable(0), + Literal(U32(0)), + Splat( + size: Bi, + value: 6, + ), + ImageLoad( + image: 0, + coordinate: 7, + array_index: None, + sample: None, + level: None, + ), + LocalVariable(1), + ImageQuery( + image: 0, + query: Size( + level: None, + ), + ), + LocalVariable(2), + Load( + pointer: 5, + ), + Load( + pointer: 9, + ), + Binary( + op: Add, + left: 12, + right: 13, + ), + Load( + pointer: 11, + ), + As( + expr: 15, + kind: Float, + convert: Some(4), + ), + Swizzle( + size: Quad, + vector: 16, + pattern: (X, Y, X, Y), + ), + Binary( + op: Add, + left: 14, + right: 17, + ), + ], + named_expressions: { + 0: "t", + }, + body: [ + Emit(( + start: 3, + end: 5, + )), + Store( + pointer: 5, + value: 4, + ), + Emit(( + start: 7, + end: 9, + )), + Store( + pointer: 9, + value: 8, + ), + Emit(( + start: 10, + end: 11, + )), + Store( + pointer: 11, + value: 10, + ), + Emit(( + start: 12, + end: 19, + )), + Return( + value: Some(18), + ), + ], + diagnostic_filter_leaf: None, + ), + ], + entry_points: [ + ( + name: "main", + stage: Fragment, + early_depth_test: None, + workgroup_size: (0, 0, 0), + workgroup_size_overrides: None, + function: ( + name: Some("main"), + arguments: [], + result: Some(( + ty: 2, + binding: Some(Location( + location: 0, + interpolation: Some(Perspective), + sampling: Some(Center), + blend_src: None, + )), + )), + local_variables: [], + expressions: [ + GlobalVariable(0), + CallResult(0), + ], + named_expressions: {}, + body: [ + Call( + function: 0, + arguments: [ + 0, + ], + result: Some(1), + ), + Return( + value: Some(1), + ), + ], + diagnostic_filter_leaf: None, + ), + ), + ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, + doc_comments: None, +) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-texture-external.ron b/naga/tests/out/ir/wgsl-texture-external.ron new file mode 100644 index 00000000000..432298419d4 --- /dev/null +++ b/naga/tests/out/ir/wgsl-texture-external.ron @@ -0,0 +1,253 @@ +( + types: [ + ( + name: None, + inner: Image( + dim: D2, + arrayed: false, + class: External, + ), + ), + ( + name: None, + inner: Sampler( + comparison: false, + ), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Vector( + size: Bi, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ], + special_types: ( + ray_desc: None, + ray_intersection: None, + ray_vertex_return: None, + predeclared_types: {}, + ), + constants: [], + overrides: [], + global_variables: [ + ( + name: Some("tex"), + space: Handle, + binding: Some(( + group: 0, + binding: 0, + )), + ty: 0, + init: None, + ), + ( + name: Some("samp"), + space: Handle, + binding: Some(( + group: 0, + binding: 1, + )), + ty: 1, + init: None, + ), + ], + global_expressions: [], + functions: [ + ( + name: Some("test"), + arguments: [ + ( + name: Some("t"), + ty: 0, + binding: None, + ), + ], + result: Some(( + ty: 2, + binding: None, + )), + local_variables: [ + ( + name: Some("a"), + ty: 2, + init: None, + ), + ( + name: Some("b"), + ty: 2, + init: None, + ), + ( + name: Some("c"), + ty: 3, + init: None, + ), + ], + expressions: [ + FunctionArgument(0), + GlobalVariable(1), + Literal(F32(0.0)), + Splat( + size: Bi, + value: 2, + ), + ImageSample( + image: 0, + sampler: 1, + gather: None, + coordinate: 3, + array_index: None, + offset: None, + level: Zero, + depth_ref: None, + clamp_to_edge: true, + ), + LocalVariable(0), + Literal(U32(0)), + Splat( + size: Bi, + value: 6, + ), + ImageLoad( + image: 0, + coordinate: 7, + array_index: None, + sample: None, + level: None, + ), + LocalVariable(1), + ImageQuery( + image: 0, + query: Size( + level: None, + ), + ), + LocalVariable(2), + Load( + pointer: 5, + ), + Load( + pointer: 9, + ), + Binary( + op: Add, + left: 12, + right: 13, + ), + Load( + pointer: 11, + ), + As( + expr: 15, + kind: Float, + convert: Some(4), + ), + Swizzle( + size: Quad, + vector: 16, + pattern: (X, Y, X, Y), + ), + Binary( + op: Add, + left: 14, + right: 17, + ), + ], + named_expressions: { + 0: "t", + }, + body: [ + Emit(( + start: 3, + end: 5, + )), + Store( + pointer: 5, + value: 4, + ), + Emit(( + start: 7, + end: 9, + )), + Store( + pointer: 9, + value: 8, + ), + Emit(( + start: 10, + end: 11, + )), + Store( + pointer: 11, + value: 10, + ), + Emit(( + start: 12, + end: 19, + )), + Return( + value: Some(18), + ), + ], + diagnostic_filter_leaf: None, + ), + ], + entry_points: [ + ( + name: "main", + stage: Fragment, + early_depth_test: None, + workgroup_size: (0, 0, 0), + workgroup_size_overrides: None, + function: ( + name: Some("main"), + arguments: [], + result: Some(( + ty: 2, + binding: Some(Location( + location: 0, + interpolation: Some(Perspective), + sampling: Some(Center), + blend_src: None, + )), + )), + local_variables: [], + expressions: [ + GlobalVariable(0), + CallResult(0), + ], + named_expressions: {}, + body: [ + Call( + function: 0, + arguments: [ + 0, + ], + result: Some(1), + ), + Return( + value: Some(1), + ), + ], + diagnostic_filter_leaf: None, + ), + ), + ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, + doc_comments: None, +) \ No newline at end of file diff --git a/naga/tests/out/wgsl/wgsl-texture-external.wgsl b/naga/tests/out/wgsl/wgsl-texture-external.wgsl new file mode 100644 index 00000000000..e0e62f5d736 --- /dev/null +++ b/naga/tests/out/wgsl/wgsl-texture-external.wgsl @@ -0,0 +1,27 @@ +@group(0) @binding(0) +var tex: texture_external; +@group(0) @binding(1) +var samp: sampler; + +fn test(t: texture_external) -> vec4 { + var a: vec4; + var b: vec4; + var c: vec2; + + let _e4 = textureSampleBaseClampToEdge(t, samp, vec2(0f)); + a = _e4; + let _e8 = textureLoad(t, vec2(0u)); + b = _e8; + let _e10 = textureDimensions(t); + c = _e10; + let _e12 = a; + let _e13 = b; + let _e15 = c; + return ((_e12 + _e13) + vec2(_e15).xyxy); +} + +@fragment +fn main() -> @location(0) vec4 { + let _e1 = test(tex); + return _e1; +} diff --git a/player/src/lib.rs b/player/src/lib.rs index 16e31947149..ae885bfffa9 100644 --- a/player/src/lib.rs +++ b/player/src/lib.rs @@ -237,6 +237,16 @@ impl GlobalPlay for wgc::global::Global { Action::DestroyTextureView(id) => { self.texture_view_drop(id).unwrap(); } + Action::CreateExternalTexture { id, desc, planes } => { + let (_, error) = + self.device_create_external_texture(device, &desc, &planes, Some(id)); + if let Some(e) = error { + panic!("{e}"); + } + } + Action::DestroyExternalTexture(id) => { + self.external_texture_drop(id); + } Action::CreateSampler(id, desc) => { let (_, error) = self.device_create_sampler(device, &desc, Some(id)); if let Some(e) = error { diff --git a/tests/tests/wgpu-validation/api/external_texture.rs b/tests/tests/wgpu-validation/api/external_texture.rs index ece0e060f4f..ba33f2e495b 100644 --- a/tests/tests/wgpu-validation/api/external_texture.rs +++ b/tests/tests/wgpu-validation/api/external_texture.rs @@ -1,6 +1,351 @@ use wgpu::*; use wgpu_test::{fail, valid}; +/// Ensures an [`ExternalTexture`] can be created from a valid descriptor and planes, +/// but appropriate errors are returned for invalid descriptors and planes. +#[test] +fn create_external_texture() { + let (device, _queue) = wgpu::Device::noop(&DeviceDescriptor { + required_features: Features::EXTERNAL_TEXTURE, + ..Default::default() + }); + + let texture_descriptor = TextureDescriptor { + label: None, + size: Extent3d { + width: 512, + height: 512, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8Unorm, + usage: TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }; + + let r_texture = device.create_texture(&TextureDescriptor { + format: TextureFormat::R8Unorm, + ..texture_descriptor + }); + let r_view = r_texture.create_view(&TextureViewDescriptor::default()); + let rg_texture = device.create_texture(&TextureDescriptor { + format: TextureFormat::Rg8Unorm, + ..texture_descriptor + }); + let rg_view = rg_texture.create_view(&TextureViewDescriptor::default()); + let rgba_texture = device.create_texture(&TextureDescriptor { + format: TextureFormat::Rgba8Unorm, + ..texture_descriptor + }); + let rgba_view = rgba_texture.create_view(&TextureViewDescriptor::default()); + + let _ = valid(&device, || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Rgba, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + }, + &[&rgba_view], + ) + }); + let _ = valid(&device, || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Nv12, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + }, + &[&r_view, &rg_view], + ) + }); + let _ = valid(&device, || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Yu12, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + }, + &[&r_view, &r_view, &r_view], + ) + }); + + // Wrong number of planes for format + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Rgba, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + }, + &[&r_view, &r_view], + ) + }, + Some("External texture format Rgba expects 1 planes, but given 2"), + ); + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Nv12, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + }, + &[&r_view], + ) + }, + Some("External texture format Nv12 expects 2 planes, but given 1"), + ); + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Yu12, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + }, + &[&r_view, &r_view], + ) + }, + Some("External texture format Yu12 expects 3 planes, but given 2"), + ); + + // Wrong plane formats + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Rgba, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + }, + &[&r_view], + ) + }, + Some("External texture format Rgba plane 0 expects format with 4 components but given view with format R8Unorm (1 components)"), + ); + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Nv12, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + }, + &[&r_view, &rgba_view], + ) + }, + Some("External texture format Nv12 plane 1 expects format with 2 components but given view with format Rgba8Unorm (4 components)"), + ); + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Yu12, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + }, + &[&r_view, &rg_view, &r_view], + ) + }, + Some("External texture format Yu12 plane 1 expects format with 1 components but given view with format Rg8Unorm (2 components)"), + ); + + // Wrong sample type + let uint_texture = device.create_texture(&TextureDescriptor { + format: TextureFormat::Rgba8Uint, + ..texture_descriptor + }); + let uint_view = uint_texture.create_view(&TextureViewDescriptor::default()); + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Rgba, + label: None, + width: uint_texture.width(), + height: uint_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + }, + &[&uint_view], + ) + }, + Some("External texture planes expect a filterable float sample type, but given view with format Rgba8Uint (sample type Uint)"), + ); + + // Wrong texture dimension + let d3_texture = device.create_texture(&TextureDescriptor { + dimension: TextureDimension::D3, + ..texture_descriptor + }); + let d3_view = d3_texture.create_view(&TextureViewDescriptor::default()); + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Rgba, + label: None, + width: d3_texture.width(), + height: d3_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + }, + &[&d3_view], + ) + }, + Some("External texture planes expect 2D dimension, but given view with dimension = D3"), + ); + + // Multisampled + let multisampled_texture = device.create_texture(&TextureDescriptor { + sample_count: 4, + usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, + ..texture_descriptor + }); + let multisampled_view = multisampled_texture.create_view(&TextureViewDescriptor::default()); + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Rgba, + label: None, + width: multisampled_texture.width(), + height: multisampled_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + }, + &[&multisampled_view], + ) + }, + Some("External texture planes cannot be multisampled, but given view with samples = 4"), + ); + + // Missing TEXTURE_BINDING + let non_binding_texture = device.create_texture(&TextureDescriptor { + usage: TextureUsages::STORAGE_BINDING, + ..texture_descriptor + }); + let non_binding_view = non_binding_texture.create_view(&TextureViewDescriptor::default()); + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Rgba, + label: None, + width: non_binding_texture.width(), + height: non_binding_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + }, + &[&non_binding_view], + ) + }, + Some("Usage flags TextureUsages(STORAGE_BINDING) of TextureView with '' label do not contain required usage flags TextureUsages(TEXTURE_BINDING)"), + ); +} + +/// Ensures an [`ExternalTexture`] can be bound to a [`BindingType::ExternalTexture`] +/// resource binding. +#[test] +fn external_texture_binding() { + let (device, _queue) = wgpu::Device::noop(&DeviceDescriptor { + required_features: Features::EXTERNAL_TEXTURE, + ..Default::default() + }); + + let bgl = valid(&device, || { + device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: None, + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::ExternalTexture, + count: None, + }], + }) + }); + + let texture_descriptor = TextureDescriptor { + label: None, + size: Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8Unorm, + usage: TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }; + let external_texture_descriptor = ExternalTextureDescriptor { + label: None, + width: texture_descriptor.size.width, + height: texture_descriptor.size.height, + format: ExternalTextureFormat::Rgba, + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + }; + + valid(&device, || { + let texture = device.create_texture(&texture_descriptor); + let view = texture.create_view(&TextureViewDescriptor::default()); + let external_texture = + device.create_external_texture(&external_texture_descriptor, &[&view]); + + device.create_bind_group(&BindGroupDescriptor { + label: None, + layout: &bgl, + entries: &[BindGroupEntry { + binding: 0, + resource: BindingResource::ExternalTexture(&external_texture), + }], + }) + }); +} + /// Ensures a [`TextureView`] can be bound to a [`BindingType::ExternalTexture`] /// resource binding. #[test] @@ -160,3 +505,138 @@ fn external_texture_binding_texture_view() { Some("Texture binding 0 expects multisampled = false, but given a view with samples = 4"), ); } + +/// Ensures that submitting a command buffer referencing an external texture, any of +/// whose plane textures have already been destroyed, results in an error. +#[test] +fn destroyed_external_texture_plane() { + let (device, queue) = wgpu::Device::noop(&DeviceDescriptor { + required_features: Features::EXTERNAL_TEXTURE, + ..Default::default() + }); + + let target_texture = device.create_texture(&TextureDescriptor { + label: None, + size: Extent3d { + width: 512, + height: 512, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8Unorm, + usage: TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + let target_view = target_texture.create_view(&TextureViewDescriptor::default()); + + let plane_texture = device.create_texture(&TextureDescriptor { + label: Some("External texture plane"), + size: Extent3d { + width: 512, + height: 512, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8Unorm, + usage: TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }); + let plane_view = plane_texture.create_view(&TextureViewDescriptor::default()); + + let external_texture = device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Rgba, + label: None, + width: plane_texture.width(), + height: plane_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + }, + &[&plane_view], + ); + + let module = device.create_shader_module(ShaderModuleDescriptor { + label: None, + source: ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + " +@group(0) @binding(0) +var tex: texture_external; +@vertex fn vert_main() -> @builtin(position) vec4 { return vec4(0); } +@fragment fn frag_main() -> @location(0) vec4 { return textureLoad(tex, vec2(0)); }", + )), + }); + + let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { + label: None, + layout: None, + vertex: VertexState { + module: &module, + entry_point: None, + compilation_options: PipelineCompilationOptions::default(), + buffers: &[], + }, + primitive: PrimitiveState::default(), + depth_stencil: None, + multisample: MultisampleState::default(), + fragment: Some(FragmentState { + module: &module, + entry_point: None, + compilation_options: PipelineCompilationOptions::default(), + targets: &[Some(ColorTargetState { + format: target_texture.format(), + blend: None, + write_mask: ColorWrites::ALL, + })], + }), + multiview: None, + cache: None, + }); + + let bind_group = device.create_bind_group(&BindGroupDescriptor { + label: None, + layout: &pipeline.get_bind_group_layout(0), + entries: &[BindGroupEntry { + binding: 0, + resource: BindingResource::ExternalTexture(&external_texture), + }], + }); + + let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor { label: None }); + let mut pass = encoder.begin_render_pass(&RenderPassDescriptor { + label: None, + color_attachments: &[Some(RenderPassColorAttachment { + view: &target_view, + depth_slice: None, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear(Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }), + store: StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + pass.set_pipeline(&pipeline); + pass.set_bind_group(0, &bind_group, &[]); + pass.draw(0..0, 0..0); + drop(pass); + + plane_texture.destroy(); + + fail( + &device, + || queue.submit([encoder.finish()]), + Some("Texture with 'External texture plane' label has been destroyed"), + ); +} diff --git a/wgpu-core/Cargo.toml b/wgpu-core/Cargo.toml index ee131dc9f82..d4772efa2df 100644 --- a/wgpu-core/Cargo.toml +++ b/wgpu-core/Cargo.toml @@ -72,14 +72,7 @@ observe_locks = ["std", "dep:ron", "serde/serde_derive"] serde = ["dep:serde", "wgpu-types/serde", "arrayvec/serde", "hashbrown/serde"] ## Enable API tracing. -trace = [ - "serde", - "std", - "dep:ron", - "naga/serialize", - "wgpu-types/trace", - "dep:bytemuck", -] +trace = ["serde", "std", "dep:ron", "naga/serialize", "wgpu-types/trace"] ## Enable API replaying replay = ["serde", "naga/deserialize"] @@ -100,7 +93,7 @@ wgsl = ["naga/wgsl-in"] glsl = ["naga/glsl-in"] ## Enable `ShaderModuleSource::SpirV` -spirv = ["naga/spv-in", "dep:bytemuck"] +spirv = ["naga/spv-in"] #! ### Other # -------------------------------------------------------------------- @@ -180,7 +173,7 @@ arrayvec.workspace = true bit-vec.workspace = true bit-set.workspace = true bitflags.workspace = true -bytemuck = { workspace = true, optional = true } +bytemuck.workspace = true document-features.workspace = true hashbrown.workspace = true indexmap.workspace = true diff --git a/wgpu-core/src/binding_model.rs b/wgpu-core/src/binding_model.rs index 6d99ef2f177..791d9eb194d 100644 --- a/wgpu-core/src/binding_model.rs +++ b/wgpu-core/src/binding_model.rs @@ -19,12 +19,13 @@ use crate::{ device::{ bgl, Device, DeviceError, MissingDownlevelFlags, MissingFeatures, SHADER_STAGE_COUNT, }, - id::{BindGroupLayoutId, BufferId, SamplerId, TextureViewId, TlasId}, + id::{BindGroupLayoutId, BufferId, ExternalTextureId, SamplerId, TextureViewId, TlasId}, init_tracker::{BufferInitTrackerAction, TextureInitTrackerAction}, pipeline::{ComputePipeline, RenderPipeline}, resource::{ - Buffer, DestroyedResourceError, InvalidResourceError, Labeled, MissingBufferUsageError, - MissingTextureUsageError, ResourceErrorIdent, Sampler, TextureView, Tlas, TrackingData, + Buffer, DestroyedResourceError, ExternalTexture, InvalidResourceError, Labeled, + MissingBufferUsageError, MissingTextureUsageError, ResourceErrorIdent, Sampler, + TextureView, Tlas, TrackingData, }, resource_log, snatch::{SnatchGuard, Snatchable}, @@ -492,8 +493,14 @@ impl BindingTypeMaxCountValidator { /// cbindgen:ignore #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct BindGroupEntry<'a, B = BufferId, S = SamplerId, TV = TextureViewId, TLAS = TlasId> -where +pub struct BindGroupEntry< + 'a, + B = BufferId, + S = SamplerId, + TV = TextureViewId, + TLAS = TlasId, + ET = ExternalTextureId, +> where [BufferBinding]: ToOwned, [S]: ToOwned, [TV]: ToOwned, @@ -506,15 +513,21 @@ where pub binding: u32, #[cfg_attr( feature = "serde", - serde(bound(deserialize = "BindingResource<'a, B, S, TV, TLAS>: Deserialize<'de>")) + serde(bound(deserialize = "BindingResource<'a, B, S, TV, TLAS, ET>: Deserialize<'de>")) )] /// Resource to attach to the binding - pub resource: BindingResource<'a, B, S, TV, TLAS>, + pub resource: BindingResource<'a, B, S, TV, TLAS, ET>, } /// cbindgen:ignore -pub type ResolvedBindGroupEntry<'a> = - BindGroupEntry<'a, Arc, Arc, Arc, Arc>; +pub type ResolvedBindGroupEntry<'a> = BindGroupEntry< + 'a, + Arc, + Arc, + Arc, + Arc, + Arc, +>; /// Describes a group of bindings and the resources to be bound. #[derive(Clone, Debug)] @@ -526,6 +539,7 @@ pub struct BindGroupDescriptor< S = SamplerId, TV = TextureViewId, TLAS = TlasId, + ET = ExternalTextureId, > where [BufferBinding]: ToOwned, [S]: ToOwned, @@ -533,8 +547,8 @@ pub struct BindGroupDescriptor< <[BufferBinding] as ToOwned>::Owned: fmt::Debug, <[S] as ToOwned>::Owned: fmt::Debug, <[TV] as ToOwned>::Owned: fmt::Debug, - [BindGroupEntry<'a, B, S, TV, TLAS>]: ToOwned, - <[BindGroupEntry<'a, B, S, TV, TLAS>] as ToOwned>::Owned: fmt::Debug, + [BindGroupEntry<'a, B, S, TV, TLAS, ET>]: ToOwned, + <[BindGroupEntry<'a, B, S, TV, TLAS, ET>] as ToOwned>::Owned: fmt::Debug, { /// Debug label of the bind group. /// @@ -545,11 +559,12 @@ pub struct BindGroupDescriptor< #[cfg_attr( feature = "serde", serde(bound( - deserialize = "<[BindGroupEntry<'a, B, S, TV, TLAS>] as ToOwned>::Owned: Deserialize<'de>" + deserialize = "<[BindGroupEntry<'a, B, S, TV, TLAS, ET>] as ToOwned>::Owned: Deserialize<'de>" )) )] /// The resources to bind to this bind group. - pub entries: Cow<'a, [BindGroupEntry<'a, B, S, TV, TLAS>]>, + #[allow(clippy::type_complexity)] + pub entries: Cow<'a, [BindGroupEntry<'a, B, S, TV, TLAS, ET>]>, } /// cbindgen:ignore @@ -560,6 +575,7 @@ pub type ResolvedBindGroupDescriptor<'a> = BindGroupDescriptor< Arc, Arc, Arc, + Arc, >; /// Describes a [`BindGroupLayout`]. @@ -881,8 +897,14 @@ pub type ResolvedBufferBinding = BufferBinding>; // They're different enough that it doesn't make sense to share a common type #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum BindingResource<'a, B = BufferId, S = SamplerId, TV = TextureViewId, TLAS = TlasId> -where +pub enum BindingResource< + 'a, + B = BufferId, + S = SamplerId, + TV = TextureViewId, + TLAS = TlasId, + ET = ExternalTextureId, +> where [BufferBinding]: ToOwned, [S]: ToOwned, [TV]: ToOwned, @@ -909,10 +931,17 @@ where )] TextureViewArray(Cow<'a, [TV]>), AccelerationStructure(TLAS), + ExternalTexture(ET), } -pub type ResolvedBindingResource<'a> = - BindingResource<'a, Arc, Arc, Arc, Arc>; +pub type ResolvedBindingResource<'a> = BindingResource< + 'a, + Arc, + Arc, + Arc, + Arc, + Arc, +>; #[derive(Clone, Debug, Error)] #[non_exhaustive] diff --git a/wgpu-core/src/device/global.rs b/wgpu-core/src/device/global.rs index 5e6e4fc463e..e348fb95219 100644 --- a/wgpu-core/src/device/global.rs +++ b/wgpu-core/src/device/global.rs @@ -510,6 +510,75 @@ impl Global { Ok(()) } + pub fn device_create_external_texture( + &self, + device_id: DeviceId, + desc: &resource::ExternalTextureDescriptor, + planes: &[id::TextureViewId], + id_in: Option, + ) -> ( + id::ExternalTextureId, + Option, + ) { + profiling::scope!("Device::create_external_texture"); + + let hub = &self.hub; + + let fid = hub.external_textures.prepare(id_in); + + let error = 'error: { + let device = self.hub.devices.get(device_id); + + #[cfg(feature = "trace")] + if let Some(ref mut trace) = *device.trace.lock() { + let planes = Box::from(planes); + trace.add(trace::Action::CreateExternalTexture { + id: fid.id(), + desc: desc.clone(), + planes, + }); + } + + let planes = planes + .iter() + .map(|plane_id| self.hub.texture_views.get(*plane_id).get()) + .collect::, _>>(); + let planes = match planes { + Ok(planes) => planes, + Err(error) => break 'error error.into(), + }; + + let external_texture = match device.create_external_texture(desc, &planes) { + Ok(external_texture) => external_texture, + Err(error) => break 'error error, + }; + + let id = fid.assign(Fallible::Valid(external_texture)); + api_log!("Device::create_external_texture({desc:?}) -> {id:?}"); + + return (id, None); + }; + + let id = fid.assign(Fallible::Invalid(Arc::new(desc.label.to_string()))); + (id, Some(error)) + } + + pub fn external_texture_drop(&self, external_texture_id: id::ExternalTextureId) { + profiling::scope!("ExternalTexture::drop"); + api_log!("ExternalTexture::drop {external_texture_id:?}"); + + let hub = &self.hub; + + let _external_texture = hub.external_textures.remove(external_texture_id); + + #[cfg(feature = "trace")] + if let Ok(external_texture) = _external_texture.get() { + if let Some(t) = external_texture.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyExternalTexture(external_texture_id)); + } + } + } + pub fn device_create_sampler( &self, device_id: DeviceId, @@ -733,6 +802,7 @@ impl Global { sampler_storage: &Storage>, texture_view_storage: &Storage>, tlas_storage: &Storage>, + external_texture_storage: &Storage>, ) -> Result, binding_model::CreateBindGroupError> { let resolve_buffer = |bb: &BufferBinding| { @@ -764,6 +834,12 @@ impl Global { .get() .map_err(binding_model::CreateBindGroupError::from) }; + let resolve_external_texture = |id: &id::ExternalTextureId| { + external_texture_storage + .get(*id) + .get() + .map_err(binding_model::CreateBindGroupError::from) + }; let resource = match e.resource { BindingResource::Buffer(ref buffer) => { ResolvedBindingResource::Buffer(resolve_buffer(buffer)?) @@ -798,6 +874,9 @@ impl Global { BindingResource::AccelerationStructure(ref tlas) => { ResolvedBindingResource::AccelerationStructure(resolve_tlas(tlas)?) } + BindingResource::ExternalTexture(ref et) => { + ResolvedBindingResource::ExternalTexture(resolve_external_texture(et)?) + } }; Ok(ResolvedBindGroupEntry { binding: e.binding, @@ -810,6 +889,7 @@ impl Global { let texture_view_guard = hub.texture_views.read(); let sampler_guard = hub.samplers.read(); let tlas_guard = hub.tlas_s.read(); + let external_texture_guard = hub.external_textures.read(); desc.entries .iter() .map(|e| { @@ -819,6 +899,7 @@ impl Global { &sampler_guard, &texture_view_guard, &tlas_guard, + &external_texture_guard, ) }) .collect::, _>>() diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index 42ebffdaaa9..16a2ecb0b6f 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -489,6 +489,10 @@ pub fn create_validator( Caps::RAY_HIT_VERTEX_POSITION, features.intersects(wgt::Features::EXPERIMENTAL_RAY_HIT_VERTEX_RETURN), ); + caps.set( + Caps::TEXTURE_EXTERNAL, + features.intersects(wgt::Features::EXTERNAL_TEXTURE), + ); naga::valid::Validator::new(flags, caps) } diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index 53e9586da64..025cbae22a0 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -40,8 +40,9 @@ use crate::{ pipeline, pool::ResourcePool, resource::{ - self, AccelerationStructure, Buffer, Fallible, Labeled, ParentDevice, QuerySet, Sampler, - StagingBuffer, Texture, TextureView, TextureViewNotRenderableReason, Tlas, TrackingData, + self, AccelerationStructure, Buffer, ExternalTexture, Fallible, Labeled, ParentDevice, + QuerySet, Sampler, StagingBuffer, Texture, TextureView, TextureViewNotRenderableReason, + Tlas, TrackingData, }, resource_log, snatch::{SnatchGuard, SnatchLock, Snatchable}, @@ -74,6 +75,40 @@ pub(crate) struct CommandIndices { pub(crate) next_acceleration_structure_build_command_index: u64, } +/// Parameters provided to shaders via a uniform buffer, describing a +/// [`binding_model::BindingResource::ExternalTexture`] resource binding. +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Zeroable, bytemuck::Pod)] +pub struct ExternalTextureParams { + /// 4x4 column-major matrix with which to convert sampled YCbCr values + /// to RGBA. + /// This is ignored when `num_planes` is 1. + pub yuv_conversion_matrix: [f32; 16], + /// 3x2 column-major matrix with which to multiply texture coordinates + /// prior to sampling from the external texture. + pub sample_transform: [f32; 6], + /// Size of the external texture. This value should be returned by size + /// queries in shader code. Note that this may not match the dimensions of + /// the underlying texture(s). A value of [0, 0] indicates that the actual + /// size of plane 0 should be used. + pub size: [u32; 2], + /// Number of planes. 1 indicates a single RGBA plane. 2 indicates a Y + /// plane and an interleaved CbCr plane. 3 indicates separate Y, Cb, and Cr + /// planes. + pub num_planes: u32, +} + +impl ExternalTextureParams { + pub fn from_desc(desc: &wgt::ExternalTextureDescriptor) -> Self { + Self { + yuv_conversion_matrix: desc.yuv_conversion_matrix, + size: [desc.width, desc.height], + sample_transform: desc.sample_transform, + num_planes: desc.num_planes() as u32, + } + } +} + /// Structure describing a logical device. Some members are internally mutable, /// stored behind mutexes. pub struct Device { @@ -138,6 +173,11 @@ pub struct Device { // Optional so that we can late-initialize this after the queue is created. pub(crate) timestamp_normalizer: OnceCellOrLock, + /// Uniform buffer containing [`ExternalTextureParams`] with values such + /// that a [`TextureView`] bound to a [`wgt::BindingType::ExternalTexture`] + /// binding point will be rendered correctly. Intended to be used as the + /// [`hal::ExternalTextureBinding::params`] field. + pub(crate) default_external_texture_params_buffer: ManuallyDrop>, // needs to be dropped last #[cfg(feature = "trace")] pub(crate) trace: Mutex>, @@ -165,6 +205,10 @@ impl Drop for Device { // SAFETY: We are in the Drop impl and we don't use self.zero_buffer anymore after this point. let zero_buffer = unsafe { ManuallyDrop::take(&mut self.zero_buffer) }; + // SAFETY: We are in the Drop impl and we don't use + // self.default_external_texture_params_buffer anymore after this point. + let default_external_texture_params_buffer = + unsafe { ManuallyDrop::take(&mut self.default_external_texture_params_buffer) }; // SAFETY: We are in the Drop impl and we don't use self.fence anymore after this point. let fence = unsafe { ManuallyDrop::take(&mut self.fence.write()) }; if let Some(indirect_validation) = self.indirect_validation.take() { @@ -175,6 +219,8 @@ impl Drop for Device { } unsafe { self.raw.destroy_buffer(zero_buffer); + self.raw + .destroy_buffer(default_external_texture_params_buffer); self.raw.destroy_fence(fence); } } @@ -254,6 +300,19 @@ impl Device { } .map_err(DeviceError::from_hal)?; + let default_external_texture_params_buffer = unsafe { + raw_device.create_buffer(&hal::BufferDescriptor { + label: hal_label( + Some("(wgpu internal) default external texture params buffer"), + instance_flags, + ), + size: size_of::() as _, + usage: wgt::BufferUses::COPY_DST | wgt::BufferUses::UNIFORM, + memory_flags: hal::MemoryFlags::empty(), + }) + } + .map_err(DeviceError::from_hal)?; + let alignments = adapter.raw.capabilities.alignments.clone(); let downlevel = adapter.raw.capabilities.downlevel.clone(); @@ -279,6 +338,9 @@ impl Device { adapter: adapter.clone(), queue: OnceCellOrLock::new(), zero_buffer: ManuallyDrop::new(zero_buffer), + default_external_texture_params_buffer: ManuallyDrop::new( + default_external_texture_params_buffer, + ), label: desc.label.to_string(), command_allocator, command_indices: RwLock::new( @@ -329,7 +391,72 @@ impl Device { }) } - pub fn late_init_resources_with_queue(&self) -> Result<(), RequestDeviceError> { + /// Initializes [`Device::default_external_texture_params_buffer`] with + /// required values such that a [`TextureView`] bound to a + /// [`wgt::BindingType::ExternalTexture`] binding point will be rendered + /// correctly. + fn init_default_external_texture_params_buffer(self: &Arc) -> Result<(), DeviceError> { + let data = ExternalTextureParams { + #[rustfmt::skip] + yuv_conversion_matrix: [ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0, + ], + size: [0, 0], + #[rustfmt::skip] + sample_transform: [ + 1.0, 0.0, + 0.0, 1.0, + 0.0, 0.0 + ], + num_planes: 1, + }; + let mut staging_buffer = + StagingBuffer::new(self, wgt::BufferSize::new(size_of_val(&data) as _).unwrap())?; + staging_buffer.write(bytemuck::bytes_of(&data)); + let staging_buffer = staging_buffer.flush(); + + let params_buffer = self.default_external_texture_params_buffer.as_ref(); + let queue = self.get_queue().unwrap(); + let mut pending_writes = queue.pending_writes.lock(); + + unsafe { + pending_writes + .command_encoder + .transition_buffers(&[hal::BufferBarrier { + buffer: params_buffer, + usage: hal::StateTransition { + from: wgt::BufferUses::MAP_WRITE, + to: wgt::BufferUses::COPY_SRC, + }, + }]); + pending_writes.command_encoder.copy_buffer_to_buffer( + staging_buffer.raw(), + params_buffer, + &[hal::BufferCopy { + src_offset: 0, + dst_offset: 0, + size: staging_buffer.size, + }], + ); + pending_writes.consume(staging_buffer); + pending_writes + .command_encoder + .transition_buffers(&[hal::BufferBarrier { + buffer: params_buffer, + usage: hal::StateTransition { + from: wgt::BufferUses::COPY_DST, + to: wgt::BufferUses::UNIFORM, + }, + }]); + } + + Ok(()) + } + + pub fn late_init_resources_with_queue(self: &Arc) -> Result<(), RequestDeviceError> { let queue = self.get_queue().unwrap(); let timestamp_normalizer = crate::timestamp_normalization::TimestampNormalizer::new( @@ -341,6 +468,8 @@ impl Device { .set(timestamp_normalizer) .unwrap_or_else(|_| panic!("Called late_init_resources_with_queue twice")); + self.init_default_external_texture_params_buffer()?; + Ok(()) } @@ -1543,6 +1672,101 @@ impl Device { Ok(view) } + pub(crate) fn create_external_texture( + self: &Arc, + desc: &resource::ExternalTextureDescriptor, + planes: &[Arc], + ) -> Result, resource::CreateExternalTextureError> { + use resource::CreateExternalTextureError; + self.require_features(wgt::Features::EXTERNAL_TEXTURE)?; + self.check_is_valid()?; + + if desc.num_planes() != planes.len() { + return Err(CreateExternalTextureError::IncorrectPlaneCount { + format: desc.format, + expected: desc.num_planes(), + provided: planes.len(), + }); + } + + let planes = planes + .iter() + .enumerate() + .map(|(i, plane)| { + if plane.samples != 1 { + return Err(CreateExternalTextureError::InvalidPlaneMultisample( + plane.samples, + )); + } + + let sample_type = plane + .desc + .format + .sample_type(Some(plane.desc.range.aspect), Some(self.features)) + .unwrap(); + if !matches!(sample_type, TextureSampleType::Float { filterable: true }) { + return Err(CreateExternalTextureError::InvalidPlaneSampleType { + format: plane.desc.format, + sample_type, + }); + } + + if plane.desc.dimension != TextureViewDimension::D2 { + return Err(CreateExternalTextureError::InvalidPlaneDimension( + plane.desc.dimension, + )); + } + + let expected_components = match desc.format { + wgt::ExternalTextureFormat::Rgba => 4, + wgt::ExternalTextureFormat::Nv12 => match i { + 0 => 1, + 1 => 2, + _ => unreachable!(), + }, + wgt::ExternalTextureFormat::Yu12 => 1, + }; + if plane.desc.format.components() != expected_components { + return Err(CreateExternalTextureError::InvalidPlaneFormat { + format: desc.format, + plane: i, + expected: expected_components, + provided: plane.desc.format, + }); + } + + plane.check_usage(wgt::TextureUsages::TEXTURE_BINDING)?; + Ok(plane.clone()) + }) + .collect::>()?; + + let params_data = ExternalTextureParams::from_desc(desc); + let label = desc.label.as_ref().map(|l| alloc::format!("{l} params")); + let params_desc = resource::BufferDescriptor { + label: label.map(Cow::Owned), + size: size_of_val(¶ms_data) as wgt::BufferAddress, + usage: wgt::BufferUsages::UNIFORM | wgt::BufferUsages::COPY_DST, + mapped_at_creation: false, + }; + let params = self.create_buffer(¶ms_desc)?; + self.get_queue().unwrap().write_buffer( + Fallible::Valid(params.clone()), + 0, + bytemuck::bytes_of(¶ms_data), + )?; + + let external_texture = ExternalTexture { + device: self.clone(), + planes, + params, + label: desc.label.to_string(), + tracking_data: TrackingData::new(self.tracker_indices.external_textures.clone()), + }; + let external_texture = Arc::new(external_texture); + + Ok(external_texture) + } + pub(crate) fn create_sampler( self: &Arc, desc: &resource::SamplerDescriptor, @@ -2383,6 +2607,121 @@ impl Device { Ok(tlas.try_raw(snatch_guard)?) } + fn create_external_texture_binding<'a>( + &'a self, + binding: u32, + decl: &wgt::BindGroupLayoutEntry, + external_texture: &'a Arc, + used: &mut BindGroupStates, + snatch_guard: &'a SnatchGuard, + ) -> Result< + hal::ExternalTextureBinding<'a, dyn hal::DynBuffer, dyn hal::DynTextureView>, + binding_model::CreateBindGroupError, + > { + use crate::binding_model::CreateBindGroupError as Error; + + external_texture.same_device(self)?; + + used.external_textures + .insert_single(external_texture.clone()); + + match decl.ty { + wgt::BindingType::ExternalTexture => {} + _ => { + return Err(Error::WrongBindingType { + binding, + actual: decl.ty, + expected: "ExternalTexture", + }); + } + } + + let planes = (0..3) + .map(|i| { + // We always need 3 bindings. If we have fewer than 3 planes + // just bind plane 0 multiple times. The shader will only + // sample from valid planes anyway. + let plane = external_texture + .planes + .get(i) + .unwrap_or(&external_texture.planes[0]); + let internal_use = wgt::TextureUses::RESOURCE; + used.views.insert_single(plane.clone(), internal_use); + let view = plane.try_raw(snatch_guard)?; + Ok(hal::TextureBinding { + view, + usage: internal_use, + }) + }) + // We can remove this intermediate Vec by using + // array::try_from_fn() above, once it stabilizes. + .collect::, Error>>()?; + let planes = planes.try_into().unwrap(); + + used.buffers + .insert_single(external_texture.params.clone(), wgt::BufferUses::UNIFORM); + let params = hal::BufferBinding { + buffer: external_texture.params.try_raw(snatch_guard)?, + offset: 0, + size: wgt::BufferSize::new(external_texture.params.size), + }; + + Ok(hal::ExternalTextureBinding { planes, params }) + } + + fn create_external_texture_binding_from_view<'a>( + &'a self, + binding: u32, + decl: &wgt::BindGroupLayoutEntry, + view: &'a Arc, + used: &mut BindGroupStates, + snatch_guard: &'a SnatchGuard, + ) -> Result< + hal::ExternalTextureBinding<'a, dyn hal::DynBuffer, dyn hal::DynTextureView>, + binding_model::CreateBindGroupError, + > { + use crate::binding_model::CreateBindGroupError as Error; + + view.same_device(self)?; + + let internal_use = self.texture_use_parameters(binding, decl, view, "SampledTexture")?; + used.views.insert_single(view.clone(), internal_use); + + match decl.ty { + wgt::BindingType::ExternalTexture => {} + _ => { + return Err(Error::WrongBindingType { + binding, + actual: decl.ty, + expected: "ExternalTexture", + }); + } + } + + // We need 3 bindings, so just repeat the same texture view 3 times. + let planes = [ + hal::TextureBinding { + view: view.try_raw(snatch_guard)?, + usage: internal_use, + }, + hal::TextureBinding { + view: view.try_raw(snatch_guard)?, + usage: internal_use, + }, + hal::TextureBinding { + view: view.try_raw(snatch_guard)?, + usage: internal_use, + }, + ]; + let params = hal::BufferBinding { + buffer: self.default_external_texture_params_buffer.as_ref(), + offset: 0, + size: None, + }; + + Ok(hal::ExternalTextureBinding { planes, params }) + } + // This function expects the provided bind group layout to be resolved // (not passing a duplicate) beforehand. pub(crate) fn create_bind_group( @@ -2423,6 +2762,7 @@ impl Device { let mut hal_samplers = Vec::new(); let mut hal_textures = Vec::new(); let mut hal_tlas_s = Vec::new(); + let mut hal_external_textures = Vec::new(); let snatch_guard = self.snatchable_lock.read(); for entry in desc.entries.iter() { let binding = entry.binding; @@ -2489,19 +2829,33 @@ impl Device { (res_index, num_bindings) } - Br::TextureView(ref view) => { - let tb = self.create_texture_binding( - binding, - decl, - view, - &mut used, - &mut used_texture_ranges, - &snatch_guard, - )?; - let res_index = hal_textures.len(); - hal_textures.push(tb); - (res_index, 1) - } + Br::TextureView(ref view) => match decl.ty { + wgt::BindingType::ExternalTexture => { + let et = self.create_external_texture_binding_from_view( + binding, + decl, + view, + &mut used, + &snatch_guard, + )?; + let res_index = hal_external_textures.len(); + hal_external_textures.push(et); + (res_index, 1) + } + _ => { + let tb = self.create_texture_binding( + binding, + decl, + view, + &mut used, + &mut used_texture_ranges, + &snatch_guard, + )?; + let res_index = hal_textures.len(); + hal_textures.push(tb); + (res_index, 1) + } + }, Br::TextureViewArray(ref views) => { let num_bindings = views.len(); Self::check_array_binding(self.features, decl.count, num_bindings)?; @@ -2529,6 +2883,18 @@ impl Device { hal_tlas_s.push(tlas); (res_index, 1) } + Br::ExternalTexture(ref et) => { + let et = self.create_external_texture_binding( + binding, + decl, + et, + &mut used, + &snatch_guard, + )?; + let res_index = hal_external_textures.len(); + hal_external_textures.push(et); + (res_index, 1) + } }; hal_entries.push(hal::BindGroupEntry { @@ -2554,6 +2920,7 @@ impl Device { samplers: &hal_samplers, textures: &hal_textures, acceleration_structures: &hal_tlas_s, + external_textures: &hal_external_textures, }; let raw = unsafe { self.raw().create_bind_group(&hal_desc) } .map_err(|e| self.handle_hal_error(e))?; diff --git a/wgpu-core/src/device/trace.rs b/wgpu-core/src/device/trace.rs index 9291ba9f4c6..668158a08a5 100644 --- a/wgpu-core/src/device/trace.rs +++ b/wgpu-core/src/device/trace.rs @@ -58,6 +58,12 @@ pub enum Action<'a> { desc: crate::resource::TextureViewDescriptor<'a>, }, DestroyTextureView(id::TextureViewId), + CreateExternalTexture { + id: id::ExternalTextureId, + desc: crate::resource::ExternalTextureDescriptor<'a>, + planes: alloc::boxed::Box<[id::TextureViewId]>, + }, + DestroyExternalTexture(id::ExternalTextureId), CreateSampler(id::SamplerId, crate::resource::SamplerDescriptor<'a>), DestroySampler(id::SamplerId), GetSurfaceTexture { diff --git a/wgpu-core/src/hub.rs b/wgpu-core/src/hub.rs index f52ac006a84..8dbad492d95 100644 --- a/wgpu-core/src/hub.rs +++ b/wgpu-core/src/hub.rs @@ -111,7 +111,8 @@ use crate::{ pipeline::{ComputePipeline, PipelineCache, RenderPipeline, ShaderModule}, registry::{Registry, RegistryReport}, resource::{ - Blas, Buffer, Fallible, QuerySet, Sampler, StagingBuffer, Texture, TextureView, Tlas, + Blas, Buffer, ExternalTexture, Fallible, QuerySet, Sampler, StagingBuffer, Texture, + TextureView, Tlas, }, }; @@ -133,6 +134,7 @@ pub struct HubReport { pub buffers: RegistryReport, pub textures: RegistryReport, pub texture_views: RegistryReport, + pub external_textures: RegistryReport, pub samplers: RegistryReport, } @@ -181,6 +183,7 @@ pub struct Hub { pub(crate) staging_buffers: Registry, pub(crate) textures: Registry>, pub(crate) texture_views: Registry>, + pub(crate) external_textures: Registry>, pub(crate) samplers: Registry>, pub(crate) blas_s: Registry>, pub(crate) tlas_s: Registry>, @@ -206,6 +209,7 @@ impl Hub { staging_buffers: Registry::new(), textures: Registry::new(), texture_views: Registry::new(), + external_textures: Registry::new(), samplers: Registry::new(), blas_s: Registry::new(), tlas_s: Registry::new(), @@ -230,6 +234,7 @@ impl Hub { buffers: self.buffers.generate_report(), textures: self.textures.generate_report(), texture_views: self.texture_views.generate_report(), + external_textures: self.external_textures.generate_report(), samplers: self.samplers.generate_report(), } } diff --git a/wgpu-core/src/id.rs b/wgpu-core/src/id.rs index 8858e1f8966..965f4d2a3c2 100644 --- a/wgpu-core/src/id.rs +++ b/wgpu-core/src/id.rs @@ -245,6 +245,7 @@ ids! { pub type StagingBufferId StagingBuffer; pub type TextureViewId TextureView; pub type TextureId Texture; + pub type ExternalTextureId ExternalTexture; pub type SamplerId Sampler; pub type BindGroupLayoutId BindGroupLayout; pub type PipelineLayoutId PipelineLayout; diff --git a/wgpu-core/src/indirect_validation/dispatch.rs b/wgpu-core/src/indirect_validation/dispatch.rs index 00e3798e9ba..6e2911c1a46 100644 --- a/wgpu-core/src/indirect_validation/dispatch.rs +++ b/wgpu-core/src/indirect_validation/dispatch.rs @@ -240,6 +240,7 @@ impl Dispatch { samplers: &[], textures: &[], acceleration_structures: &[], + external_textures: &[], }; let dst_bind_group = unsafe { device @@ -286,6 +287,7 @@ impl Dispatch { samplers: &[], textures: &[], acceleration_structures: &[], + external_textures: &[], }; unsafe { device diff --git a/wgpu-core/src/indirect_validation/draw.rs b/wgpu-core/src/indirect_validation/draw.rs index d88acb8d60d..cb7e69dbb68 100644 --- a/wgpu-core/src/indirect_validation/draw.rs +++ b/wgpu-core/src/indirect_validation/draw.rs @@ -143,6 +143,7 @@ impl Draw { samplers: &[], textures: &[], acceleration_structures: &[], + external_textures: &[], }; unsafe { device @@ -692,6 +693,7 @@ fn create_buffer_and_bind_group( samplers: &[], textures: &[], acceleration_structures: &[], + external_textures: &[], }; let bind_group = unsafe { device.create_bind_group(&bind_group_desc) }?; Ok(BufferPoolEntry { buffer, bind_group }) diff --git a/wgpu-core/src/resource.rs b/wgpu-core/src/resource.rs index 46be1c1e689..f756ed6c9d2 100644 --- a/wgpu-core/src/resource.rs +++ b/wgpu-core/src/resource.rs @@ -1805,6 +1805,73 @@ crate::impl_parent_device!(TextureView); crate::impl_storage_item!(TextureView); crate::impl_trackable!(TextureView); +pub type ExternalTextureDescriptor<'a> = wgt::ExternalTextureDescriptor>; + +#[derive(Debug)] +pub struct ExternalTexture { + pub(crate) device: Arc, + /// Between 1 and 3 (inclusive) planes of texture data. + pub(crate) planes: arrayvec::ArrayVec, 3>, + /// Buffer containing a [`crate::device::resource::ExternalTextureParams`] + /// describing the external texture. + pub(crate) params: Arc, + /// The `label` from the descriptor used to create the resource. + pub(crate) label: String, + pub(crate) tracking_data: TrackingData, +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum CreateExternalTextureError { + #[error(transparent)] + Device(#[from] DeviceError), + #[error(transparent)] + MissingFeatures(#[from] MissingFeatures), + #[error(transparent)] + InvalidResource(#[from] InvalidResourceError), + #[error(transparent)] + CreateBuffer(#[from] CreateBufferError), + #[error(transparent)] + QueueWrite(#[from] queue::QueueWriteError), + #[error("External texture format {format:?} expects {expected} planes, but given {provided}")] + IncorrectPlaneCount { + format: wgt::ExternalTextureFormat, + expected: usize, + provided: usize, + }, + #[error("External texture planes cannot be multisampled, but given view with samples = {0}")] + InvalidPlaneMultisample(u32), + #[error("External texture planes expect a filterable float sample type, but given view with format {format:?} (sample type {sample_type:?})")] + InvalidPlaneSampleType { + format: wgt::TextureFormat, + sample_type: wgt::TextureSampleType, + }, + #[error("External texture planes expect 2D dimension, but given view with dimension = {0:?}")] + InvalidPlaneDimension(wgt::TextureViewDimension), + #[error(transparent)] + MissingTextureUsage(#[from] MissingTextureUsageError), + #[error("External texture format {format:?} plane {plane} expects format with {expected} components but given view with format {provided:?} ({} components)", + provided.components())] + InvalidPlaneFormat { + format: wgt::ExternalTextureFormat, + plane: usize, + expected: u8, + provided: wgt::TextureFormat, + }, +} + +impl Drop for ExternalTexture { + fn drop(&mut self) { + resource_log!("Destroy raw {}", self.error_ident()); + } +} + +crate::impl_resource_type!(ExternalTexture); +crate::impl_labeled!(ExternalTexture); +crate::impl_parent_device!(ExternalTexture); +crate::impl_storage_item!(ExternalTexture); +crate::impl_trackable!(ExternalTexture); + /// Describes a [`Sampler`] #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/wgpu-core/src/timestamp_normalization/mod.rs b/wgpu-core/src/timestamp_normalization/mod.rs index dd4d466235c..fd0f87aaf78 100644 --- a/wgpu-core/src/timestamp_normalization/mod.rs +++ b/wgpu-core/src/timestamp_normalization/mod.rs @@ -290,6 +290,7 @@ impl TimestampNormalizer { samplers: &[], textures: &[], acceleration_structures: &[], + external_textures: &[], entries: &[hal::BindGroupEntry { binding: 0, resource_index: 0, diff --git a/wgpu-core/src/track/mod.rs b/wgpu-core/src/track/mod.rs index a878c5f39b6..638727a56e8 100644 --- a/wgpu-core/src/track/mod.rs +++ b/wgpu-core/src/track/mod.rs @@ -222,6 +222,7 @@ pub(crate) struct TrackerIndexAllocators { pub buffers: Arc, pub textures: Arc, pub texture_views: Arc, + pub external_textures: Arc, pub samplers: Arc, pub bind_groups: Arc, pub compute_pipelines: Arc, @@ -238,6 +239,7 @@ impl TrackerIndexAllocators { buffers: Arc::new(SharedTrackerIndexAllocator::new()), textures: Arc::new(SharedTrackerIndexAllocator::new()), texture_views: Arc::new(SharedTrackerIndexAllocator::new()), + external_textures: Arc::new(SharedTrackerIndexAllocator::new()), samplers: Arc::new(SharedTrackerIndexAllocator::new()), bind_groups: Arc::new(SharedTrackerIndexAllocator::new()), compute_pipelines: Arc::new(SharedTrackerIndexAllocator::new()), @@ -427,6 +429,7 @@ impl fmt::Display for InvalidUse { pub(crate) struct BindGroupStates { pub buffers: BufferBindGroupState, pub views: TextureViewBindGroupState, + pub external_textures: StatelessTracker, pub samplers: StatelessTracker, pub acceleration_structures: StatelessTracker, } @@ -436,6 +439,7 @@ impl BindGroupStates { Self { buffers: BufferBindGroupState::new(), views: TextureViewBindGroupState::new(), + external_textures: StatelessTracker::new(), samplers: StatelessTracker::new(), acceleration_structures: StatelessTracker::new(), } diff --git a/wgpu-core/src/validation.rs b/wgpu-core/src/validation.rs index a7e1d4f1d00..0bc2e892920 100644 --- a/wgpu-core/src/validation.rs +++ b/wgpu-core/src/validation.rs @@ -43,6 +43,10 @@ impl From<&ResourceType> for BindingTypeName { fn from(ty: &ResourceType) -> BindingTypeName { match ty { ResourceType::Buffer { .. } => BindingTypeName::Buffer, + ResourceType::Texture { + class: naga::ImageClass::External, + .. + } => BindingTypeName::ExternalTexture, ResourceType::Texture { .. } => BindingTypeName::Texture, ResourceType::Sampler { .. } => BindingTypeName::Sampler, ResourceType::AccelerationStructure { .. } => BindingTypeName::AccelerationStructure, @@ -548,6 +552,7 @@ impl Resource { access: naga_access, } } + BindingType::ExternalTexture => naga::ImageClass::External, _ => { return Err(BindingError::WrongType { binding: (&entry.ty).into(), @@ -655,6 +660,7 @@ impl Resource { f }, }, + naga::ImageClass::External => BindingType::ExternalTexture, } } ResourceType::AccelerationStructure { vertex_return } => { diff --git a/wgpu-hal/examples/halmark/main.rs b/wgpu-hal/examples/halmark/main.rs index 75f3bc2fb9a..032dd911a97 100644 --- a/wgpu-hal/examples/halmark/main.rs +++ b/wgpu-hal/examples/halmark/main.rs @@ -461,6 +461,7 @@ impl Example { samplers: &[&sampler], textures: &[texture_binding], acceleration_structures: &[], + external_textures: &[], entries: &[ hal::BindGroupEntry { binding: 0, @@ -495,6 +496,7 @@ impl Example { samplers: &[], textures: &[], acceleration_structures: &[], + external_textures: &[], entries: &[hal::BindGroupEntry { binding: 0, resource_index: 0, diff --git a/wgpu-hal/examples/ray-traced-triangle/main.rs b/wgpu-hal/examples/ray-traced-triangle/main.rs index a8d3a77b916..eb0494c4997 100644 --- a/wgpu-hal/examples/ray-traced-triangle/main.rs +++ b/wgpu-hal/examples/ray-traced-triangle/main.rs @@ -619,6 +619,7 @@ impl Example { samplers: &[], textures: &[texture_binding], acceleration_structures: &[&tlas], + external_textures: &[], entries: &[ hal::BindGroupEntry { binding: 0, diff --git a/wgpu-hal/src/dynamic/device.rs b/wgpu-hal/src/dynamic/device.rs index fd7c10f254d..de66b1619f6 100644 --- a/wgpu-hal/src/dynamic/device.rs +++ b/wgpu-hal/src/dynamic/device.rs @@ -345,6 +345,11 @@ impl DynDevice for D { .iter() .map(|a| a.expect_downcast_ref()) .collect(); + let external_textures: Vec<_> = desc + .external_textures + .iter() + .map(|et| et.clone().expect_downcast()) + .collect(); let desc = BindGroupDescriptor { label: desc.label.to_owned(), @@ -354,6 +359,7 @@ impl DynDevice for D { textures: &textures, entries: desc.entries, acceleration_structures: &acceleration_structures, + external_textures: &external_textures, }; unsafe { D::create_bind_group(self, &desc) } diff --git a/wgpu-hal/src/dynamic/mod.rs b/wgpu-hal/src/dynamic/mod.rs index a8dbae94ee0..85d8ca00450 100644 --- a/wgpu-hal/src/dynamic/mod.rs +++ b/wgpu-hal/src/dynamic/mod.rs @@ -23,7 +23,8 @@ use wgt::WasmNotSendSync; use crate::{ AccelerationStructureAABBs, AccelerationStructureEntries, AccelerationStructureInstances, AccelerationStructureTriangleIndices, AccelerationStructureTriangleTransform, - AccelerationStructureTriangles, BufferBinding, ProgrammableStage, TextureBinding, + AccelerationStructureTriangles, BufferBinding, ExternalTextureBinding, ProgrammableStage, + TextureBinding, }; /// Base trait for all resources, allows downcasting via [`Any`]. @@ -143,6 +144,16 @@ impl<'a> TextureBinding<'a, dyn DynTextureView> { } } +impl<'a> ExternalTextureBinding<'a, dyn DynBuffer, dyn DynTextureView> { + pub fn expect_downcast( + self, + ) -> ExternalTextureBinding<'a, B, T> { + let planes = self.planes.map(|plane| plane.expect_downcast()); + let params = self.params.expect_downcast(); + ExternalTextureBinding { planes, params } + } +} + impl<'a> ProgrammableStage<'a, dyn DynShaderModule> { fn expect_downcast(self) -> ProgrammableStage<'a, T> { ProgrammableStage { diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index 4184c8c6f1b..a2f7efc715d 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -2034,6 +2034,23 @@ impl<'a, T: DynTextureView + ?Sized> Clone for TextureBinding<'a, T> { } } +#[derive(Debug)] +pub struct ExternalTextureBinding<'a, B: DynBuffer + ?Sized, T: DynTextureView + ?Sized> { + pub planes: [TextureBinding<'a, T>; 3], + pub params: BufferBinding<'a, B>, +} + +impl<'a, B: DynBuffer + ?Sized, T: DynTextureView + ?Sized> Clone + for ExternalTextureBinding<'a, B, T> +{ + fn clone(&self) -> Self { + ExternalTextureBinding { + planes: self.planes.clone(), + params: self.params.clone(), + } + } +} + /// cbindgen:ignore #[derive(Clone, Debug)] pub struct BindGroupEntry { @@ -2067,6 +2084,7 @@ pub struct BindGroupDescriptor< pub textures: &'a [TextureBinding<'a, T>], pub entries: &'a [BindGroupEntry], pub acceleration_structures: &'a [&'a A], + pub external_textures: &'a [ExternalTextureBinding<'a, B, T>], } #[derive(Clone, Debug)] diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 3ba3bbe2683..9bf754a778a 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -6131,6 +6131,73 @@ impl TextureDescriptor { } } +/// Format of an `ExternalTexture`. This indicates the number of underlying +/// planes used by the `ExternalTexture` as well as each plane's format. +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum ExternalTextureFormat { + /// Single [`TextureFormat::Rgba8Unorm`] or [`TextureFormat::Bgra8Unorm`] format plane. + Rgba, + /// [`TextureFormat::R8Unorm`] Y plane, and [`TextureFormat::Rg8Unorm`] + /// interleaved CbCr plane. + Nv12, + /// Separate [`TextureFormat::R8Unorm`] Y, Cb, and Cr planes. + Yu12, +} + +/// Describes an [`ExternalTexture`](../wgpu/struct.ExternalTexture.html). +/// +/// Corresponds to [WebGPU `GPUExternalTextureDescriptor`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpuexternaltexturedescriptor). +#[repr(C)] +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct ExternalTextureDescriptor { + /// Debug label of the external texture. This will show up in graphics + /// debuggers for easy identification. + pub label: L, + /// Width of the external texture. Note that both this and `height` may + /// not match the dimensions of the underlying texture(s). This could be + /// due to a crop rect or rotation. + pub width: u32, + /// Height of the external texture. + pub height: u32, + /// Format of the external texture. + pub format: ExternalTextureFormat, + /// 4x4 column-major matrix with which to convert sampled YCbCr values + /// to RGBA. + /// This is ignored when `format` is [`ExternalTextureFormat::Rgba`]. + pub yuv_conversion_matrix: [f32; 16], + /// 3x2 column-major matrix with which to multiply texture coordinates + /// prior to sampling from the external texture. + pub sample_transform: [f32; 6], +} + +impl ExternalTextureDescriptor { + /// Takes a closure and maps the label of the external texture descriptor into another. + #[must_use] + pub fn map_label(&self, fun: impl FnOnce(&L) -> K) -> ExternalTextureDescriptor { + ExternalTextureDescriptor { + label: fun(&self.label), + width: self.width, + height: self.height, + format: self.format, + yuv_conversion_matrix: self.yuv_conversion_matrix, + sample_transform: self.sample_transform, + } + } + + /// The number of underlying planes used by the external texture. + pub fn num_planes(&self) -> usize { + match self.format { + ExternalTextureFormat::Rgba => 1, + ExternalTextureFormat::Nv12 => 2, + ExternalTextureFormat::Yu12 => 3, + } + } +} + /// Describes a `Sampler`. /// /// For use with `Device::create_sampler`. diff --git a/wgpu/src/api/bind_group.rs b/wgpu/src/api/bind_group.rs index 2f4ae007ff6..8471fca53fd 100644 --- a/wgpu/src/api/bind_group.rs +++ b/wgpu/src/api/bind_group.rs @@ -81,6 +81,12 @@ pub enum BindingResource<'a> { /// built using `build_acceleration_structures` a validation error is generated otherwise this is a part of the /// safety section of `build_acceleration_structures_unsafe_tlas` and so undefined behavior occurs. AccelerationStructure(&'a Tlas), + /// Binding is backed by an external texture. + /// + /// [`Features::EXTERNAL_TEXTURE`] must be supported to use this feature. + /// + /// Corresponds to [`wgt::BindingType::ExternalTexture`]. + ExternalTexture(&'a ExternalTexture), } #[cfg(send_sync)] static_assertions::assert_impl_all!(BindingResource<'_>: Send, Sync); diff --git a/wgpu/src/api/device.rs b/wgpu/src/api/device.rs index 99ed5071df9..68d25e09586 100644 --- a/wgpu/src/api/device.rs +++ b/wgpu/src/api/device.rs @@ -315,6 +315,20 @@ impl Device { } } + /// Creates a new [`ExternalTexture`]. + #[must_use] + pub fn create_external_texture( + &self, + desc: &ExternalTextureDescriptor<'_>, + planes: &[&TextureView], + ) -> ExternalTexture { + let external_texture = self.inner.create_external_texture(desc, planes); + + ExternalTexture { + inner: external_texture, + } + } + /// Creates a [`Buffer`] from a wgpu-hal Buffer. /// /// # Safety diff --git a/wgpu/src/api/external_texture.rs b/wgpu/src/api/external_texture.rs new file mode 100644 index 00000000000..faba59d11c9 --- /dev/null +++ b/wgpu/src/api/external_texture.rs @@ -0,0 +1,24 @@ +use crate::*; + +/// Handle to an external texture on the GPU. +/// +/// It can be created with [`Device::create_external_texture`]. +/// +/// Corresponds to [WebGPU `GPUExternalTexture`](https://gpuweb.github.io/gpuweb/#gpuexternaltexture). +#[derive(Debug, Clone)] +pub struct ExternalTexture { + pub(crate) inner: dispatch::DispatchExternalTexture, +} +#[cfg(send_sync)] +static_assertions::assert_impl_all!(ExternalTexture: Send, Sync); + +crate::cmp::impl_eq_ord_hash_proxy!(ExternalTexture => .inner); + +/// Describes an [`ExternalTexture`]. +/// +/// For use with [`Device::create_external_texture`]. +/// +/// Corresponds to [WebGPU `GPUExternalTextureDescriptor`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpuexternaltexturedescriptor). +pub type ExternalTextureDescriptor<'a> = wgt::ExternalTextureDescriptor>; +static_assertions::assert_impl_all!(ExternalTextureDescriptor<'_>: Send, Sync); diff --git a/wgpu/src/api/mod.rs b/wgpu/src/api/mod.rs index c4804d8b61d..8178d2be576 100644 --- a/wgpu/src/api/mod.rs +++ b/wgpu/src/api/mod.rs @@ -32,6 +32,7 @@ mod common_pipeline; mod compute_pass; mod compute_pipeline; mod device; +mod external_texture; mod instance; mod pipeline_cache; mod pipeline_layout; @@ -60,6 +61,7 @@ pub use common_pipeline::*; pub use compute_pass::*; pub use compute_pipeline::*; pub use device::*; +pub use external_texture::*; pub use instance::*; pub use pipeline_cache::*; pub use pipeline_layout::*; diff --git a/wgpu/src/backend/custom.rs b/wgpu/src/backend/custom.rs index f82e1150b28..767281b5c6a 100644 --- a/wgpu/src/backend/custom.rs +++ b/wgpu/src/backend/custom.rs @@ -79,6 +79,7 @@ dyn_type!(pub ref struct DynTextureView(dyn TextureViewInterface)); dyn_type!(pub ref struct DynSampler(dyn SamplerInterface)); dyn_type!(pub ref struct DynBuffer(dyn BufferInterface)); dyn_type!(pub ref struct DynTexture(dyn TextureInterface)); +dyn_type!(pub ref struct DynExternalTexture(dyn ExternalTextureInterface)); dyn_type!(pub ref struct DynBlas(dyn BlasInterface)); dyn_type!(pub ref struct DynTlas(dyn TlasInterface)); dyn_type!(pub ref struct DynQuerySet(dyn QuerySetInterface)); diff --git a/wgpu/src/backend/webgpu.rs b/wgpu/src/backend/webgpu.rs index 12662e7c010..b3b1c1067c6 100644 --- a/wgpu/src/backend/webgpu.rs +++ b/wgpu/src/backend/webgpu.rs @@ -1259,6 +1259,12 @@ pub struct WebTexture { ident: crate::cmp::Identifier, } +#[derive(Debug)] +pub struct WebExternalTexture { + /// Unique identifier for this ExternalTexture. + ident: crate::cmp::Identifier, +} + #[derive(Debug)] pub struct WebBlas { /// Unique identifier for this Blas. @@ -1392,6 +1398,7 @@ impl_send_sync!(WebTextureView); impl_send_sync!(WebSampler); impl_send_sync!(WebBuffer); impl_send_sync!(WebTexture); +impl_send_sync!(WebExternalTexture); impl_send_sync!(WebBlas); impl_send_sync!(WebTlas); impl_send_sync!(WebQuerySet); @@ -1421,6 +1428,7 @@ crate::cmp::impl_eq_ord_hash_proxy!(WebTextureView => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebSampler => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebBuffer => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebTexture => .ident); +crate::cmp::impl_eq_ord_hash_proxy!(WebExternalTexture => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebBlas => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebTlas => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebQuerySet => .ident); @@ -2009,6 +2017,9 @@ impl dispatch::DeviceInterface for WebDevice { crate::BindingResource::AccelerationStructure(_) => { unimplemented!("Raytracing not implemented for web") } + crate::BindingResource::ExternalTexture(_) => { + unimplemented!("ExternalTexture not implemented for web") + } }; webgpu_sys::GpuBindGroupEntry::new(binding.binding, &mapped_resource) @@ -2243,6 +2254,14 @@ impl dispatch::DeviceInterface for WebDevice { .into() } + fn create_external_texture( + &self, + _desc: &crate::ExternalTextureDescriptor<'_>, + _planes: &[&crate::TextureView], + ) -> dispatch::DispatchExternalTexture { + unimplemented!("ExternalTexture not implemented for web"); + } + fn create_blas( &self, _desc: &crate::CreateBlasDescriptor<'_>, @@ -2746,6 +2765,13 @@ impl Drop for WebTexture { } } +impl dispatch::ExternalTextureInterface for WebExternalTexture {} +impl Drop for WebExternalTexture { + fn drop(&mut self) { + unimplemented!("ExternalTexture not implemented for web"); + } +} + impl dispatch::BlasInterface for WebBlas { fn prepare_compact_async(&self, _callback: BlasCompactCallback) { unimplemented!("Raytracing not implemented for web") diff --git a/wgpu/src/backend/wgpu_core.rs b/wgpu/src/backend/wgpu_core.rs index b44db4dba1f..f7f96579a6d 100644 --- a/wgpu/src/backend/wgpu_core.rs +++ b/wgpu/src/backend/wgpu_core.rs @@ -556,6 +556,12 @@ pub struct CoreTextureView { id: wgc::id::TextureViewId, } +#[derive(Debug)] +pub struct CoreExternalTexture { + pub(crate) context: ContextWgpuCore, + id: wgc::id::ExternalTextureId, +} + #[derive(Debug)] pub struct CoreSampler { pub(crate) context: ContextWgpuCore, @@ -789,6 +795,7 @@ crate::cmp::impl_eq_ord_hash_proxy!(CoreTextureView => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreSampler => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreBuffer => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreTexture => .id); +crate::cmp::impl_eq_ord_hash_proxy!(CoreExternalTexture => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreBlas => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreTlas => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreQuerySet => .id); @@ -1244,6 +1251,9 @@ impl dispatch::DeviceInterface for CoreDevice { acceleration_structure.inner.as_core().id, ) } + BindingResource::ExternalTexture(external_texture) => { + bm::BindingResource::ExternalTexture(external_texture.inner.as_core().id) + } }, }) .collect::>(); @@ -1533,6 +1543,36 @@ impl dispatch::DeviceInterface for CoreDevice { .into() } + fn create_external_texture( + &self, + desc: &crate::ExternalTextureDescriptor<'_>, + planes: &[&crate::TextureView], + ) -> dispatch::DispatchExternalTexture { + let wgt_desc = desc.map_label(|l| l.map(Borrowed)); + let planes = planes + .iter() + .map(|plane| plane.inner.as_core().id) + .collect::>(); + let (id, error) = self + .context + .0 + .device_create_external_texture(self.id, &wgt_desc, &planes, None); + if let Some(cause) = error { + self.context.handle_error( + &self.error_sink, + cause, + desc.label, + "Device::create_external_texture", + ); + } + + CoreExternalTexture { + context: self.context.clone(), + id, + } + .into() + } + fn create_blas( &self, desc: &crate::CreateBlasDescriptor<'_>, @@ -2000,6 +2040,14 @@ impl Drop for CoreTextureView { } } +impl dispatch::ExternalTextureInterface for CoreExternalTexture {} + +impl Drop for CoreExternalTexture { + fn drop(&mut self) { + self.context.0.external_texture_drop(self.id); + } +} + impl dispatch::SamplerInterface for CoreSampler {} impl Drop for CoreSampler { diff --git a/wgpu/src/dispatch.rs b/wgpu/src/dispatch.rs index 558e48a1126..b21efe938a9 100644 --- a/wgpu/src/dispatch.rs +++ b/wgpu/src/dispatch.rs @@ -157,6 +157,11 @@ pub trait DeviceInterface: CommonTraits { ) -> DispatchPipelineCache; fn create_buffer(&self, desc: &crate::BufferDescriptor<'_>) -> DispatchBuffer; fn create_texture(&self, desc: &crate::TextureDescriptor<'_>) -> DispatchTexture; + fn create_external_texture( + &self, + desc: &crate::ExternalTextureDescriptor<'_>, + planes: &[&crate::TextureView], + ) -> DispatchExternalTexture; fn create_blas( &self, desc: &crate::CreateBlasDescriptor<'_>, @@ -257,6 +262,7 @@ pub trait TextureInterface: CommonTraits { fn destroy(&self); } +pub trait ExternalTextureInterface: CommonTraits {} pub trait BlasInterface: CommonTraits { fn prepare_compact_async(&self, callback: BlasCompactCallback); fn ready_for_compaction(&self) -> bool; @@ -840,6 +846,7 @@ dispatch_types! {ref type DispatchTextureView: TextureViewInterface = CoreTextur dispatch_types! {ref type DispatchSampler: SamplerInterface = CoreSampler, WebSampler, DynSampler} dispatch_types! {ref type DispatchBuffer: BufferInterface = CoreBuffer, WebBuffer, DynBuffer} dispatch_types! {ref type DispatchTexture: TextureInterface = CoreTexture, WebTexture, DynTexture} +dispatch_types! {ref type DispatchExternalTexture: ExternalTextureInterface = CoreExternalTexture, WebExternalTexture, DynExternalTexture} dispatch_types! {ref type DispatchBlas: BlasInterface = CoreBlas, WebBlas, DynBlas} dispatch_types! {ref type DispatchTlas: TlasInterface = CoreTlas, WebTlas, DynTlas} dispatch_types! {ref type DispatchQuerySet: QuerySetInterface = CoreQuerySet, WebQuerySet, DynQuerySet} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index c72eba64832..9ba11ba1242 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -76,20 +76,20 @@ pub use wgt::{ CommandBufferDescriptor, CompareFunction, CompositeAlphaMode, CopyExternalImageDestInfo, CoreCounters, DepthBiasState, DepthStencilState, DeviceLostReason, DeviceType, DownlevelCapabilities, DownlevelFlags, DownlevelLimits, Dx12BackendOptions, Dx12Compiler, - DxcShaderModel, DynamicOffset, Extent3d, Face, Features, FeaturesWGPU, FeaturesWebGPU, - FilterMode, FrontFace, GlBackendOptions, GlFenceBehavior, Gles3MinorVersion, HalCounters, - ImageSubresourceRange, IndexFormat, InstanceDescriptor, InstanceFlags, InternalCounters, - Limits, MemoryBudgetThresholds, MemoryHints, MultisampleState, NoopBackendOptions, Origin2d, - Origin3d, PipelineStatisticsTypes, PollError, PollStatus, PolygonMode, PowerPreference, - PredefinedColorSpace, PresentMode, PresentationTimestamp, PrimitiveState, PrimitiveTopology, - PushConstantRange, QueryType, RenderBundleDepthStencil, RequestAdapterError, - SamplerBindingType, SamplerBorderColor, ShaderLocation, ShaderModel, ShaderRuntimeChecks, - ShaderStages, StencilFaceState, StencilOperation, StencilState, StorageTextureAccess, - SurfaceCapabilities, SurfaceStatus, TexelCopyBufferLayout, TextureAspect, TextureDimension, - TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures, TextureSampleType, - TextureTransition, TextureUsages, TextureUses, TextureViewDimension, Trace, VertexAttribute, - VertexFormat, VertexStepMode, WasmNotSend, WasmNotSendSync, WasmNotSync, COPY_BUFFER_ALIGNMENT, - COPY_BYTES_PER_ROW_ALIGNMENT, MAP_ALIGNMENT, PUSH_CONSTANT_ALIGNMENT, + DxcShaderModel, DynamicOffset, Extent3d, ExternalTextureFormat, Face, Features, FeaturesWGPU, + FeaturesWebGPU, FilterMode, FrontFace, GlBackendOptions, GlFenceBehavior, Gles3MinorVersion, + HalCounters, ImageSubresourceRange, IndexFormat, InstanceDescriptor, InstanceFlags, + InternalCounters, Limits, MemoryBudgetThresholds, MemoryHints, MultisampleState, + NoopBackendOptions, Origin2d, Origin3d, PipelineStatisticsTypes, PollError, PollStatus, + PolygonMode, PowerPreference, PredefinedColorSpace, PresentMode, PresentationTimestamp, + PrimitiveState, PrimitiveTopology, PushConstantRange, QueryType, RenderBundleDepthStencil, + RequestAdapterError, SamplerBindingType, SamplerBorderColor, ShaderLocation, ShaderModel, + ShaderRuntimeChecks, ShaderStages, StencilFaceState, StencilOperation, StencilState, + StorageTextureAccess, SurfaceCapabilities, SurfaceStatus, TexelCopyBufferLayout, TextureAspect, + TextureDimension, TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures, + TextureSampleType, TextureTransition, TextureUsages, TextureUses, TextureViewDimension, Trace, + VertexAttribute, VertexFormat, VertexStepMode, WasmNotSend, WasmNotSendSync, WasmNotSync, + COPY_BUFFER_ALIGNMENT, COPY_BYTES_PER_ROW_ALIGNMENT, MAP_ALIGNMENT, PUSH_CONSTANT_ALIGNMENT, QUERY_RESOLVE_BUFFER_ALIGNMENT, QUERY_SET_MAX_QUERIES, QUERY_SIZE, VERTEX_STRIDE_ALIGNMENT, }; // wasm-only types, we try to keep as many types non-platform