diff --git a/naga-cli/src/bin/naga.rs b/naga-cli/src/bin/naga.rs index b018058613..44369e9df7 100644 --- a/naga-cli/src/bin/naga.rs +++ b/naga-cli/src/bin/naga.rs @@ -539,6 +539,7 @@ fn run() -> anyhow::Result<()> { 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::TEXTURE_EXTERNAL, + Some("hlsl") => C::empty(), _ => C::TEXTURE_EXTERNAL, }; caps & !missing diff --git a/naga/src/back/hlsl/help.rs b/naga/src/back/hlsl/help.rs index bea917547b..83d3a71816 100644 --- a/naga/src/back/hlsl/help.rs +++ b/naga/src/back/hlsl/help.rs @@ -33,8 +33,8 @@ use super::{ super::FunctionCtx, writer::{ ABS_FUNCTION, DIV_FUNCTION, EXTRACT_BITS_FUNCTION, F2I32_FUNCTION, F2I64_FUNCTION, - F2U32_FUNCTION, F2U64_FUNCTION, IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION, - INSERT_BITS_FUNCTION, MOD_FUNCTION, NEG_FUNCTION, + F2U32_FUNCTION, F2U64_FUNCTION, IMAGE_LOAD_EXTERNAL_FUNCTION, + IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION, INSERT_BITS_FUNCTION, MOD_FUNCTION, NEG_FUNCTION, }, BackendResult, WrappedType, }; @@ -45,8 +45,14 @@ pub(super) struct WrappedArrayLength { pub(super) writable: bool, } +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +pub(super) struct WrappedImageLoad { + pub(super) class: crate::ImageClass, +} + #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct WrappedImageSample { + pub(super) class: crate::ImageClass, pub(super) clamp_to_edge: bool, } @@ -195,7 +201,9 @@ impl<W: Write> super::Writer<'_, W> { let storage_format_str = format.to_hlsl_str(); write!(self.out, "<{storage_format_str}>")? } - crate::ImageClass::External => unimplemented!(), + crate::ImageClass::External => { + unreachable!("external images should be handled by `write_global_external_texture`"); + } } Ok(()) } @@ -254,12 +262,233 @@ impl<W: Write> super::Writer<'_, W> { Ok(()) } + pub(super) fn write_wrapped_image_load_function( + &mut self, + module: &crate::Module, + load: WrappedImageLoad, + ) -> BackendResult { + match load { + WrappedImageLoad { + class: crate::ImageClass::External, + } => { + let l1 = crate::back::Level(1); + let l2 = l1.next(); + let l3 = l2.next(); + let params_ty_name = &self.names + [&NameKey::Type(module.special_types.external_texture_params.unwrap())]; + writeln!(self.out, "float4 {IMAGE_LOAD_EXTERNAL_FUNCTION}(")?; + writeln!(self.out, "{l1}Texture2D<float4> plane0,")?; + writeln!(self.out, "{l1}Texture2D<float4> plane1,")?; + writeln!(self.out, "{l1}Texture2D<float4> plane2,")?; + writeln!(self.out, "{l1}{params_ty_name} params,")?; + writeln!(self.out, "{l1}uint2 coords)")?; + writeln!(self.out, "{{")?; + writeln!(self.out, "{l1}uint2 plane0_size;")?; + writeln!( + self.out, + "{l1}plane0.GetDimensions(plane0_size.x, plane0_size.y);" + )?; + // Clamp coords to provided size of external texture to prevent OOB read. + // If params.size is zero then clamp to the actual size of the texture. + writeln!( + self.out, + "{l1}uint2 cropped_size = params.size != 0 ? params.size : plane0_size;" + )?; + writeln!(self.out, "{l1}coords = min(coords, cropped_size - 1);")?; + + // Apply load transformation. We declare our matrices as row_major in + // HLSL, therefore we must reverse the order of this multiplication + writeln!(self.out, "{l1}float3x2 load_transform = float3x2(")?; + writeln!(self.out, "{l2}params.load_transform_0,")?; + writeln!(self.out, "{l2}params.load_transform_1,")?; + writeln!(self.out, "{l2}params.load_transform_2")?; + writeln!(self.out, "{l1});")?; + writeln!(self.out, "{l1}uint2 plane0_coords = uint2(round(mul(float3(coords, 1.0), load_transform)));")?; + writeln!(self.out, "{l1}if (params.num_planes == 1u) {{")?; + // For single plane, simply read from plane0 + writeln!( + self.out, + "{l2}return plane0.Load(uint3(plane0_coords, 0u));" + )?; + writeln!(self.out, "{l1}}} else {{")?; + + // Chroma planes may be subsampled so we must scale the coords accordingly. + writeln!(self.out, "{l2}uint2 plane1_size;")?; + writeln!( + self.out, + "{l2}plane1.GetDimensions(plane1_size.x, plane1_size.y);" + )?; + writeln!(self.out, "{l2}uint2 plane1_coords = uint2(floor(float2(plane0_coords) * float2(plane1_size) / float2(plane0_size)));")?; + + // For multi-plane, read the Y value from plane 0 + writeln!( + self.out, + "{l2}float y = plane0.Load(uint3(plane0_coords, 0u)).x;" + )?; + + writeln!(self.out, "{l2}float2 uv;")?; + writeln!(self.out, "{l2}if (params.num_planes == 2u) {{")?; + // Read UV from interleaved plane 1 + writeln!( + self.out, + "{l3}uv = plane1.Load(uint3(plane1_coords, 0u)).xy;" + )?; + writeln!(self.out, "{l2}}} else {{")?; + // Read U and V from planes 1 and 2 respectively + writeln!(self.out, "{l3}uint2 plane2_size;")?; + writeln!( + self.out, + "{l3}plane2.GetDimensions(plane2_size.x, plane2_size.y);" + )?; + writeln!(self.out, "{l2}uint2 plane2_coords = uint2(floor(float2(plane0_coords) * float2(plane2_size) / float2(plane0_size)));")?; + writeln!(self.out, "{l3}uv = float2(plane1.Load(uint3(plane1_coords, 0u)).x, plane2.Load(uint3(plane2_coords, 0u)).x);")?; + writeln!(self.out, "{l2}}}")?; + + // Convert from YUV to RGB. We declare our matrices as row_major in HLSL, + // therefore we must reverse the order of this multiplication + writeln!( + self.out, + "{l2}return mul(float4(y, uv, 1.0), params.yuv_conversion_matrix);" + )?; + + writeln!(self.out, "{l1}}}")?; + writeln!(self.out, "}}")?; + writeln!(self.out)?; + } + _ => {} + } + + Ok(()) + } + pub(super) fn write_wrapped_image_sample_function( &mut self, + module: &crate::Module, sample: WrappedImageSample, ) -> BackendResult { match sample { WrappedImageSample { + class: crate::ImageClass::External, + clamp_to_edge: true, + } => { + let l1 = crate::back::Level(1); + let l2 = l1.next(); + let l3 = l2.next(); + let params_ty_name = &self.names + [&NameKey::Type(module.special_types.external_texture_params.unwrap())]; + writeln!( + self.out, + "float4 {IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION}(" + )?; + writeln!(self.out, "{l1}Texture2D<float4> plane0,")?; + writeln!(self.out, "{l1}Texture2D<float4> plane1,")?; + writeln!(self.out, "{l1}Texture2D<float4> plane2,")?; + writeln!(self.out, "{l1}{params_ty_name} params,")?; + writeln!(self.out, "{l1}SamplerState samp,")?; + writeln!(self.out, "{l1}float2 coords)")?; + writeln!(self.out, "{{")?; + writeln!(self.out, "{l1}float2 plane0_size;")?; + writeln!( + self.out, + "{l1}plane0.GetDimensions(plane0_size.x, plane0_size.y);" + )?; + writeln!(self.out, "{l1}float3x2 sample_transform = float3x2(")?; + writeln!(self.out, "{l2}params.sample_transform_0,")?; + writeln!(self.out, "{l2}params.sample_transform_1,")?; + writeln!(self.out, "{l2}params.sample_transform_2")?; + writeln!(self.out, "{l1});")?; + // Apply sample transformation. We declare our matrices as row_major in + // HLSL, therefore we must reverse the order of this multiplication + writeln!( + self.out, + "{l1}coords = mul(float3(coords, 1.0), sample_transform);" + )?; + // Calculate the sample bounds taking in to account the transform, + // bearing in mind that it may contain a flip on either axis. We must + // calculate and adjust for the half-texel separately for each plane as + // it depends on the texture size which may vary between planes. + writeln!( + self.out, + "{l1}float2 bounds_min = mul(float3(0.0, 0.0, 1.0), sample_transform);" + )?; + writeln!( + self.out, + "{l1}float2 bounds_max = mul(float3(1.0, 1.0, 1.0), sample_transform);" + )?; + writeln!(self.out, "{l1}float4 bounds = float4(min(bounds_min, bounds_max), max(bounds_min, bounds_max));")?; + writeln!( + self.out, + "{l1}float2 plane0_half_texel = float2(0.5, 0.5) / plane0_size;" + )?; + writeln!( + self.out, + "{l1}float2 plane0_coords = clamp(coords, bounds.xy + plane0_half_texel, bounds.zw - plane0_half_texel);" + )?; + writeln!(self.out, "{l1}if (params.num_planes == 1u) {{")?; + // For single plane, simply sample from plane0 + writeln!( + self.out, + "{l2}return plane0.SampleLevel(samp, plane0_coords, 0.0f);" + )?; + writeln!(self.out, "{l1}}} else {{")?; + + writeln!(self.out, "{l2}float2 plane1_size;")?; + writeln!( + self.out, + "{l2}plane1.GetDimensions(plane1_size.x, plane1_size.y);" + )?; + writeln!( + self.out, + "{l2}float2 plane1_half_texel = float2(0.5, 0.5) / plane1_size;" + )?; + writeln!( + self.out, + "{l2}float2 plane1_coords = clamp(coords, bounds.xy + plane1_half_texel, bounds.zw - plane1_half_texel);" + )?; + + // For multi-plane, sample the Y value from plane 0 + writeln!( + self.out, + "{l2}float y = plane0.SampleLevel(samp, plane0_coords, 0.0f).x;" + )?; + writeln!(self.out, "{l2}float2 uv;")?; + writeln!(self.out, "{l2}if (params.num_planes == 2u) {{")?; + // Sample UV from interleaved plane 1 + writeln!( + self.out, + "{l3}uv = plane1.SampleLevel(samp, plane1_coords, 0.0f).xy;" + )?; + writeln!(self.out, "{l2}}} else {{")?; + // Sample U and V from planes 1 and 2 respectively + writeln!(self.out, "{l3}float2 plane2_size;")?; + writeln!( + self.out, + "{l3}plane2.GetDimensions(plane2_size.x, plane2_size.y);" + )?; + writeln!( + self.out, + "{l3}float2 plane2_half_texel = float2(0.5, 0.5) / plane2_size;" + )?; + writeln!(self.out, "{l3}float2 plane2_coords = clamp(coords, bounds.xy + plane2_half_texel, bounds.zw - plane2_half_texel);")?; + writeln!(self.out, "{l3}uv = float2(plane1.SampleLevel(samp, plane1_coords, 0.0f).x, plane2.SampleLevel(samp, plane2_coords, 0.0f).x);")?; + writeln!(self.out, "{l2}}}")?; + + // Convert from YUV to RGB. We declare our matrices as row_major in HLSL, + // therefore we must reverse the order of this multiplication + writeln!( + self.out, + "{l2}return mul(float4(y, uv, 1.0), params.yuv_conversion_matrix);" + )?; + writeln!(self.out, "{l1}}}")?; + writeln!(self.out, "}}")?; + writeln!(self.out)?; + } + WrappedImageSample { + class: + crate::ImageClass::Sampled { + kind: ScalarKind::Float, + multi: false, + }, clamp_to_edge: true, } => { writeln!(self.out, "float4 {IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION}(Texture2D<float4> tex, SamplerState samp, float2 coords) {{")?; @@ -291,7 +520,7 @@ impl<W: Write> super::Writer<'_, W> { crate::ImageClass::Depth { multi: false } => "Depth", crate::ImageClass::Sampled { multi: false, .. } => "", crate::ImageClass::Storage { .. } => "RW", - crate::ImageClass::External => unimplemented!(), + crate::ImageClass::External => "External", }; let arrayed_str = if query.arrayed { "Array" } else { "" }; let query_str = match query.query { @@ -322,102 +551,133 @@ impl<W: Write> super::Writer<'_, W> { ImageDimension as IDim, }; - const ARGUMENT_VARIABLE_NAME: &str = "tex"; - const RETURN_VARIABLE_NAME: &str = "ret"; - const MIP_LEVEL_PARAM: &str = "mip_level"; - - // Write function return type and name - let ret_ty = func_ctx.resolve_type(expr_handle, &module.types); - self.write_value_type(module, ret_ty)?; - write!(self.out, " ")?; - self.write_wrapped_image_query_function_name(wiq)?; + match wiq.class { + crate::ImageClass::External => { + if wiq.query != ImageQuery::Size { + return Err(super::Error::Custom( + "External images only support `Size` queries".into(), + )); + } - // Write function parameters - write!(self.out, "(")?; - // Texture always first parameter - self.write_image_type(wiq.dim, wiq.arrayed, wiq.class)?; - write!(self.out, " {ARGUMENT_VARIABLE_NAME}")?; - // Mipmap is a second parameter if exists - if let ImageQuery::SizeLevel = wiq.query { - write!(self.out, ", uint {MIP_LEVEL_PARAM}")?; - } - writeln!(self.out, ")")?; + write!(self.out, "uint2 ")?; + self.write_wrapped_image_query_function_name(wiq)?; + let params_name = &self.names + [&NameKey::Type(module.special_types.external_texture_params.unwrap())]; + // Only plane0 and params are used by this implementation, but it's easier to + // always take all of them as arguments so that we can unconditionally expand an + // external texture expression each of its parts. + writeln!(self.out, "(Texture2D<float4> plane0, Texture2D<float4> plane1, Texture2D<float4> plane2, {params_name} params) {{")?; + let l1 = crate::back::Level(1); + let l2 = l1.next(); + writeln!(self.out, "{l1}if (any(params.size)) {{")?; + writeln!(self.out, "{l2}return params.size;")?; + writeln!(self.out, "{l1}}} else {{")?; + // params.size == (0, 0) indicates to query and return plane 0's actual size + writeln!(self.out, "{l2}uint2 ret;")?; + writeln!(self.out, "{l2}plane0.GetDimensions(ret.x, ret.y);")?; + writeln!(self.out, "{l2}return ret;")?; + writeln!(self.out, "{l1}}}")?; + writeln!(self.out, "}}")?; + writeln!(self.out)?; + } + _ => { + const ARGUMENT_VARIABLE_NAME: &str = "tex"; + const RETURN_VARIABLE_NAME: &str = "ret"; + const MIP_LEVEL_PARAM: &str = "mip_level"; + + // Write function return type and name + let ret_ty = func_ctx.resolve_type(expr_handle, &module.types); + self.write_value_type(module, ret_ty)?; + write!(self.out, " ")?; + self.write_wrapped_image_query_function_name(wiq)?; + + // Write function parameters + write!(self.out, "(")?; + // Texture always first parameter + self.write_image_type(wiq.dim, wiq.arrayed, wiq.class)?; + write!(self.out, " {ARGUMENT_VARIABLE_NAME}")?; + // Mipmap is a second parameter if exists + if let ImageQuery::SizeLevel = wiq.query { + write!(self.out, ", uint {MIP_LEVEL_PARAM}")?; + } + writeln!(self.out, ")")?; - // Write function body - writeln!(self.out, "{{")?; + // Write function body + writeln!(self.out, "{{")?; - let array_coords = usize::from(wiq.arrayed); - // extra parameter is the mip level count or the sample count - let extra_coords = match wiq.class { - crate::ImageClass::Storage { .. } => 0, - crate::ImageClass::Sampled { .. } | crate::ImageClass::Depth { .. } => 1, - crate::ImageClass::External => unimplemented!(), - }; + let array_coords = usize::from(wiq.arrayed); + // extra parameter is the mip level count or the sample count + let extra_coords = match wiq.class { + crate::ImageClass::Storage { .. } => 0, + crate::ImageClass::Sampled { .. } | crate::ImageClass::Depth { .. } => 1, + crate::ImageClass::External => unreachable!(), + }; - // GetDimensions Overloaded Methods - // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-to-getdimensions#overloaded-methods - let (ret_swizzle, number_of_params) = match wiq.query { - ImageQuery::Size | ImageQuery::SizeLevel => { - let ret = match wiq.dim { - IDim::D1 => "x", - IDim::D2 => "xy", - IDim::D3 => "xyz", - IDim::Cube => "xy", + // GetDimensions Overloaded Methods + // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-to-getdimensions#overloaded-methods + let (ret_swizzle, number_of_params) = match wiq.query { + ImageQuery::Size | ImageQuery::SizeLevel => { + let ret = match wiq.dim { + IDim::D1 => "x", + IDim::D2 => "xy", + IDim::D3 => "xyz", + IDim::Cube => "xy", + }; + (ret, ret.len() + array_coords + extra_coords) + } + ImageQuery::NumLevels | ImageQuery::NumSamples | ImageQuery::NumLayers => { + if wiq.arrayed || wiq.dim == IDim::D3 { + ("w", 4) + } else { + ("z", 3) + } + } }; - (ret, ret.len() + array_coords + extra_coords) - } - ImageQuery::NumLevels | ImageQuery::NumSamples | ImageQuery::NumLayers => { - if wiq.arrayed || wiq.dim == IDim::D3 { - ("w", 4) - } else { - ("z", 3) - } - } - }; - // Write `GetDimensions` function. - writeln!(self.out, "{INDENT}uint4 {RETURN_VARIABLE_NAME};")?; - write!(self.out, "{INDENT}{ARGUMENT_VARIABLE_NAME}.GetDimensions(")?; - match wiq.query { - ImageQuery::SizeLevel => { - write!(self.out, "{MIP_LEVEL_PARAM}, ")?; - } - _ => match wiq.class { - crate::ImageClass::Sampled { multi: true, .. } - | crate::ImageClass::Depth { multi: true } - | crate::ImageClass::Storage { .. } => {} - _ => { - // Write zero mipmap level for supported types - write!(self.out, "0, ")?; + // Write `GetDimensions` function. + writeln!(self.out, "{INDENT}uint4 {RETURN_VARIABLE_NAME};")?; + write!(self.out, "{INDENT}{ARGUMENT_VARIABLE_NAME}.GetDimensions(")?; + match wiq.query { + ImageQuery::SizeLevel => { + write!(self.out, "{MIP_LEVEL_PARAM}, ")?; + } + _ => match wiq.class { + crate::ImageClass::Sampled { multi: true, .. } + | crate::ImageClass::Depth { multi: true } + | crate::ImageClass::Storage { .. } => {} + _ => { + // Write zero mipmap level for supported types + write!(self.out, "0, ")?; + } + }, } - }, - } - - for component in COMPONENTS[..number_of_params - 1].iter() { - write!(self.out, "{RETURN_VARIABLE_NAME}.{component}, ")?; - } - // write last parameter without comma and space for last parameter - write!( - self.out, - "{}.{}", - RETURN_VARIABLE_NAME, - COMPONENTS[number_of_params - 1] - )?; + for component in COMPONENTS[..number_of_params - 1].iter() { + write!(self.out, "{RETURN_VARIABLE_NAME}.{component}, ")?; + } - writeln!(self.out, ");")?; + // write last parameter without comma and space for last parameter + write!( + self.out, + "{}.{}", + RETURN_VARIABLE_NAME, + COMPONENTS[number_of_params - 1] + )?; - // Write return value - writeln!( - self.out, - "{INDENT}return {RETURN_VARIABLE_NAME}.{ret_swizzle};" - )?; + writeln!(self.out, ");")?; - // End of function body - writeln!(self.out, "}}")?; - // Write extra new line - writeln!(self.out)?; + // Write return value + writeln!( + self.out, + "{INDENT}return {RETURN_VARIABLE_NAME}.{ret_swizzle};" + )?; + // End of function body + writeln!(self.out, "}}")?; + // Write extra new line + writeln!(self.out)?; + } + } Ok(()) } @@ -1557,10 +1817,31 @@ impl<W: Write> super::Writer<'_, W> { self.write_wrapped_array_length_function(wal)?; } } - crate::Expression::ImageSample { clamp_to_edge, .. } => { - let wrapped = WrappedImageSample { clamp_to_edge }; + crate::Expression::ImageLoad { image, .. } => { + let class = match *func_ctx.resolve_type(image, &module.types) { + crate::TypeInner::Image { class, .. } => class, + _ => unreachable!(), + }; + let wrapped = WrappedImageLoad { class }; + if self.wrapped.insert(WrappedType::ImageLoad(wrapped)) { + self.write_wrapped_image_load_function(module, wrapped)?; + } + } + crate::Expression::ImageSample { + image, + clamp_to_edge, + .. + } => { + let class = match *func_ctx.resolve_type(image, &module.types) { + crate::TypeInner::Image { class, .. } => class, + _ => unreachable!(), + }; + let wrapped = WrappedImageSample { + class, + clamp_to_edge, + }; if self.wrapped.insert(WrappedType::ImageSample(wrapped)) { - self.write_wrapped_image_sample_function(wrapped)?; + self.write_wrapped_image_sample_function(module, wrapped)?; } } crate::Expression::ImageQuery { image, query } => { diff --git a/naga/src/back/hlsl/keywords.rs b/naga/src/back/hlsl/keywords.rs index f38d47483f..7b2ab839d4 100644 --- a/naga/src/back/hlsl/keywords.rs +++ b/naga/src/back/hlsl/keywords.rs @@ -826,6 +826,7 @@ pub const RESERVED: &[&str] = &[ super::writer::INSERT_BITS_FUNCTION, super::writer::SAMPLER_HEAP_VAR, super::writer::COMPARISON_SAMPLER_HEAP_VAR, + super::writer::SAMPLE_EXTERNAL_TEXTURE_FUNCTION, super::writer::ABS_FUNCTION, super::writer::DIV_FUNCTION, super::writer::MOD_FUNCTION, @@ -834,6 +835,7 @@ pub const RESERVED: &[&str] = &[ super::writer::F2U32_FUNCTION, super::writer::F2I64_FUNCTION, super::writer::F2U64_FUNCTION, + super::writer::IMAGE_LOAD_EXTERNAL_FUNCTION, super::writer::IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION, ]; diff --git a/naga/src/back/hlsl/mod.rs b/naga/src/back/hlsl/mod.rs index 7f55398040..0c9dd4bf6e 100644 --- a/naga/src/back/hlsl/mod.rs +++ b/naga/src/back/hlsl/mod.rs @@ -106,6 +106,37 @@ index buffer for each bind group. This buffer is accessed in the shader to get t sampler index within the heap. See the wgpu_hal dx12 backend documentation for more information. +# External textures + +Support for [`crate::ImageClass::External`] textures is implemented by lowering +each external texture global variable to 3 `Texture2D<float4>`s, and a `cbuffer` +of type `NagaExternalTextureParams`. This provides up to 3 planes of texture +data (for example single planar RGBA, or separate Y, Cb, and Cr planes), and the +parameters buffer containing information describing how to handle these +correctly. The bind target to use for each of these globals is specified via +[`Options::external_texture_binding_map`]. + +External textures are supported by WGSL's `textureDimensions()`, +`textureLoad()`, and `textureSampleBaseClampToEdge()` built-in functions. These +are implemented using helper functions. See the following functions for how +these are generated: + * `Writer::write_wrapped_image_query_function` + * `Writer::write_wrapped_image_load_function` + * `Writer::write_wrapped_image_sample_function` + +Ideally the set of global variables could be wrapped in a single struct that +could conveniently be passed around. But, alas, HLSL does not allow structs to +have `Texture2D` members. Fortunately, however, external textures can only be +used as arguments to either built-in or user-defined functions. We therefore +expand any external texture function argument to four consecutive arguments (3 +textures and the params struct) when declaring user-defined functions, and +ensure our built-in function implementations take the same arguments. Then, +whenever we need to emit an external texture in `Writer::write_expr`, which +fortunately can only ever be for a global variable or function argument, we +simply emit the variable name of each of the three textures and the parameters +struct in a comma-separated list. This won't win any awards for elegance, but +it works for our purposes. + [hlsl]: https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl [ilov]: https://gpuweb.github.io/gpuweb/wgsl/#internal-value-layout [16bb]: https://github.com/microsoft/DirectXShaderCompiler/wiki/Buffer-Packing#constant-buffer-packing @@ -335,6 +366,41 @@ where pub type DynamicStorageBufferOffsetsTargets = alloc::collections::BTreeMap<u32, OffsetsBindTarget>; +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct ExternalTextureBindTarget { + pub planes: [BindTarget; 3], + pub params: BindTarget, +} + +#[cfg(any(feature = "serialize", feature = "deserialize"))] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +struct ExternalTextureBindingMapSerialization { + resource_binding: crate::ResourceBinding, + bind_target: ExternalTextureBindTarget, +} + +#[cfg(feature = "deserialize")] +fn deserialize_external_texture_binding_map<'de, D>( + deserializer: D, +) -> Result<ExternalTextureBindingMap, D::Error> +where + D: serde::Deserializer<'de>, +{ + use serde::Deserialize; + + let vec = Vec::<ExternalTextureBindingMapSerialization>::deserialize(deserializer)?; + let mut map = ExternalTextureBindingMap::default(); + for item in vec { + map.insert(item.resource_binding, item.bind_target); + } + Ok(map) +} +pub type ExternalTextureBindingMap = + alloc::collections::BTreeMap<crate::ResourceBinding, ExternalTextureBindTarget>; + /// Shorthand result used internally by the backend type BackendResult = Result<(), Error>; @@ -381,6 +447,11 @@ pub struct Options { serde(deserialize_with = "deserialize_storage_buffer_offsets") )] pub dynamic_storage_buffer_offsets_targets: DynamicStorageBufferOffsetsTargets, + #[cfg_attr( + feature = "deserialize", + serde(deserialize_with = "deserialize_external_texture_binding_map") + )] + pub external_texture_binding_map: ExternalTextureBindingMap, /// Should workgroup variables be zero initialized (by polyfilling)? pub zero_initialize_workgroup_memory: bool, /// Should we restrict indexing of vectors, matrices and arrays? @@ -401,6 +472,7 @@ impl Default for Options { sampler_buffer_binding_map: alloc::collections::BTreeMap::default(), push_constants_target: None, dynamic_storage_buffer_offsets_targets: alloc::collections::BTreeMap::new(), + external_texture_binding_map: ExternalTextureBindingMap::default(), zero_initialize_workgroup_memory: true, restrict_indexing: true, force_loop_bounding: true, @@ -425,6 +497,29 @@ impl Options { None => Err(EntryPointError::MissingBinding(*res_binding)), } } + + fn resolve_external_texture_resource_binding( + &self, + res_binding: &crate::ResourceBinding, + ) -> Result<ExternalTextureBindTarget, EntryPointError> { + match self.external_texture_binding_map.get(res_binding) { + Some(target) => Ok(*target), + None if self.fake_missing_bindings => { + let fake = BindTarget { + space: res_binding.group as u8, + register: res_binding.binding, + binding_array_size: None, + dynamic_storage_buffer_offsets_index: None, + restrict_indexing: false, + }; + Ok(ExternalTextureBindTarget { + planes: [fake, fake, fake], + params: fake, + }) + } + None => Err(EntryPointError::MissingBinding(*res_binding)), + } + } } /// Reflection info for entry point names. @@ -479,6 +574,7 @@ enum WrappedType { ArrayLength(help::WrappedArrayLength), ImageSample(help::WrappedImageSample), ImageQuery(help::WrappedImageQuery), + ImageLoad(help::WrappedImageLoad), ImageLoadScalar(crate::Scalar), Constructor(help::WrappedConstructor), StructMatrixAccess(help::WrappedStructMatrixAccess), diff --git a/naga/src/back/hlsl/writer.rs b/naga/src/back/hlsl/writer.rs index 2f98b1fbfe..1cd388d5e1 100644 --- a/naga/src/back/hlsl/writer.rs +++ b/naga/src/back/hlsl/writer.rs @@ -17,7 +17,7 @@ use super::{ use crate::{ back::{self, get_entry_points, Baked}, common, - proc::{self, index, NameKey}, + proc::{self, index, ExternalTextureNameKey, NameKey}, valid, Handle, Module, RayQueryFunction, Scalar, ScalarKind, ShaderStage, TypeInner, }; @@ -34,6 +34,7 @@ pub(crate) const EXTRACT_BITS_FUNCTION: &str = "naga_extractBits"; pub(crate) const INSERT_BITS_FUNCTION: &str = "naga_insertBits"; pub(crate) const SAMPLER_HEAP_VAR: &str = "nagaSamplerHeap"; pub(crate) const COMPARISON_SAMPLER_HEAP_VAR: &str = "nagaComparisonSamplerHeap"; +pub(crate) const SAMPLE_EXTERNAL_TEXTURE_FUNCTION: &str = "nagaSampleExternalTexture"; pub(crate) const ABS_FUNCTION: &str = "naga_abs"; pub(crate) const DIV_FUNCTION: &str = "naga_div"; pub(crate) const MOD_FUNCTION: &str = "naga_mod"; @@ -44,6 +45,7 @@ pub(crate) const F2I64_FUNCTION: &str = "naga_f2i64"; pub(crate) const F2U64_FUNCTION: &str = "naga_f2u64"; pub(crate) const IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION: &str = "nagaTextureSampleBaseClampToEdge"; +pub(crate) const IMAGE_LOAD_EXTERNAL_FUNCTION: &str = "nagaTextureLoadExternal"; enum Index { Expression(Handle<crate::Expression>), @@ -431,6 +433,10 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { .find(|&(var_handle, var)| match var.binding { Some(ref binding) if !info[var_handle].is_empty() => { self.options.resolve_resource_binding(binding).is_err() + && self + .options + .resolve_external_texture_resource_binding(binding) + .is_err() } _ => false, }) @@ -473,8 +479,14 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { match var.binding { Some(ref binding) if !info[var_handle].is_empty() => { if let Err(err) = self.options.resolve_resource_binding(binding) { - ep_error = Some(err); - break; + if self + .options + .resolve_external_texture_resource_binding(binding) + .is_err() + { + ep_error = Some(err); + break; + } } } _ => {} @@ -904,6 +916,25 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { let global = &module.global_variables[handle]; let inner = &module.types[global.ty].inner; + let handle_ty = match *inner { + TypeInner::BindingArray { ref base, .. } => &module.types[*base].inner, + _ => inner, + }; + + // External textures are handled entirely differently, so defer entirely to that method. + // We do so prior to calling resolve_resource_binding() below, as we even need to resolve + // their bindings separately. + let is_external_texture = matches!( + *handle_ty, + TypeInner::Image { + class: crate::ImageClass::External, + .. + } + ); + if is_external_texture { + return self.write_global_external_texture(module, handle, global); + } + if let Some(ref binding) = global.binding { if let Err(err) = self.options.resolve_resource_binding(binding) { log::info!( @@ -916,11 +947,6 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { } } - let handle_ty = match *inner { - TypeInner::BindingArray { ref base, .. } => &module.types[*base].inner, - _ => inner, - }; - // Samplers are handled entirely differently, so defer entirely to that method. let is_sampler = matches!(*handle_ty, TypeInner::Sampler { .. }); @@ -1133,6 +1159,70 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { Ok(()) } + /// Write the declarations for an external texture global variable. + /// These are emitted as multiple global variables: Three `Texture2D`s + /// (one for each plane) and a parameters cbuffer. + fn write_global_external_texture( + &mut self, + module: &Module, + handle: Handle<crate::GlobalVariable>, + global: &crate::GlobalVariable, + ) -> BackendResult { + let res_binding = global + .binding + .as_ref() + .expect("External texture global variables must have a resource binding"); + let ext_tex_bindings = match self + .options + .resolve_external_texture_resource_binding(res_binding) + { + Ok(bindings) => bindings, + Err(err) => { + log::info!( + "Skipping global {:?} (name {:?}) for being inaccessible: {}", + handle, + global.name, + err, + ); + return Ok(()); + } + }; + + let mut write_plane = |bt: &super::BindTarget, name| -> BackendResult { + write!( + self.out, + "Texture2D<float4> {}: register(t{}", + name, bt.register + )?; + if bt.space != 0 { + write!(self.out, ", space{}", bt.space)?; + } + writeln!(self.out, ");")?; + Ok(()) + }; + for (i, bt) in ext_tex_bindings.planes.iter().enumerate() { + let plane_name = &self.names + [&NameKey::ExternalTextureGlobalVariable(handle, ExternalTextureNameKey::Plane(i))]; + write_plane(bt, plane_name)?; + } + + let params_name = &self.names + [&NameKey::ExternalTextureGlobalVariable(handle, ExternalTextureNameKey::Params)]; + let params_ty_name = + &self.names[&NameKey::Type(module.special_types.external_texture_params.unwrap())]; + write!( + self.out, + "cbuffer {}: register(b{}", + params_name, ext_tex_bindings.params.register + )?; + if ext_tex_bindings.params.space != 0 { + write!(self.out, ", space{}", ext_tex_bindings.params.space)?; + } + writeln!(self.out, ") {{ {params_ty_name} {params_name}; }};")?; + + Ok(()) + } + /// Helper method used to write global constants /// /// # Notes @@ -1485,25 +1575,51 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { if index != 0 { write!(self.out, ", ")?; } - // Write argument type - let arg_ty = match module.types[arg.ty].inner { - // pointers in function arguments are expected and resolve to `inout` - TypeInner::Pointer { base, .. } => { - //TODO: can we narrow this down to just `in` when possible? - write!(self.out, "inout ")?; - base - } - _ => arg.ty, - }; - self.write_type(module, arg_ty)?; - let argument_name = - &self.names[&NameKey::FunctionArgument(handle, index as u32)]; + // External texture arguments must be expanded into separate + // arguments for each plane and the params buffer. + if let TypeInner::Image { + class: crate::ImageClass::External, + .. + } = module.types[arg.ty].inner + { + let plane_names = [0, 1, 2].map(|i| { + &self.names[&NameKey::ExternalTextureFunctionArgument( + handle, + index as u32, + ExternalTextureNameKey::Plane(i), + )] + }); + let params_name = &self.names[&NameKey::ExternalTextureFunctionArgument( + handle, + index as u32, + ExternalTextureNameKey::Params, + )]; + let params_ty_name = &self.names + [&NameKey::Type(module.special_types.external_texture_params.unwrap())]; + write!(self.out, "Texture2D<float4> {}, Texture2D<float4> {}, Texture2D<float4> {}, {params_ty_name} {}", + plane_names[0], plane_names[1], plane_names[2], params_name)?; + } else { + // Write argument type + let arg_ty = match module.types[arg.ty].inner { + // pointers in function arguments are expected and resolve to `inout` + TypeInner::Pointer { base, .. } => { + //TODO: can we narrow this down to just `in` when possible? + write!(self.out, "inout ")?; + base + } + _ => arg.ty, + }; + self.write_type(module, arg_ty)?; + + let argument_name = + &self.names[&NameKey::FunctionArgument(handle, index as u32)]; - // Write argument name. Space is important. - write!(self.out, " {argument_name}")?; - if let TypeInner::Array { base, size, .. } = module.types[arg_ty].inner { - self.write_array_size(module, base, size)?; + // Write argument name. Space is important. + write!(self.out, " {argument_name}")?; + if let TypeInner::Array { base, size, .. } = module.types[arg_ty].inner { + self.write_array_size(module, base, size)?; + } } } } @@ -3117,9 +3233,34 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { } } Expression::FunctionArgument(pos) => { - let key = func_ctx.argument_key(pos); - let name = &self.names[&key]; - write!(self.out, "{name}")?; + let ty = func_ctx.resolve_type(expr, &module.types); + + // We know that any external texture function argument has been expanded into + // separate consecutive arguments for each plane and the parameters buffer. And we + // also know that external textures can only ever be used as an argument to another + // function. Therefore we can simply emit each of the expanded arguments in a + // consecutive comma-separated list. + if let TypeInner::Image { + class: crate::ImageClass::External, + .. + } = *ty + { + let plane_names = [0, 1, 2].map(|i| { + &self.names[&func_ctx + .external_texture_argument_key(pos, ExternalTextureNameKey::Plane(i))] + }); + let params_name = &self.names[&func_ctx + .external_texture_argument_key(pos, ExternalTextureNameKey::Params)]; + write!( + self.out, + "{}, {}, {}, {}", + plane_names[0], plane_names[1], plane_names[2], params_name + )?; + } else { + let key = func_ctx.argument_key(pos); + let name = &self.names[&key]; + write!(self.out, "{name}")?; + } } Expression::ImageSample { coordinate, @@ -3282,7 +3423,34 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { let is_storage_space = matches!(global_variable.space, crate::AddressSpace::Storage { .. }); - if !is_binding_array_of_samplers && !is_storage_space { + // Our external texture global variable has been expanded into multiple + // global variables, one for each plane and the parameters buffer. + // External textures can only ever be used as arguments to a function + // call, and we know that an external texture argument to any function + // will have been expanded to separate consecutive arguments for each + // plane and the parameters buffer. Therefore we can simply emit each of + // the expanded global variables in a consecutive comma-separated list. + if let TypeInner::Image { + class: crate::ImageClass::External, + .. + } = *ty + { + let plane_names = [0, 1, 2].map(|i| { + &self.names[&NameKey::ExternalTextureGlobalVariable( + handle, + ExternalTextureNameKey::Plane(i), + )] + }); + let params_name = &self.names[&NameKey::ExternalTextureGlobalVariable( + handle, + ExternalTextureNameKey::Params, + )]; + write!( + self.out, + "{}, {}, {}, {}", + plane_names[0], plane_names[1], plane_names[2], params_name + )?; + } else if !is_binding_array_of_samplers && !is_storage_space { let name = &self.names[&NameKey::GlobalVariable(handle)]; write!(self.out, "{name}")?; } @@ -4113,6 +4281,17 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { ) -> Result<(), Error> { let mut wrapping_type = None; match *func_ctx.resolve_type(image, &module.types) { + TypeInner::Image { + class: crate::ImageClass::External, + .. + } => { + write!(self.out, "{IMAGE_LOAD_EXTERNAL_FUNCTION}(")?; + self.write_expr(module, image, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, coordinate, func_ctx)?; + write!(self.out, ")")?; + return Ok(()); + } TypeInner::Image { class: crate::ImageClass::Storage { format, .. }, .. diff --git a/naga/src/back/mod.rs b/naga/src/back/mod.rs index d7b14475bf..0d13d63dd9 100644 --- a/naga/src/back/mod.rs +++ b/naga/src/back/mod.rs @@ -196,6 +196,31 @@ impl FunctionCtx<'_> { } } + /// Helper method that generates a [`NameKey`](crate::proc::NameKey) for an external texture + /// function argument. + /// + /// # Panics + /// - If the function arguments are less or equal to `arg` + /// - If `self.ty` is not `FunctionType::Function`. + pub const fn external_texture_argument_key( + &self, + arg: u32, + external_texture_key: crate::proc::ExternalTextureNameKey, + ) -> crate::proc::NameKey { + match self.ty { + FunctionType::Function(handle) => { + crate::proc::NameKey::ExternalTextureFunctionArgument( + handle, + arg, + external_texture_key, + ) + } + FunctionType::EntryPoint(_) => { + panic!("External textures cannot be used as arguments to entry points") + } + } + } + /// Returns true if the given expression points to a fixed-function pipeline input. pub fn is_fixed_function_input( &self, diff --git a/naga/src/back/wgsl/writer.rs b/naga/src/back/wgsl/writer.rs index 58c1b734f0..dd2696c17f 100644 --- a/naga/src/back/wgsl/writer.rs +++ b/naga/src/back/wgsl/writer.rs @@ -109,12 +109,28 @@ impl<W: Write> Writer<W> { self.required_polyfills.clear(); } - fn is_builtin_wgsl_struct(&self, module: &Module, handle: Handle<crate::Type>) -> bool { + /// Determine if `ty` is the Naga IR presentation of a WGSL builtin type. + /// + /// Return true if `ty` refers to the Naga IR form of a WGSL builtin type + /// like `__atomic_compare_exchange_result`. + /// + /// Even though the module may use the type, the WGSL backend should avoid + /// emitting a definition for it, since it is [predeclared] in WGSL. + /// + /// This also covers types like [`NagaExternalTextureParams`], which other + /// backends use to lower WGSL constructs like external textures to their + /// implementations. WGSL can express these directly, so the types need not + /// be emitted. + /// + /// [predeclared]: https://www.w3.org/TR/WGSL/#predeclared + /// [`NagaExternalTextureParams`]: crate::ir::SpecialTypes::external_texture_params + fn is_builtin_wgsl_struct(&self, module: &Module, ty: Handle<crate::Type>) -> bool { module .special_types .predeclared_types .values() - .any(|t| *t == handle) + .any(|t| *t == ty) + || Some(ty) == module.special_types.external_texture_params } pub fn write(&mut self, module: &Module, info: &valid::ModuleInfo) -> BackendResult { diff --git a/naga/src/compact/mod.rs b/naga/src/compact/mod.rs index fe4296be84..7fbb5c532f 100644 --- a/naga/src/compact/mod.rs +++ b/naga/src/compact/mod.rs @@ -380,6 +380,7 @@ impl<'module> ModuleTracer<'module> { ref ray_intersection, ref ray_vertex_return, ref predeclared_types, + ref external_texture_params, } = *special_types; if let Some(ray_desc) = *ray_desc { @@ -391,6 +392,12 @@ impl<'module> ModuleTracer<'module> { if let Some(ray_vertex_return) = *ray_vertex_return { self.types_used.insert(ray_vertex_return); } + // The `external_texture_params` type is generated purely as a + // convenience to the backends. While it will never actually be used in + // the IR, it must be marked as used so that it survives compaction. + if let Some(external_texture_params) = *external_texture_params { + self.types_used.insert(external_texture_params); + } for (_, &handle) in predeclared_types { self.types_used.insert(handle); } @@ -532,6 +539,7 @@ impl ModuleMap { ref mut ray_intersection, ref mut ray_vertex_return, ref mut predeclared_types, + ref mut external_texture_params, } = *special; if let Some(ref mut ray_desc) = *ray_desc { @@ -545,6 +553,10 @@ impl ModuleMap { self.types.adjust(ray_vertex_return); } + if let Some(ref mut external_texture_params) = *external_texture_params { + self.types.adjust(external_texture_params); + } + for handle in predeclared_types.values_mut() { self.types.adjust(handle); } diff --git a/naga/src/front/type_gen.rs b/naga/src/front/type_gen.rs index 9a01b637d5..d58561796b 100644 --- a/naga/src/front/type_gen.rs +++ b/naga/src/front/type_gen.rs @@ -276,6 +276,100 @@ impl crate::Module { handle } + /// Generate [`SpecialTypes::external_texture_params`]. + /// + /// [`SpecialTypes::external_texture_params`]: crate::ir::SpecialTypes::external_texture_params + pub fn generate_external_texture_params_type(&mut self) -> Handle<crate::Type> { + if let Some(handle) = self.special_types.external_texture_params { + return handle; + } + + let ty_u32 = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar(crate::Scalar::U32), + }, + Span::UNDEFINED, + ); + let ty_vec2u = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Vector { + size: crate::VectorSize::Bi, + scalar: crate::Scalar::U32, + }, + }, + Span::UNDEFINED, + ); + let ty_mat3x2f = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Matrix { + columns: crate::VectorSize::Tri, + rows: crate::VectorSize::Bi, + scalar: crate::Scalar::F32, + }, + }, + Span::UNDEFINED, + ); + let ty_mat4x4f = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Matrix { + columns: crate::VectorSize::Quad, + rows: crate::VectorSize::Quad, + scalar: crate::Scalar::F32, + }, + }, + Span::UNDEFINED, + ); + + let handle = self.types.insert( + crate::Type { + name: Some("NagaExternalTextureParams".to_string()), + inner: crate::TypeInner::Struct { + members: vec![ + crate::StructMember { + name: Some("yuv_conversion_matrix".to_string()), + ty: ty_mat4x4f, + binding: None, + offset: 0, + }, + crate::StructMember { + name: Some("sample_transform".to_string()), + ty: ty_mat3x2f, + binding: None, + offset: 64, + }, + crate::StructMember { + name: Some("load_transform".to_string()), + ty: ty_mat3x2f, + binding: None, + offset: 88, + }, + crate::StructMember { + name: Some("size".to_string()), + ty: ty_vec2u, + binding: None, + offset: 112, + }, + crate::StructMember { + name: Some("num_planes".to_string()), + ty: ty_u32, + binding: None, + offset: 120, + }, + ], + span: 128, + }, + }, + Span::UNDEFINED, + ); + + self.special_types.external_texture_params = Some(handle); + handle + } + /// Populate this module's [`SpecialTypes::predeclared_types`] type and return the handle. /// /// [`SpecialTypes::predeclared_types`]: crate::SpecialTypes::predeclared_types diff --git a/naga/src/front/wgsl/lower/mod.rs b/naga/src/front/wgsl/lower/mod.rs index c8545ca654..3b6e7806c3 100644 --- a/naga/src/front/wgsl/lower/mod.rs +++ b/naga/src/front/wgsl/lower/mod.rs @@ -3983,11 +3983,29 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { dim, arrayed, class, - } => ir::TypeInner::Image { - dim, - arrayed, - class, - }, + } => { + if class == crate::ImageClass::External { + // Other than the WGSL backend, every backend that supports + // external textures does so by lowering them to a set of + // ordinary textures and some parameters saying how to + // sample from them. We don't know which backend will + // consume the `Module` we're building, but in case it's not + // WGSL, populate `SpecialTypes::external_texture_params` + // with the type the backend will use for the parameter + // buffer. + // + // This is *not* the type we are lowering here: that's an + // ordinary `TypeInner::Image`. But the fact we are + // lowering a `texture_external` implies the backends may + // need `SpecialTypes::external_texture_params` too. + ctx.module.generate_external_texture_params_type(); + } + ir::TypeInner::Image { + dim, + arrayed, + class, + } + } ast::Type::Sampler { comparison } => ir::TypeInner::Sampler { comparison }, ast::Type::AccelerationStructure { vertex_return } => { ir::TypeInner::AccelerationStructure { vertex_return } diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index 0101954849..f21992ab03 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -2346,6 +2346,25 @@ pub struct SpecialTypes { /// Call [`Module::generate_vertex_return_type`] pub ray_vertex_return: Option<Handle<Type>>, + /// Struct containing parameters required by some backends to emit code for + /// [`ImageClass::External`] textures. + /// + /// In WGSL, this type would be: + /// + /// ```ignore + /// struct NagaExternalTextureParams { // align size offset + /// yuv_conversion_matrix: mat4x4<f32>, // 16 64 0 + /// sample_transform: mat3x2<f32>, // 8 24 64 + /// load_transform: mat3x2<f32>, // 8 24 88 + /// size: vec2<u32>, // 8 8 112 + /// num_planes: u32, // 4 4 120 + /// } // whole struct: 16 128 + /// ``` + /// + /// Call [`Module::generate_external_texture_params_type`] to populate this + /// if needed and return the handle. + pub external_texture_params: Option<Handle<Type>>, + /// Types for predeclared wgsl types instantiated on demand. /// /// Call [`Module::generate_predeclared_type`] to populate this if diff --git a/naga/src/proc/mod.rs b/naga/src/proc/mod.rs index f2584c64b3..413e49c1ee 100644 --- a/naga/src/proc/mod.rs +++ b/naga/src/proc/mod.rs @@ -18,7 +18,7 @@ pub use constant_evaluator::{ pub use emitter::Emitter; pub use index::{BoundsCheckPolicies, BoundsCheckPolicy, IndexableLength, IndexableLengthError}; pub use layouter::{Alignment, LayoutError, LayoutErrorInner, Layouter, TypeLayout}; -pub use namer::{EntryPointIndex, NameKey, Namer}; +pub use namer::{EntryPointIndex, ExternalTextureNameKey, NameKey, Namer}; pub use overloads::{Conclusion, MissingSpecialType, OverloadSet, Rule}; pub use terminator::ensure_block_returns; use thiserror::Error; diff --git a/naga/src/proc/namer.rs b/naga/src/proc/namer.rs index 504617dd1d..0b812ff036 100644 --- a/naga/src/proc/namer.rs +++ b/naga/src/proc/namer.rs @@ -15,6 +15,40 @@ use crate::{arena::Handle, FastHashMap, FastHashSet}; pub type EntryPointIndex = u16; const SEPARATOR: char = '_'; +/// A component of a lowered external texture. +/// +/// Whereas the WGSL backend implements [`ImageClass::External`] +/// images directly, most other Naga backends lower them to a +/// collection of ordinary textures that represent individual planes +/// (as received from a video decoder, perhaps), together with a +/// struct of parameters saying how they should be cropped, sampled, +/// and color-converted. +/// +/// This lowering means that individual globals and function +/// parameters in Naga IR must be split out by the backends into +/// collections of globals and parameters of simpler types. +/// +/// A value of this enum serves as a name key for one specific +/// component in the lowered representation of an external texture. +/// That is, these keys are for variables/parameters that do not exist +/// in the Naga IR, only in its lowered form. +/// +/// [`ImageClass::External`]: crate::ir::ImageClass::External +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum ExternalTextureNameKey { + Plane(usize), + Params, +} + +impl ExternalTextureNameKey { + const ALL: &[(&str, ExternalTextureNameKey)] = &[ + ("_plane0", ExternalTextureNameKey::Plane(0)), + ("_plane1", ExternalTextureNameKey::Plane(1)), + ("_plane2", ExternalTextureNameKey::Plane(2)), + ("_params", ExternalTextureNameKey::Params), + ]; +} + #[derive(Debug, Eq, Hash, PartialEq)] pub enum NameKey { Constant(Handle<crate::Constant>), @@ -37,6 +71,17 @@ pub enum NameKey { /// Entry point version of `FunctionOobLocal`. EntryPointOobLocal(EntryPointIndex, Handle<crate::Type>), + + /// A global variable holding a component of a lowered external texture. + /// + /// See [`ExternalTextureNameKey`] for details. + ExternalTextureGlobalVariable(Handle<crate::GlobalVariable>, ExternalTextureNameKey), + + /// A function argument holding a component of a lowered external + /// texture. + /// + /// See [`ExternalTextureNameKey`] for details. + ExternalTextureFunctionArgument(Handle<crate::Function>, u32, ExternalTextureNameKey), } /// This processor assigns names to all the things in a module @@ -272,6 +317,27 @@ impl Namer { for (index, arg) in fun.arguments.iter().enumerate() { let name = self.call_or(&arg.name, "param"); output.insert(NameKey::FunctionArgument(fun_handle, index as u32), name); + + if matches!( + module.types[arg.ty].inner, + crate::TypeInner::Image { + class: crate::ImageClass::External, + .. + } + ) { + let base = arg.name.as_deref().unwrap_or("param"); + for &(suffix, ext_key) in ExternalTextureNameKey::ALL { + let name = self.call(&format!("{base}_{suffix}")); + output.insert( + NameKey::ExternalTextureFunctionArgument( + fun_handle, + index as u32, + ext_key, + ), + name, + ); + } + } } for (handle, var) in fun.local_variables.iter() { let name = self.call_or(&var.name, "local"); @@ -282,6 +348,23 @@ impl Namer { for (handle, var) in module.global_variables.iter() { let name = self.call_or(&var.name, "global"); output.insert(NameKey::GlobalVariable(handle), name); + + if matches!( + module.types[var.ty].inner, + crate::TypeInner::Image { + class: crate::ImageClass::External, + .. + } + ) { + let base = var.name.as_deref().unwrap_or("global"); + for &(suffix, ext_key) in ExternalTextureNameKey::ALL { + let name = self.call(&format!("{base}_{suffix}")); + output.insert( + NameKey::ExternalTextureGlobalVariable(handle, ext_key), + name, + ); + } + } } for (handle, constant) in module.constants.iter() { diff --git a/naga/tests/in/wgsl/texture-external.toml b/naga/tests/in/wgsl/texture-external.toml index f8f46d4223..3ea75be88d 100644 --- a/naga/tests/in/wgsl/texture-external.toml +++ b/naga/tests/in/wgsl/texture-external.toml @@ -1,2 +1,15 @@ god_mode = true -targets = "IR | WGSL" +targets = "HLSL | IR | WGSL" + +[[hlsl.binding_map]] +resource_binding = { group = 0, binding = 1 } +bind_target = { register = 0, space = 0 } + +[[hlsl.external_texture_binding_map]] +resource_binding = { group = 0, binding = 0 } +bind_target.planes = [ + { space = 0, register = 0 }, + { space = 0, register = 1 }, + { space = 0, register = 2 }, +] +bind_target.params = { space = 0, register = 3 } diff --git a/naga/tests/out/hlsl/wgsl-texture-external.hlsl b/naga/tests/out/hlsl/wgsl-texture-external.hlsl new file mode 100644 index 0000000000..baf8141521 --- /dev/null +++ b/naga/tests/out/hlsl/wgsl-texture-external.hlsl @@ -0,0 +1,143 @@ +struct NagaExternalTextureParams { + row_major float4x4 yuv_conversion_matrix; + float2 sample_transform_0; float2 sample_transform_1; float2 sample_transform_2; + float2 load_transform_0; float2 load_transform_1; float2 load_transform_2; + uint2 size; + uint num_planes; + int _end_pad_0; +}; + +Texture2D<float4> tex_plane0_: register(t0); +Texture2D<float4> tex_plane1_: register(t1); +Texture2D<float4> tex_plane2_: register(t2); +cbuffer tex_params: register(b3) { NagaExternalTextureParams tex_params; }; +SamplerState nagaSamplerHeap[2048]: register(s0, space0); +SamplerComparisonState nagaComparisonSamplerHeap[2048]: register(s0, space1); +StructuredBuffer<uint> nagaGroup0SamplerIndexArray : register(t0, space255); +static const SamplerState samp = nagaSamplerHeap[nagaGroup0SamplerIndexArray[0]]; + +float4 nagaTextureSampleBaseClampToEdge( + Texture2D<float4> plane0, + Texture2D<float4> plane1, + Texture2D<float4> plane2, + NagaExternalTextureParams params, + SamplerState samp, + float2 coords) +{ + float2 plane0_size; + plane0.GetDimensions(plane0_size.x, plane0_size.y); + float3x2 sample_transform = float3x2( + params.sample_transform_0, + params.sample_transform_1, + params.sample_transform_2 + ); + coords = mul(float3(coords, 1.0), sample_transform); + float2 bounds_min = mul(float3(0.0, 0.0, 1.0), sample_transform); + float2 bounds_max = mul(float3(1.0, 1.0, 1.0), sample_transform); + float4 bounds = float4(min(bounds_min, bounds_max), max(bounds_min, bounds_max)); + float2 plane0_half_texel = float2(0.5, 0.5) / plane0_size; + float2 plane0_coords = clamp(coords, bounds.xy + plane0_half_texel, bounds.zw - plane0_half_texel); + if (params.num_planes == 1u) { + return plane0.SampleLevel(samp, plane0_coords, 0.0f); + } else { + float2 plane1_size; + plane1.GetDimensions(plane1_size.x, plane1_size.y); + float2 plane1_half_texel = float2(0.5, 0.5) / plane1_size; + float2 plane1_coords = clamp(coords, bounds.xy + plane1_half_texel, bounds.zw - plane1_half_texel); + float y = plane0.SampleLevel(samp, plane0_coords, 0.0f).x; + float2 uv; + if (params.num_planes == 2u) { + uv = plane1.SampleLevel(samp, plane1_coords, 0.0f).xy; + } else { + float2 plane2_size; + plane2.GetDimensions(plane2_size.x, plane2_size.y); + float2 plane2_half_texel = float2(0.5, 0.5) / plane2_size; + float2 plane2_coords = clamp(coords, bounds.xy + plane2_half_texel, bounds.zw - plane2_half_texel); + uv = float2(plane1.SampleLevel(samp, plane1_coords, 0.0f).x, plane2.SampleLevel(samp, plane2_coords, 0.0f).x); + } + return mul(float4(y, uv, 1.0), params.yuv_conversion_matrix); + } +} + +float4 nagaTextureLoadExternal( + Texture2D<float4> plane0, + Texture2D<float4> plane1, + Texture2D<float4> plane2, + NagaExternalTextureParams params, + uint2 coords) +{ + uint2 plane0_size; + plane0.GetDimensions(plane0_size.x, plane0_size.y); + uint2 cropped_size = params.size != 0 ? params.size : plane0_size; + coords = min(coords, cropped_size - 1); + float3x2 load_transform = float3x2( + params.load_transform_0, + params.load_transform_1, + params.load_transform_2 + ); + uint2 plane0_coords = uint2(round(mul(float3(coords, 1.0), load_transform))); + if (params.num_planes == 1u) { + return plane0.Load(uint3(plane0_coords, 0u)); + } else { + uint2 plane1_size; + plane1.GetDimensions(plane1_size.x, plane1_size.y); + uint2 plane1_coords = uint2(floor(float2(plane0_coords) * float2(plane1_size) / float2(plane0_size))); + float y = plane0.Load(uint3(plane0_coords, 0u)).x; + float2 uv; + if (params.num_planes == 2u) { + uv = plane1.Load(uint3(plane1_coords, 0u)).xy; + } else { + uint2 plane2_size; + plane2.GetDimensions(plane2_size.x, plane2_size.y); + uint2 plane2_coords = uint2(floor(float2(plane0_coords) * float2(plane2_size) / float2(plane0_size))); + uv = float2(plane1.Load(uint3(plane1_coords, 0u)).x, plane2.Load(uint3(plane2_coords, 0u)).x); + } + return mul(float4(y, uv, 1.0), params.yuv_conversion_matrix); + } +} + +uint2 NagaExternalDimensions2D(Texture2D<float4> plane0, Texture2D<float4> plane1, Texture2D<float4> plane2, NagaExternalTextureParams params) { + if (any(params.size)) { + return params.size; + } else { + uint2 ret; + plane0.GetDimensions(ret.x, ret.y); + return ret; + } +} + +float4 test(Texture2D<float4> t_plane0_, Texture2D<float4> t_plane1_, Texture2D<float4> t_plane2_, NagaExternalTextureParams t_params) +{ + float4 a = (float4)0; + float4 b = (float4)0; + uint2 c = (uint2)0; + + float4 _e4 = nagaTextureSampleBaseClampToEdge(t_plane0_, t_plane1_, t_plane2_, t_params, samp, (0.0).xx); + a = _e4; + float4 _e8 = nagaTextureLoadExternal(t_plane0_, t_plane1_, t_plane2_, t_params, (0u).xx); + b = _e8; + c = NagaExternalDimensions2D(t_plane0_, t_plane1_, t_plane2_, t_params); + float4 _e12 = a; + float4 _e13 = b; + uint2 _e15 = c; + return ((_e12 + _e13) + float2(_e15).xyxy); +} + +float4 fragment_main() : SV_Target0 +{ + const float4 _e1 = test(tex_plane0_, tex_plane1_, tex_plane2_, tex_params); + return _e1; +} + +float4 vertex_main() : SV_Position +{ + const float4 _e1 = test(tex_plane0_, tex_plane1_, tex_plane2_, tex_params); + return _e1; +} + +[numthreads(1, 1, 1)] +void compute_main() +{ + const float4 _e1 = test(tex_plane0_, tex_plane1_, tex_plane2_, tex_params); + return; +} diff --git a/naga/tests/out/hlsl/wgsl-texture-external.ron b/naga/tests/out/hlsl/wgsl-texture-external.ron new file mode 100644 index 0000000000..23afa21e1f --- /dev/null +++ b/naga/tests/out/hlsl/wgsl-texture-external.ron @@ -0,0 +1,20 @@ +( + vertex:[ + ( + entry_point:"vertex_main", + target_profile:"vs_5_1", + ), + ], + fragment:[ + ( + entry_point:"fragment_main", + target_profile:"ps_5_1", + ), + ], + compute:[ + ( + entry_point:"compute_main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/ir/spv-fetch_depth.compact.ron b/naga/tests/out/ir/spv-fetch_depth.compact.ron index 024f918022..c2b7b9b5f6 100644 --- a/naga/tests/out/ir/spv-fetch_depth.compact.ron +++ b/naga/tests/out/ir/spv-fetch_depth.compact.ron @@ -67,6 +67,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [ diff --git a/naga/tests/out/ir/spv-fetch_depth.ron b/naga/tests/out/ir/spv-fetch_depth.ron index 8daa29847f..dd019d62aa 100644 --- a/naga/tests/out/ir/spv-fetch_depth.ron +++ b/naga/tests/out/ir/spv-fetch_depth.ron @@ -130,6 +130,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [ diff --git a/naga/tests/out/ir/spv-shadow.compact.ron b/naga/tests/out/ir/spv-shadow.compact.ron index 7f4e6df5c1..04547e651c 100644 --- a/naga/tests/out/ir/spv-shadow.compact.ron +++ b/naga/tests/out/ir/spv-shadow.compact.ron @@ -155,6 +155,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [ diff --git a/naga/tests/out/ir/spv-shadow.ron b/naga/tests/out/ir/spv-shadow.ron index 35a9e48bef..0babda31f5 100644 --- a/naga/tests/out/ir/spv-shadow.ron +++ b/naga/tests/out/ir/spv-shadow.ron @@ -278,6 +278,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [ diff --git a/naga/tests/out/ir/spv-spec-constants.compact.ron b/naga/tests/out/ir/spv-spec-constants.compact.ron index 698a807258..a07dd0aca0 100644 --- a/naga/tests/out/ir/spv-spec-constants.compact.ron +++ b/naga/tests/out/ir/spv-spec-constants.compact.ron @@ -171,6 +171,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [ diff --git a/naga/tests/out/ir/spv-spec-constants.ron b/naga/tests/out/ir/spv-spec-constants.ron index 5afd71ed65..643d3c7303 100644 --- a/naga/tests/out/ir/spv-spec-constants.ron +++ b/naga/tests/out/ir/spv-spec-constants.ron @@ -262,6 +262,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [ diff --git a/naga/tests/out/ir/wgsl-access.compact.ron b/naga/tests/out/ir/wgsl-access.compact.ron index 5e15ff84ca..fff8d0bcf9 100644 --- a/naga/tests/out/ir/wgsl-access.compact.ron +++ b/naga/tests/out/ir/wgsl-access.compact.ron @@ -421,6 +421,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-access.ron b/naga/tests/out/ir/wgsl-access.ron index 5e15ff84ca..fff8d0bcf9 100644 --- a/naga/tests/out/ir/wgsl-access.ron +++ b/naga/tests/out/ir/wgsl-access.ron @@ -421,6 +421,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-collatz.compact.ron b/naga/tests/out/ir/wgsl-collatz.compact.ron index ce0d6d8c89..30168b2629 100644 --- a/naga/tests/out/ir/wgsl-collatz.compact.ron +++ b/naga/tests/out/ir/wgsl-collatz.compact.ron @@ -44,6 +44,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-collatz.ron b/naga/tests/out/ir/wgsl-collatz.ron index ce0d6d8c89..30168b2629 100644 --- a/naga/tests/out/ir/wgsl-collatz.ron +++ b/naga/tests/out/ir/wgsl-collatz.ron @@ -44,6 +44,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-const_assert.compact.ron b/naga/tests/out/ir/wgsl-const_assert.compact.ron index c10c9f97d3..4d77a57494 100644 --- a/naga/tests/out/ir/wgsl-const_assert.compact.ron +++ b/naga/tests/out/ir/wgsl-const_assert.compact.ron @@ -4,6 +4,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-const_assert.ron b/naga/tests/out/ir/wgsl-const_assert.ron index c10c9f97d3..4d77a57494 100644 --- a/naga/tests/out/ir/wgsl-const_assert.ron +++ b/naga/tests/out/ir/wgsl-const_assert.ron @@ -4,6 +4,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-diagnostic-filter.compact.ron b/naga/tests/out/ir/wgsl-diagnostic-filter.compact.ron index dc4d2defdb..10f533f105 100644 --- a/naga/tests/out/ir/wgsl-diagnostic-filter.compact.ron +++ b/naga/tests/out/ir/wgsl-diagnostic-filter.compact.ron @@ -4,6 +4,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-diagnostic-filter.ron b/naga/tests/out/ir/wgsl-diagnostic-filter.ron index dc4d2defdb..10f533f105 100644 --- a/naga/tests/out/ir/wgsl-diagnostic-filter.ron +++ b/naga/tests/out/ir/wgsl-diagnostic-filter.ron @@ -4,6 +4,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-index-by-value.compact.ron b/naga/tests/out/ir/wgsl-index-by-value.compact.ron index 8f3de4f3e2..31a6010541 100644 --- a/naga/tests/out/ir/wgsl-index-by-value.compact.ron +++ b/naga/tests/out/ir/wgsl-index-by-value.compact.ron @@ -81,6 +81,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-index-by-value.ron b/naga/tests/out/ir/wgsl-index-by-value.ron index 8f3de4f3e2..31a6010541 100644 --- a/naga/tests/out/ir/wgsl-index-by-value.ron +++ b/naga/tests/out/ir/wgsl-index-by-value.ron @@ -81,6 +81,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-local-const.compact.ron b/naga/tests/out/ir/wgsl-local-const.compact.ron index 8bce0bb008..b7deb55aca 100644 --- a/naga/tests/out/ir/wgsl-local-const.compact.ron +++ b/naga/tests/out/ir/wgsl-local-const.compact.ron @@ -26,6 +26,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [ diff --git a/naga/tests/out/ir/wgsl-local-const.ron b/naga/tests/out/ir/wgsl-local-const.ron index 8bce0bb008..b7deb55aca 100644 --- a/naga/tests/out/ir/wgsl-local-const.ron +++ b/naga/tests/out/ir/wgsl-local-const.ron @@ -26,6 +26,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [ diff --git a/naga/tests/out/ir/wgsl-must-use.compact.ron b/naga/tests/out/ir/wgsl-must-use.compact.ron index 0534e73639..4d148b9061 100644 --- a/naga/tests/out/ir/wgsl-must-use.compact.ron +++ b/naga/tests/out/ir/wgsl-must-use.compact.ron @@ -12,6 +12,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-must-use.ron b/naga/tests/out/ir/wgsl-must-use.ron index 0534e73639..4d148b9061 100644 --- a/naga/tests/out/ir/wgsl-must-use.ron +++ b/naga/tests/out/ir/wgsl-must-use.ron @@ -12,6 +12,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-overrides-atomicCompareExchangeWeak.compact.ron b/naga/tests/out/ir/wgsl-overrides-atomicCompareExchangeWeak.compact.ron index ac45d5bad6..1e568ebbc3 100644 --- a/naga/tests/out/ir/wgsl-overrides-atomicCompareExchangeWeak.compact.ron +++ b/naga/tests/out/ir/wgsl-overrides-atomicCompareExchangeWeak.compact.ron @@ -53,6 +53,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: { AtomicCompareExchangeWeakResult(( kind: Uint, diff --git a/naga/tests/out/ir/wgsl-overrides-atomicCompareExchangeWeak.ron b/naga/tests/out/ir/wgsl-overrides-atomicCompareExchangeWeak.ron index ac45d5bad6..1e568ebbc3 100644 --- a/naga/tests/out/ir/wgsl-overrides-atomicCompareExchangeWeak.ron +++ b/naga/tests/out/ir/wgsl-overrides-atomicCompareExchangeWeak.ron @@ -53,6 +53,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: { AtomicCompareExchangeWeakResult(( kind: Uint, diff --git a/naga/tests/out/ir/wgsl-overrides-ray-query.compact.ron b/naga/tests/out/ir/wgsl-overrides-ray-query.compact.ron index 0848ed2b73..649ba28d47 100644 --- a/naga/tests/out/ir/wgsl-overrides-ray-query.compact.ron +++ b/naga/tests/out/ir/wgsl-overrides-ray-query.compact.ron @@ -85,6 +85,7 @@ ray_desc: Some(5), ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-overrides-ray-query.ron b/naga/tests/out/ir/wgsl-overrides-ray-query.ron index 0848ed2b73..649ba28d47 100644 --- a/naga/tests/out/ir/wgsl-overrides-ray-query.ron +++ b/naga/tests/out/ir/wgsl-overrides-ray-query.ron @@ -85,6 +85,7 @@ ray_desc: Some(5), ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-overrides.compact.ron b/naga/tests/out/ir/wgsl-overrides.compact.ron index 4c40ef32de..eca5156107 100644 --- a/naga/tests/out/ir/wgsl-overrides.compact.ron +++ b/naga/tests/out/ir/wgsl-overrides.compact.ron @@ -26,6 +26,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-overrides.ron b/naga/tests/out/ir/wgsl-overrides.ron index 4c40ef32de..eca5156107 100644 --- a/naga/tests/out/ir/wgsl-overrides.ron +++ b/naga/tests/out/ir/wgsl-overrides.ron @@ -26,6 +26,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-storage-textures.compact.ron b/naga/tests/out/ir/wgsl-storage-textures.compact.ron index 4f1325da56..eb70e3badd 100644 --- a/naga/tests/out/ir/wgsl-storage-textures.compact.ron +++ b/naga/tests/out/ir/wgsl-storage-textures.compact.ron @@ -71,6 +71,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-storage-textures.ron b/naga/tests/out/ir/wgsl-storage-textures.ron index 4f1325da56..eb70e3badd 100644 --- a/naga/tests/out/ir/wgsl-storage-textures.ron +++ b/naga/tests/out/ir/wgsl-storage-textures.ron @@ -71,6 +71,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-template-list-trailing-comma.compact.ron b/naga/tests/out/ir/wgsl-template-list-trailing-comma.compact.ron index 67ec4ffb52..bfcd83f7f3 100644 --- a/naga/tests/out/ir/wgsl-template-list-trailing-comma.compact.ron +++ b/naga/tests/out/ir/wgsl-template-list-trailing-comma.compact.ron @@ -28,6 +28,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-template-list-trailing-comma.ron b/naga/tests/out/ir/wgsl-template-list-trailing-comma.ron index 67ec4ffb52..bfcd83f7f3 100644 --- a/naga/tests/out/ir/wgsl-template-list-trailing-comma.ron +++ b/naga/tests/out/ir/wgsl-template-list-trailing-comma.ron @@ -28,6 +28,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [], diff --git a/naga/tests/out/ir/wgsl-texture-external.compact.ron b/naga/tests/out/ir/wgsl-texture-external.compact.ron index 4c0876618d..2bceb18a51 100644 --- a/naga/tests/out/ir/wgsl-texture-external.compact.ron +++ b/naga/tests/out/ir/wgsl-texture-external.compact.ron @@ -2,34 +2,101 @@ types: [ ( name: None, - inner: Image( - dim: D2, - arrayed: false, - class: External, + inner: Scalar(( + kind: Uint, + width: 4, + )), + ), + ( + name: None, + inner: Vector( + size: Bi, + scalar: ( + kind: Uint, + width: 4, + ), ), ), ( name: None, - inner: Sampler( - comparison: false, + inner: Matrix( + columns: Tri, + rows: Bi, + scalar: ( + kind: Float, + width: 4, + ), ), ), ( name: None, - inner: Vector( - size: Quad, + inner: Matrix( + columns: Quad, + rows: Quad, scalar: ( kind: Float, width: 4, ), ), ), + ( + name: Some("NagaExternalTextureParams"), + inner: Struct( + members: [ + ( + name: Some("yuv_conversion_matrix"), + ty: 3, + binding: None, + offset: 0, + ), + ( + name: Some("sample_transform"), + ty: 2, + binding: None, + offset: 64, + ), + ( + name: Some("load_transform"), + ty: 2, + binding: None, + offset: 88, + ), + ( + name: Some("size"), + ty: 1, + binding: None, + offset: 112, + ), + ( + name: Some("num_planes"), + ty: 0, + binding: None, + offset: 120, + ), + ], + span: 128, + ), + ), + ( + name: None, + inner: Image( + dim: D2, + arrayed: false, + class: External, + ), + ), + ( + name: None, + inner: Sampler( + comparison: false, + ), + ), ( name: None, inner: Vector( - size: Bi, + size: Quad, scalar: ( - kind: Uint, + kind: Float, width: 4, ), ), @@ -39,6 +106,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: Some(4), predeclared_types: {}, ), constants: [], @@ -51,7 +119,7 @@ group: 0, binding: 0, )), - ty: 0, + ty: 5, init: None, ), ( @@ -61,7 +129,7 @@ group: 0, binding: 1, )), - ty: 1, + ty: 6, init: None, ), ], @@ -72,28 +140,28 @@ arguments: [ ( name: Some("t"), - ty: 0, + ty: 5, binding: None, ), ], result: Some(( - ty: 2, + ty: 7, binding: None, )), local_variables: [ ( name: Some("a"), - ty: 2, + ty: 7, init: None, ), ( name: Some("b"), - ty: 2, + ty: 7, init: None, ), ( name: Some("c"), - ty: 3, + ty: 1, init: None, ), ], @@ -217,7 +285,7 @@ name: Some("fragment_main"), arguments: [], result: Some(( - ty: 2, + ty: 7, binding: Some(Location( location: 0, interpolation: Some(Perspective), @@ -256,7 +324,7 @@ name: Some("vertex_main"), arguments: [], result: Some(( - ty: 2, + ty: 7, binding: Some(BuiltIn(Position( invariant: false, ))), diff --git a/naga/tests/out/ir/wgsl-texture-external.ron b/naga/tests/out/ir/wgsl-texture-external.ron index 4c0876618d..2bceb18a51 100644 --- a/naga/tests/out/ir/wgsl-texture-external.ron +++ b/naga/tests/out/ir/wgsl-texture-external.ron @@ -2,34 +2,101 @@ types: [ ( name: None, - inner: Image( - dim: D2, - arrayed: false, - class: External, + inner: Scalar(( + kind: Uint, + width: 4, + )), + ), + ( + name: None, + inner: Vector( + size: Bi, + scalar: ( + kind: Uint, + width: 4, + ), ), ), ( name: None, - inner: Sampler( - comparison: false, + inner: Matrix( + columns: Tri, + rows: Bi, + scalar: ( + kind: Float, + width: 4, + ), ), ), ( name: None, - inner: Vector( - size: Quad, + inner: Matrix( + columns: Quad, + rows: Quad, scalar: ( kind: Float, width: 4, ), ), ), + ( + name: Some("NagaExternalTextureParams"), + inner: Struct( + members: [ + ( + name: Some("yuv_conversion_matrix"), + ty: 3, + binding: None, + offset: 0, + ), + ( + name: Some("sample_transform"), + ty: 2, + binding: None, + offset: 64, + ), + ( + name: Some("load_transform"), + ty: 2, + binding: None, + offset: 88, + ), + ( + name: Some("size"), + ty: 1, + binding: None, + offset: 112, + ), + ( + name: Some("num_planes"), + ty: 0, + binding: None, + offset: 120, + ), + ], + span: 128, + ), + ), + ( + name: None, + inner: Image( + dim: D2, + arrayed: false, + class: External, + ), + ), + ( + name: None, + inner: Sampler( + comparison: false, + ), + ), ( name: None, inner: Vector( - size: Bi, + size: Quad, scalar: ( - kind: Uint, + kind: Float, width: 4, ), ), @@ -39,6 +106,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: Some(4), predeclared_types: {}, ), constants: [], @@ -51,7 +119,7 @@ group: 0, binding: 0, )), - ty: 0, + ty: 5, init: None, ), ( @@ -61,7 +129,7 @@ group: 0, binding: 1, )), - ty: 1, + ty: 6, init: None, ), ], @@ -72,28 +140,28 @@ arguments: [ ( name: Some("t"), - ty: 0, + ty: 5, binding: None, ), ], result: Some(( - ty: 2, + ty: 7, binding: None, )), local_variables: [ ( name: Some("a"), - ty: 2, + ty: 7, init: None, ), ( name: Some("b"), - ty: 2, + ty: 7, init: None, ), ( name: Some("c"), - ty: 3, + ty: 1, init: None, ), ], @@ -217,7 +285,7 @@ name: Some("fragment_main"), arguments: [], result: Some(( - ty: 2, + ty: 7, binding: Some(Location( location: 0, interpolation: Some(Perspective), @@ -256,7 +324,7 @@ name: Some("vertex_main"), arguments: [], result: Some(( - ty: 2, + ty: 7, binding: Some(BuiltIn(Position( invariant: false, ))), diff --git a/naga/tests/out/ir/wgsl-types_with_comments.compact.ron b/naga/tests/out/ir/wgsl-types_with_comments.compact.ron index dde1bd367b..1139fab197 100644 --- a/naga/tests/out/ir/wgsl-types_with_comments.compact.ron +++ b/naga/tests/out/ir/wgsl-types_with_comments.compact.ron @@ -37,6 +37,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [ diff --git a/naga/tests/out/ir/wgsl-types_with_comments.ron b/naga/tests/out/ir/wgsl-types_with_comments.ron index 3a23c49c4f..a1761c17c8 100644 --- a/naga/tests/out/ir/wgsl-types_with_comments.ron +++ b/naga/tests/out/ir/wgsl-types_with_comments.ron @@ -62,6 +62,7 @@ ray_desc: None, ray_intersection: None, ray_vertex_return: None, + external_texture_params: None, predeclared_types: {}, ), constants: [ diff --git a/wgpu-hal/src/dx12/device.rs b/wgpu-hal/src/dx12/device.rs index 7d245e8ce3..fb0a39528b 100644 --- a/wgpu-hal/src/dx12/device.rs +++ b/wgpu-hal/src/dx12/device.rs @@ -1388,6 +1388,7 @@ impl crate::Device for super::Device { restrict_indexing: true, sampler_heap_target, sampler_buffer_binding_map, + external_texture_binding_map: hlsl::ExternalTextureBindingMap::default(), force_loop_bounding: true, }, })