#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct WrappedUnaryOp { pub(super) op: crate::UnaryOperator, // This can only represent scalar or vector types. If we ever need to wrap // unary ops with other types, we'll need a better representation. pub(super) ty: (Option<crate::VectorSize>, crate::Scalar),
}
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct WrappedBinaryOp { pub(super) op: crate::BinaryOperator, // This can only represent scalar or vector types. If we ever need to wrap // binary ops with other types, we'll need a better representation. pub(super) left_ty: (Option<crate::VectorSize>, crate::Scalar), pub(super) right_ty: (Option<crate::VectorSize>, crate::Scalar),
}
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct WrappedCast { // This can only represent scalar or vector types. If we ever need to wrap // casts with other types, we'll need a better representation. pub(super) vector_size: Option<crate::VectorSize>, pub(super) src_scalar: crate::Scalar, pub(super) dst_scalar: crate::Scalar,
}
/// HLSL backend requires its own `ImageQuery` enum. /// /// It is used inside `WrappedImageQuery` and should be unique per ImageQuery function. /// IR version can't be unique per function, because it's store mipmap level as an expression. /// /// For example: /// ```wgsl /// let dim_cube_array_lod = textureDimensions(image_cube_array, 1); /// let dim_cube_array_lod2 = textureDimensions(image_cube_array, 1); /// ``` /// /// ```ir /// ImageQuery { /// image: [1], /// query: Size { /// level: Some( /// [1], /// ), /// }, /// }, /// ImageQuery { /// image: [1], /// query: Size { /// level: Some( /// [2], /// ), /// }, /// }, /// ``` /// /// HLSL should generate only 1 function for this case. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) enum ImageQuery {
Size,
SizeLevel,
NumLevels,
NumLayers,
NumSamples,
}
// Write return value
writeln!(self.out, "{INDENT}return {RETURN_VARIABLE_NAME};")?;
// End of function body
writeln!(self.out, "}}")?; // Write extra new line
writeln!(self.out)?;
Ok(())
}
/// Helper function used by [`Self::write_wrapped_image_load_function`] and /// [`Self::write_wrapped_image_sample_function`] to write the shared YUV /// to RGB conversion code for external textures. Expects the preceding /// code to declare the Y component as a `float` variable of name `y`, the /// UV components as a `float2` variable of name `uv`, and the external /// texture params as a variable of name `params`. The emitted code will /// return the result. fn write_convert_yuv_to_rgb_and_return(
&mutself,
level: crate::back::Level,
y: &str,
uv: &str,
params: &str,
) -> BackendResult { let l1 = level; let l2 = l1.next();
// Convert from YUV to non-linear RGB in the source color space. We // declare our matrices as row_major in HLSL, therefore we must reverse // the order of this multiplication
writeln!( self.out, "{l1}float3 srcGammaRgb = mul(float4({y}, {uv}, 1.0), {params}.yuv_conversion_matrix).rgb;"
)?;
// Apply the inverse of the source transfer function to convert to // linear RGB in the source color space.
writeln!( self.out, "{l1}float3 srcLinearRgb = srcGammaRgb < {params}.src_tf.k * {params}.src_tf.b ?"
)?;
writeln!(self.out, "{l2}srcGammaRgb / {params}.src_tf.k :")?;
writeln!(self.out, "{l2}pow((srcGammaRgb + {params}.src_tf.a - 1.0) / {params}.src_tf.a, {params}.src_tf.g);")?;
// Multiply by the gamut conversion matrix to convert to linear RGB in // the destination color space. We declare our matrices as row_major in // HLSL, therefore we must reverse the order of this multiplication.
writeln!( self.out, "{l1}float3 dstLinearRgb = mul(srcLinearRgb, {params}.gamut_conversion_matrix);"
)?;
// Finally, apply the dest transfer function to convert to non-linear // RGB in the destination color space, and return the result.
writeln!( self.out, "{l1}float3 dstGammaRgb = dstLinearRgb < {params}.dst_tf.b ?"
)?;
writeln!(self.out, "{l2}{params}.dst_tf.k * dstLinearRgb :")?;
writeln!(self.out, "{l2}{params}.dst_tf.a * pow(dstLinearRgb, 1.0 / {params}.dst_tf.g) - ({params}.dst_tf.a - 1);")?;
pub(super) fn write_wrapped_image_load_function(
&mutself,
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 = any(params.size) ? 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;"
)?;
pub(super) fn write_wrapped_image_sample_function(
&mutself,
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. The purported size of the texture // (params.size) is irrelevant here as we are dealing with normalized // coordinates. Usually we would clamp to (0,0)..(1,1). However, we must // apply the sample transformation to that, also bearing in mind that it // may contain a flip on either axis. We calculate and adjust for the // half-texel separately for each plane as it depends on the actual // 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 {{")?;
/// Helper function that write wrapped function for `Expression::ImageQuery` /// /// <https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-to-getdimensions> pub(super) fn write_wrapped_image_query_function(
&mutself,
module: &crate::Module,
wiq: WrappedImageQuery,
expr_handle: Handle<crate::Expression>,
func_ctx: &FunctionCtx,
) -> BackendResult { usecrate::{
back::{COMPONENTS, INDENT},
ImageDimension as IDim,
};
match wiq.class { crate::ImageClass::External => { if wiq.query != ImageQuery::Size { return Err(super::Error::Custom( "External images only support `Size` queries".into(),
));
}
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 iflet ImageQuery::SizeLevel = wiq.query {
write!(self.out, ", uint {MIP_LEVEL_PARAM}")?;
}
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 => unreachable!(),
};
// GetDimensions Overloaded Methods // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-to-getdimensions#overloaded-methods // We rely on the validator to reject invalid queries. let (ret_swizzle, number_of_out_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 => { // We want `NumberOfLevels` or `Samples` match wiq.dim {
IDim::D1 => ("y", 2),
IDim::D3 => ("w", 4),
IDim::D2 | IDim::Cube => { if wiq.arrayed {
("w", 4)
} else {
("z", 3)
}
}
}
} // We want `Elements`
ImageQuery::NumLayers => ("z", 3 + extra_coords),
};
for component in COMPONENTS[..number_of_out_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_out_params - 1]
)?;
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(())
}
/// Helper function that write wrapped function for `Expression::Compose` for structures. fn write_wrapped_constructor_function(
&mutself,
module: &crate::Module,
constructor: WrappedConstructor,
) -> BackendResult { usecrate::back::INDENT;
match module.types[constructor.ty].inner { crate::TypeInner::Struct { ref members, .. } => { for (i, member) in members.iter().enumerate() {
write_arg(i, member.ty)?;
}
} crate::TypeInner::Array {
base,
size: crate::ArraySize::Constant(size),
..
} => { for i in0..size.get() as usize {
write_arg(i, base)?;
}
}
_ => unreachable!(),
};
write!(self.out, ")")?;
// Write function body
writeln!(self.out, " {{")?;
match module.types[constructor.ty].inner { crate::TypeInner::Struct { ref members, .. } => { let struct_name = &self.names[&NameKey::Type(constructor.ty)];
writeln!( self.out, "{INDENT}{struct_name} {RETURN_VARIABLE_NAME} = ({struct_name})0;"
)?; for (i, member) in members.iter().enumerate() { let field_name = &self.names[&NameKey::StructMember(constructor.ty, i as u32)];
match module.types[member.ty].inner { crate::TypeInner::Matrix {
columns,
rows: crate::VectorSize::Bi,
..
} if member.binding.is_none() => { for j in0..columns as u8 {
writeln!( self.out, "{INDENT}{RETURN_VARIABLE_NAME}.{field_name}_{j} = {ARGUMENT_VARIABLE_NAME}{i}[{j}];"
)?;
}
} ref other => { // We cast arrays of native HLSL `floatCx2`s to arrays of `matCx2`s // (where the inner matrix is represented by a struct with C `float2` members). // See the module-level block comment in mod.rs for details. iflet Some(super::writer::MatrixType {
columns,
rows: crate::VectorSize::Bi,
width,
}) = super::writer::get_inner_matrix_data(module, member.ty)
{
write!( self.out, "{}{}.{} = (__mat{}x2_f{}",
INDENT,
RETURN_VARIABLE_NAME,
field_name,
columns as u8,
width * 8
)?; ifletcrate::TypeInner::Array { base, size, .. } = *other { self.write_array_size(module, base, size)?;
}
writeln!(self.out, "){ARGUMENT_VARIABLE_NAME}{i};",)?;
} else {
writeln!( self.out, "{INDENT}{RETURN_VARIABLE_NAME}.{field_name} = {ARGUMENT_VARIABLE_NAME}{i};",
)?;
}
}
}
}
} crate::TypeInner::Array {
base,
size: crate::ArraySize::Constant(size),
..
} => {
write!(self.out, "{INDENT}")?; self.write_type(module, base)?;
write!(self.out, " {RETURN_VARIABLE_NAME}")?; self.write_array_size(module, base, crate::ArraySize::Constant(size))?;
write!(self.out, " = {{ ")?; for i in0..size.get() { if i != 0 {
write!(self.out, ", ")?;
}
write!(self.out, "{ARGUMENT_VARIABLE_NAME}{i}")?;
}
writeln!(self.out, " }};",)?;
}
_ => unreachable!(),
}
// Write return value
writeln!(self.out, "{INDENT}return {RETURN_VARIABLE_NAME};")?;
// End of function body
writeln!(self.out, "}}")?; // Write extra new line
writeln!(self.out)?;
Ok(())
}
/// Writes the conversion from a single length storage texture load to a vec4 with the loaded /// scalar in its `x` component, 1 in its `a` component and 0 everywhere else. fn write_loaded_scalar_to_storage_loaded_value(
&mutself,
scalar_type: crate::Scalar,
) -> BackendResult { const ARGUMENT_VARIABLE_NAME: &str = "arg"; const RETURN_VARIABLE_NAME: &str = "ret";
let zero; let one; match scalar_type.kind {
ScalarKind::Sint => {
assert_eq!(
scalar_type.width, 4, "Scalar {scalar_type:?} is not a result from any storage format"
);
zero = "0";
one = "1";
}
ScalarKind::Uint => match scalar_type.width { 4 => {
zero = "0u";
one = "1u";
} 8 => {
zero = "0uL";
one = "1uL"
}
_ => unreachable!("Scalar {scalar_type:?} is not a result from any storage format"),
},
ScalarKind::Float => {
assert_eq!(
scalar_type.width, 4, "Scalar {scalar_type:?} is not a result from any storage format"
);
zero = "0.0";
one = "1.0";
}
_ => unreachable!("Scalar {scalar_type:?} is not a result from any storage format"),
}
pub(super) fn write_wrapped_struct_matrix_get_function_name(
&mutself,
access: WrappedStructMatrixAccess,
) -> BackendResult { let name = &self.names[&NameKey::Type(access.ty)]; let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)];
write!(self.out, "GetMat{field_name}On{name}")?;
Ok(())
}
/// Writes a function used to get a matCx2 from within a structure. pub(super) fn write_wrapped_struct_matrix_get_function(
&mutself,
module: &crate::Module,
access: WrappedStructMatrixAccess,
) -> BackendResult { usecrate::back::INDENT;
// Write function return type and name let member = match module.types[access.ty].inner { crate::TypeInner::Struct { ref members, .. } => &members[access.index as usize],
_ => unreachable!(),
}; let ret_ty = &module.types[member.ty].inner; self.write_value_type(module, ret_ty)?;
write!(self.out, " ")?; self.write_wrapped_struct_matrix_get_function_name(access)?;
// Write function parameters
write!(self.out, "(")?; let struct_name = &self.names[&NameKey::Type(access.ty)];
write!(self.out, "{struct_name} {STRUCT_ARGUMENT_VARIABLE_NAME}")?;
// Write function body
writeln!(self.out, ") {{")?;
// Write return value
write!(self.out, "{INDENT}return ")?; self.write_value_type(module, ret_ty)?;
write!(self.out, "(")?; let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; match module.types[member.ty].inner { crate::TypeInner::Matrix { columns, .. } => { for i in0..columns as u8 { if i != 0 {
write!(self.out, ", ")?;
}
write!(self.out, "{STRUCT_ARGUMENT_VARIABLE_NAME}.{field_name}_{i}")?;
}
}
_ => unreachable!(),
}
writeln!(self.out, ");")?;
// End of function body
writeln!(self.out, "}}")?; // Write extra new line
writeln!(self.out)?;
Ok(())
}
pub(super) fn write_wrapped_struct_matrix_set_function_name(
&mutself,
access: WrappedStructMatrixAccess,
) -> BackendResult { let name = &self.names[&NameKey::Type(access.ty)]; let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)];
write!(self.out, "SetMat{field_name}On{name}")?;
Ok(())
}
/// Writes a function used to set a matCx2 from within a structure. pub(super) fn write_wrapped_struct_matrix_set_function(
&mutself,
module: &crate::Module,
access: WrappedStructMatrixAccess,
) -> BackendResult { usecrate::back::INDENT;
// Write function return type and name
write!(self.out, "void ")?; self.write_wrapped_struct_matrix_set_function_name(access)?;
// Write function parameters
write!(self.out, "(")?; let struct_name = &self.names[&NameKey::Type(access.ty)];
write!(self.out, "{struct_name} {STRUCT_ARGUMENT_VARIABLE_NAME}, ")?; let member = match module.types[access.ty].inner { crate::TypeInner::Struct { ref members, .. } => &members[access.index as usize],
_ => unreachable!(),
}; self.write_type(module, member.ty)?;
write!(self.out, " {MATRIX_ARGUMENT_VARIABLE_NAME}")?; // Write function body
writeln!(self.out, ") {{")?;
let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)];
match module.types[member.ty].inner { crate::TypeInner::Matrix { columns, .. } => { for i in0..columns as u8 {
writeln!( self.out, "{INDENT}{STRUCT_ARGUMENT_VARIABLE_NAME}.{field_name}_{i} = {MATRIX_ARGUMENT_VARIABLE_NAME}[{i}];"
)?;
}
}
_ => unreachable!(),
}
// End of function body
writeln!(self.out, "}}")?; // Write extra new line
writeln!(self.out)?;
Ok(())
}
pub(super) fn write_wrapped_struct_matrix_set_vec_function_name(
&mutself,
access: WrappedStructMatrixAccess,
) -> BackendResult { let name = &self.names[&NameKey::Type(access.ty)]; let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)];
write!(self.out, "SetMatVec{field_name}On{name}")?;
Ok(())
}
/// Writes a function used to set a vec2 on a matCx2 from within a structure. pub(super) fn write_wrapped_struct_matrix_set_vec_function(
&mutself,
module: &crate::Module,
access: WrappedStructMatrixAccess,
) -> BackendResult { usecrate::back::INDENT;
let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)];
match module.types[member.ty].inner { crate::TypeInner::Matrix { columns, .. } => { for i in0..columns as u8 {
writeln!( self.out, "{INDENT}case {i}: {{ {STRUCT_ARGUMENT_VARIABLE_NAME}.{field_name}_{i} = {VECTOR_ARGUMENT_VARIABLE_NAME}; break; }}"
)?;
}
}
_ => unreachable!(),
}
writeln!(self.out, "{INDENT}}}")?;
// End of function body
writeln!(self.out, "}}")?; // Write extra new line
writeln!(self.out)?;
Ok(())
}
pub(super) fn write_wrapped_struct_matrix_set_scalar_function_name(
&mutself,
access: WrappedStructMatrixAccess,
) -> BackendResult { let name = &self.names[&NameKey::Type(access.ty)]; let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)];
write!(self.out, "SetMatScalar{field_name}On{name}")?;
Ok(())
}
/// Writes a function used to set a float on a matCx2 from within a structure. pub(super) fn write_wrapped_struct_matrix_set_scalar_function(
&mutself,
module: &crate::Module,
access: WrappedStructMatrixAccess,
) -> BackendResult { usecrate::back::INDENT;
/// Helper function that writes wrapped functions for expressions in a function pub(super) fn write_wrapped_expression_functions(
&mutself,
module: &crate::Module,
expressions: &crate::Arena<crate::Expression>,
context: Option<&FunctionCtx>,
) -> BackendResult { for (handle, _) in expressions.iter() { match expressions[handle] { crate::Expression::Compose { ty, .. } => { match module.types[ty].inner { crate::TypeInner::Struct { .. } | crate::TypeInner::Array { .. } => { let constructor = WrappedConstructor { ty }; ifself.wrapped.insert(WrappedType::Constructor(constructor)) { self.write_wrapped_constructor_function(module, constructor)?;
}
}
_ => {}
};
} crate::Expression::ImageLoad { image, .. } => { // This can only happen in a function as this is not a valid const expression match *context.as_ref().unwrap().resolve_type(image, &module.types) { crate::TypeInner::Image {
class: crate::ImageClass::Storage { format, .. },
..
} => { if format.single_component() { let scalar: crate::Scalar = format.into(); ifself.wrapped.insert(WrappedType::ImageLoadScalar(scalar)) { self.write_loaded_scalar_to_storage_loaded_value(scalar)?;
}
}
}
_ => {}
}
} crate::Expression::RayQueryGetIntersection { committed, .. } => { if committed { if !self.written_committed_intersection { self.write_committed_intersection_function(module)?; self.written_committed_intersection = true;
}
} elseif !self.written_candidate_intersection { self.write_candidate_intersection_function(module)?; self.written_candidate_intersection = true;
}
}
_ => {}
}
}
Ok(())
}
// TODO: we could merge this with iteration in write_wrapped_expression_functions... // /// Helper function that writes zero value wrapped functions pub(super) fn write_wrapped_zero_value_functions(
&mutself,
module: &crate::Module,
expressions: &crate::Arena<crate::Expression>,
) -> BackendResult { for (handle, _) in expressions.iter() { ifletcrate::Expression::ZeroValue(ty) = expressions[handle] { let zero_value = WrappedZeroValue { ty }; ifself.wrapped.insert(WrappedType::ZeroValue(zero_value)) { self.write_wrapped_zero_value_function(module, zero_value)?;
}
}
}
Ok(())
}
match fun { crate::MathFunction::ExtractBits => { // The behavior of our extractBits polyfill is undefined if offset + count > bit_width. We need // to first sanitize the offset and count first. If we don't do this, we will get out-of-spec // values if the extracted range is not within the bit width. // // This encodes the exact formula specified by the wgsl spec: // https://gpuweb.github.io/gpuweb/wgsl/#extractBits-unsigned-builtin // // w = sizeof(x) * 8 // o = min(offset, w) // c = min(count, w - o) // // bitfieldExtract(x, o, c) let scalar = arg_ty.scalar().unwrap(); let components = arg_ty.components();
let wrapped = WrappedMath {
fun,
scalar,
components,
};
if !self.wrapped.insert(WrappedType::Math(wrapped)) { continue;
}
// Write return type self.write_value_type(module, arg_ty)?;
let scalar_width: u8 = scalar.width * 8;
// Write function name and parameters
writeln!(self.out, " {EXTRACT_BITS_FUNCTION}(")?;
write!(self.out, " ")?; self.write_value_type(module, arg_ty)?;
writeln!(self.out, " e,")?;
writeln!(self.out, " uint offset,")?;
writeln!(self.out, " uint count")?;
writeln!(self.out, ") {{")?;
// Write function body
writeln!(self.out, " uint w = {scalar_width};")?;
writeln!(self.out, " uint o = min(offset, w);")?;
writeln!(self.out, " uint c = min(count, w - o);")?;
writeln!( self.out, " return (c == 0 ? 0 : (e << (w - c - o)) >> (w - c));"
)?;
// End of function body
writeln!(self.out, "}}")?;
} crate::MathFunction::InsertBits => { // The behavior of our insertBits polyfill has the same constraints as the extractBits polyfill.
let scalar = arg_ty.scalar().unwrap(); let components = arg_ty.components();
let wrapped = WrappedMath {
fun,
scalar,
components,
};
if !self.wrapped.insert(WrappedType::Math(wrapped)) { continue;
}
// Write return type self.write_value_type(module, arg_ty)?;
let scalar_width: u8 = scalar.width * 8; let scalar_max: u64 = match scalar.width { 1 => 0xFF, 2 => 0xFFFF, 4 => 0xFFFFFFFF, 8 => 0xFFFFFFFFFFFFFFFF,
_ => unreachable!(),
};
// Write function body
writeln!(self.out, " uint w = {scalar_width}u;")?;
writeln!(self.out, " uint o = min(offset, w);")?;
writeln!(self.out, " uint c = min(count, w - o);")?;
// The `u` suffix on the literals is _extremely_ important. Otherwise it will use // i32 shifting instead of the intended u32 shifting.
writeln!( self.out, " uint mask = (({scalar_max}u >> ({scalar_width}u - c)) << o);"
)?;
writeln!( self.out, " return (c == 0 ? e : ((e & ~mask) | ((newbits << o) & mask)));"
)?;
// End of function body
writeln!(self.out, "}}")?;
} // Taking the absolute value of the minimum value of a two's // complement signed integer type causes overflow, which is // undefined behaviour in HLSL. To avoid this, when the value is // negative we bitcast the value to unsigned and negate it, then // bitcast back to signed. // This adheres to the WGSL spec in that the absolute of the type's // minimum value should equal to the minimum value. // // TODO(#7109): asint()/asuint() only support 32-bit integers, so we // must find another solution for different bit-widths. crate::MathFunction::Abs if matches!(arg_ty.scalar(), Some(crate::Scalar::I32)) =>
{ let scalar = arg_ty.scalar().unwrap(); let components = arg_ty.components();
let wrapped = WrappedMath {
fun,
scalar,
components,
};
if !self.wrapped.insert(WrappedType::Math(wrapped)) { continue;
}
let level = crate::back::Level(1);
writeln!( self.out, "{level}return val >= 0 ? val : asint(-asuint(val));"
)?;
writeln!(self.out, "}}")?;
writeln!(self.out)?;
}
_ => {}
}
}
}
Ok(())
}
pub(super) fn write_wrapped_unary_ops(
&mutself,
module: &crate::Module,
func_ctx: &FunctionCtx,
) -> BackendResult { for (_, expression) in func_ctx.expressions.iter() { ifletcrate::Expression::Unary { op, expr } = *expression { let expr_ty = func_ctx.resolve_type(expr, &module.types); let Some((vector_size, scalar)) = expr_ty.vector_size_and_scalar() else { continue;
}; let wrapped = WrappedUnaryOp {
op,
ty: (vector_size, scalar),
};
// Negating the minimum value of a two's complement signed integer type // causes overflow, which is undefined behaviour in HLSL. To avoid this // we bitcast the value to unsigned and negate it, then bitcast back to // signed. This adheres to the WGSL spec in that the negative of the // type's minimum value should equal to the minimum value. // // TODO(#7109): asint()/asuint() only support 32-bit integers, so we must // find another solution for different bit-widths. match (op, scalar) {
(crate::UnaryOperator::Negate, crate::Scalar::I32) => { if !self.wrapped.insert(WrappedType::UnaryOp(wrapped)) { continue;
}
pub(super) fn write_wrapped_binary_ops(
&mutself,
module: &crate::Module,
func_ctx: &FunctionCtx,
) -> BackendResult { for (expr_handle, expression) in func_ctx.expressions.iter() { ifletcrate::Expression::Binary { op, left, right } = *expression { let expr_ty = func_ctx.resolve_type(expr_handle, &module.types); let left_ty = func_ctx.resolve_type(left, &module.types); let right_ty = func_ctx.resolve_type(right, &module.types);
match (op, expr_ty.scalar()) { // Signed integer division of the type's minimum representable value // divided by -1, or signed or unsigned division by zero, is // undefined behaviour in HLSL. We override the divisor to 1 in these // cases. // This adheres to the WGSL spec in that: // * TYPE_MIN / -1 == TYPE_MIN // * x / 0 == x
( crate::BinaryOperator::Divide,
Some(
scalar @ crate::Scalar {
kind: ScalarKind::Sint | ScalarKind::Uint,
..
},
),
) => { let Some(left_wrapped_ty) = left_ty.vector_size_and_scalar() else { continue;
}; let Some(right_wrapped_ty) = right_ty.vector_size_and_scalar() else { continue;
}; let wrapped = WrappedBinaryOp {
op,
left_ty: left_wrapped_ty,
right_ty: right_wrapped_ty,
}; if !self.wrapped.insert(WrappedType::BinaryOp(wrapped)) { continue;
}
self.write_value_type(module, expr_ty)?;
write!(self.out, " {DIV_FUNCTION}(")?; self.write_value_type(module, left_ty)?;
write!(self.out, " lhs, ")?; self.write_value_type(module, right_ty)?;
writeln!(self.out, " rhs) {{")?; let level = crate::back::Level(1); match scalar.kind {
ScalarKind::Sint => { let min_val = match scalar.width { 2 => crate::Literal::I16(i16::MIN), 4 => crate::Literal::I32(i32::MIN), 8 => crate::Literal::I64(i64::MIN),
_ => { return Err(super::Error::UnsupportedScalar(scalar));
}
};
write!(self.out, "{level}return lhs / (((lhs == ")?; self.write_literal(min_val)?;
writeln!(self.out, " & rhs == -1) | (rhs == 0)) ? 1 : rhs);")?
}
ScalarKind::Uint => {
writeln!(self.out, "{level}return lhs / (rhs == 0u ? 1u : rhs);")?
}
_ => unreachable!(),
}
writeln!(self.out, "}}")?;
writeln!(self.out)?;
} // The modulus operator is only defined for integers in HLSL when // either both sides are positive or both sides are negative. To // avoid this undefined behaviour we use the following equation: // // dividend - (dividend / divisor) * divisor // // overriding the divisor to 1 if either it is 0, or it is -1 // and the dividend is the minimum representable value. // // This adheres to the WGSL spec in that: // * min_value % -1 == 0 // * x % 0 == 0
( crate::BinaryOperator::Modulo,
Some(
scalar @ crate::Scalar {
kind: ScalarKind::Sint | ScalarKind::Uint | ScalarKind::Float,
..
},
),
) => { let Some(left_wrapped_ty) = left_ty.vector_size_and_scalar() else { continue;
}; let Some(right_wrapped_ty) = right_ty.vector_size_and_scalar() else { continue;
}; let wrapped = WrappedBinaryOp {
op,
left_ty: left_wrapped_ty,
right_ty: right_wrapped_ty,
}; if !self.wrapped.insert(WrappedType::BinaryOp(wrapped)) { continue;
}
self.write_value_type(module, expr_ty)?;
write!(self.out, " {MOD_FUNCTION}(")?; self.write_value_type(module, left_ty)?;
write!(self.out, " lhs, ")?; self.write_value_type(module, right_ty)?;
writeln!(self.out, " rhs) {{")?; let level = crate::back::Level(1); match scalar.kind {
ScalarKind::Sint => { let min_val = match scalar.width { 2 => crate::Literal::I16(i16::MIN), 4 => crate::Literal::I32(i32::MIN), 8 => crate::Literal::I64(i64::MIN),
_ => { return Err(super::Error::UnsupportedScalar(scalar));
}
};
write!(self.out, "{level}")?; self.write_value_type(module, right_ty)?;
write!(self.out, " divisor = ((lhs == ")?; self.write_literal(min_val)?;
writeln!(self.out, " & rhs == -1) | (rhs == 0)) ? 1 : rhs;")?;
writeln!( self.out, "{level}return lhs - (lhs / divisor) * divisor;"
)?
}
ScalarKind::Uint => {
writeln!(self.out, "{level}return lhs % (rhs == 0u ? 1u : rhs);")?
} // HLSL's fmod has the same definition as WGSL's % operator but due // to its implementation in DXC it is not as accurate as the WGSL spec // requires it to be. See: // - https://shader-playground.timjones.io/0c8572816dbb6fc4435cc5d016a978a7 // - https://github.com/llvm/llvm-project/blob/50f9b8acafdca48e87e6b8e393c1f116a2d193ee/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h#L78-L81
ScalarKind::Float => {
writeln!(self.out, "{level}return lhs - rhs * trunc(lhs / rhs);")?
}
_ => unreachable!(),
}
writeln!(self.out, "}}")?;
writeln!(self.out)?;
}
_ => {}
}
}
}
Ok(())
}
fn write_wrapped_cast_functions(
&mutself,
module: &crate::Module,
func_ctx: &FunctionCtx,
) -> BackendResult { for (_, expression) in func_ctx.expressions.iter() { ifletcrate::Expression::As {
expr,
kind,
convert: Some(width),
} = *expression
{ // Avoid undefined behaviour when casting from a float to integer // when the value is out of range for the target type. Additionally // ensure we clamp to the correct value as per the WGSL spec. // // https://www.w3.org/TR/WGSL/#floating-point-conversion: // * If X is exactly representable in the target type T, then the // result is that value. // * Otherwise, the result is the value in T closest to // truncate(X) and also exactly representable in the original // floating point type. let src_ty = func_ctx.resolve_type(expr, &module.types); let Some((vector_size, src_scalar)) = src_ty.vector_size_and_scalar() else { continue;
}; let dst_scalar = crate::Scalar { kind, width }; if src_scalar.kind != ScalarKind::Float
|| (dst_scalar.kind != ScalarKind::Sint && dst_scalar.kind != ScalarKind::Uint)
{ continue;
}
let wrapped = WrappedCast {
src_scalar,
vector_size,
dst_scalar,
}; if !self.wrapped.insert(WrappedType::Cast(wrapped)) { continue;
}
for (handle, _) in func_ctx.expressions.iter() { match func_ctx.expressions[handle] { crate::Expression::ArrayLength(expr) => { let global_expr = match func_ctx.expressions[expr] { crate::Expression::GlobalVariable(_) => expr, crate::Expression::AccessIndex { base, index: _ } => base, ref other => unreachable!("Array length of {:?}", other),
}; let global_var = match func_ctx.expressions[global_expr] { crate::Expression::GlobalVariable(var_handle) => {
&module.global_variables[var_handle]
} ref other => { return Err(super::Error::Unimplemented(format!( "Array length of base {other:?}"
)))
}
}; let storage_access = match global_var.space { crate::AddressSpace::Storage { access } => access,
_ => crate::StorageAccess::default(),
}; let wal = WrappedArrayLength {
writable: storage_access.contains(crate::StorageAccess::STORE),
};
ifself.wrapped.insert(WrappedType::ArrayLength(wal)) { self.write_wrapped_array_length_function(wal)?;
}
} crate::Expression::ImageLoad { image, .. } => { let class = match *func_ctx.resolve_type(image, &module.types) { crate::TypeInner::Image { class, .. } => class,
_ => unreachable!(),
}; let wrapped = WrappedImageLoad { class }; ifself.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,
}; ifself.wrapped.insert(WrappedType::ImageSample(wrapped)) { self.write_wrapped_image_sample_function(module, wrapped)?;
}
} crate::Expression::ImageQuery { image, query } => { let wiq = match *func_ctx.resolve_type(image, &module.types) { crate::TypeInner::Image {
dim,
arrayed,
class,
} => WrappedImageQuery {
dim,
arrayed,
class,
query: query.into(),
},
_ => unreachable!("we only query images"),
};
ifself.wrapped.insert(WrappedType::ImageQuery(wiq)) { self.write_wrapped_image_query_function(module, wiq, handle, func_ctx)?;
}
} // Write `WrappedConstructor` for structs that are loaded from `AddressSpace::Storage` // since they will later be used by the fn `write_storage_load` crate::Expression::Load { pointer } => { let pointer_space = func_ctx
.resolve_type(pointer, &module.types)
.pointer_space();
fn write_wrapped_constructor<W: Write>(
writer: &mutsuper::Writer<'_, W>,
ty: Handle<crate::Type>,
module: &crate::Module,
) -> BackendResult { match module.types[ty].inner { crate::TypeInner::Struct { ref members, .. } => { for member in members {
write_wrapped_constructor(writer, member.ty, module)?;
}
let constructor = WrappedConstructor { ty }; if writer.wrapped.insert(WrappedType::Constructor(constructor)) {
writer
.write_wrapped_constructor_function(module, constructor)?;
}
} crate::TypeInner::Array { base, .. } => {
write_wrapped_constructor(writer, base, module)?;
let constructor = WrappedConstructor { ty }; if writer.wrapped.insert(WrappedType::Constructor(constructor)) {
writer
.write_wrapped_constructor_function(module, constructor)?;
}
}
_ => {}
};
Ok(())
}
} // We treat matrices of the form `matCx2` as a sequence of C `vec2`s // (see top level module docs for details). // // The functions injected here are required to get the matrix accesses working. crate::Expression::AccessIndex { base, index } => { let base_ty_res = &func_ctx.info[base].ty; letmut resolved = base_ty_res.inner_with(&module.types); let base_ty_handle = match *resolved { crate::TypeInner::Pointer { base, .. } => {
resolved = &module.types[base].inner;
Some(base)
}
_ => base_ty_res.handle(),
}; ifletcrate::TypeInner::Struct { ref members, .. } = *resolved { let member = &members[index as usize];
match module.types[member.ty].inner { crate::TypeInner::Matrix {
rows: crate::VectorSize::Bi,
..
} if member.binding.is_none() => { let ty = base_ty_handle.unwrap(); let access = WrappedStructMatrixAccess { ty, index };
/// Writes out the sampler heap declarations if they haven't been written yet. pub(super) fn write_sampler_heaps(&mut self) -> BackendResult { if self.wrapped.sampler_heaps { return Ok(());
}
/// Writes out the sampler index buffer declaration if it hasn't been written yet.
pub(super) fn write_wrapped_sampler_buffer(
&mut self,
key: super::SamplerIndexBufferKey,
) -> BackendResult { // The astute will notice that we do a double hash lookup, but we do this to avoid // holding a mutable reference to `self` while trying to call `write_sampler_heaps`. // // We only pay this double lookup cost when we actually need to write out the sampler // buffer, which should be not be common.
if self.wrapped.sampler_index_buffers.contains_key(&key) { return Ok(());
};
self.write_sampler_heaps()?;
// Because the group number can be arbitrary, we use the namer to generate a unique name // instead of adding it to the reserved name list.
let sampler_array_name = self
.namer
.call(&format!("nagaGroup{}SamplerIndexArray", key.group));
let bind_target = match self.options.sampler_buffer_binding_map.get(&key) {
Some(&bind_target) => bind_target,
None if self.options.fake_missing_bindings => super::BindTarget {
space: u8::MAX,
register: key.group,
binding_array_size: None,
dynamic_storage_buffer_offsets_index: None,
restrict_indexing: false,
},
None => {
unreachable!("Sampler buffer of group {key:?} not bound to a register");
}
};
let bit_width = width * 8;
let type_name = crate::Scalar {
kind: ScalarKind::Float,
width,
}
.to_hlsl_str()?;
// typedef
write!(self.out, "typedef struct {{ ")?; for i in 0..columns as u8 {
write!(self.out, "{type_name}2 _{i}; ")?;
}
writeln!(self.out, "}} __mat{}x2_f{bit_width};", columns as u8)?;
// __get_col_of_mat
writeln!(
self.out, "{type_name}2 __get_col_of_mat{}x2_f{bit_width}(__mat{}x2_f{bit_width} mat, uint idx) {{",
columns as u8, columns as u8
)?;
writeln!(self.out, "{INDENT}switch(idx) {{")?; for i in 0..columns as u8 {
writeln!(self.out, "{INDENT}case {i}: {{ return mat._{i}; }}")?;
}
writeln!(self.out, "{INDENT}default: {{ return ({type_name}2)0; }}")?;
writeln!(self.out, "{INDENT}}}")?;
writeln!(self.out, "}}")?;
// __set_col_of_mat
writeln!(
self.out, "void __set_col_of_mat{}x2_f{bit_width}(__mat{}x2_f{bit_width} mat, uint idx, {type_name}2 value) {{",
columns as u8, columns as u8
)?;
writeln!(self.out, "{INDENT}switch(idx) {{")?; for i in 0..columns as u8 {
writeln!(self.out, "{INDENT}case {i}: {{ mat._{i} = value; break; }}")?;
}
writeln!(self.out, "{INDENT}}}")?;
writeln!(self.out, "}}")?;
// __set_el_of_mat
writeln!(
self.out, "void __set_el_of_mat{}x2_f{bit_width}(__mat{}x2_f{bit_width} mat, uint idx, uint vec_idx, {type_name} value) {{",
columns as u8, columns as u8
)?;
writeln!(self.out, "{INDENT}switch(idx) {{")?; for i in 0..columns as u8 {
writeln!(
self.out, "{INDENT}case {i}: {{ mat._{i}[vec_idx] = value; break; }}"
)?;
}
writeln!(self.out, "{INDENT}}}")?;
writeln!(self.out, "}}")?;
writeln!(self.out)?;
Ok(())
}
pub(super) fn write_all_mat_cx2_typedefs_and_functions(
&mut self,
module: &crate::Module,
) -> BackendResult { for (handle, _) in module.global_variables.iter() {
let global = &module.global_variables[handle];
if global.space == crate::AddressSpace::Uniform { if let Some(super::writer::MatrixType {
columns,
rows: crate::VectorSize::Bi,
width,
}) = super::writer::get_inner_matrix_data(module, global.ty)
{
let entry = WrappedMatCx2 { columns, width }; if self.wrapped.insert(WrappedType::MatCx2(entry)) {
self.write_mat_cx2_typedef_and_functions(entry)?;
}
}
}
}
for (_, ty) in module.types.iter() { if let crate::TypeInner::Struct { ref members, .. } = ty.inner { for member in members.iter() { if let crate::TypeInner::Array { .. } = module.types[member.ty].inner { if let Some(super::writer::MatrixType {
columns,
rows: crate::VectorSize::Bi,
width,
}) = super::writer::get_inner_matrix_data(module, member.ty)
{
let entry = WrappedMatCx2 { columns, width }; if self.wrapped.insert(WrappedType::MatCx2(entry)) {
self.write_mat_cx2_typedef_and_functions(entry)?;
}
}
}
}
}
}
/// Helper function that write wrapped function for `Expression::ZeroValue` /// /// This is necessary since we might have a member access after the zero value expression, e.g. /// `.y` (in practice this can come up when consuming SPIRV that's been produced by glslc). /// /// So we can't just write `(float4)0` since `(float4)0.y` won't parse correctly. /// /// Parenthesizing the expression like `((float4)0).y` would work... except DXC can't handle /// cases like: /// /// ```text /// tests\out\hlsl\access.hlsl:183:41: error: cannot compile this l-value expression yet /// t_1.am = (__mat4x2_f32[2])((float4x2[2])0); /// ^ /// ```
fn write_wrapped_zero_value_function(
&mut self,
module: &crate::Module,
zero_value: WrappedZeroValue,
) -> BackendResult {
use crate::back::INDENT;
// Write function return type and name if let crate::TypeInner::Array { base, size, .. } = module.types[zero_value.ty].inner {
write!(self.out, "typedef ")?;
self.write_type(module, zero_value.ty)?;
write!(self.out, " ret_")?;
self.write_wrapped_zero_value_function_name(module, zero_value)?;
self.write_array_size(module, base, size)?;
writeln!(self.out, ";")?;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.