Quelle builtins.rs
Sprache: unbekannt
|
|
use super::{
ast::{
BuiltinVariations, FunctionDeclaration, FunctionKind, Overload, ParameterInfo,
ParameterQualifier,
},
context::Context,
Error, ErrorKind, Frontend, Result,
};
use crate::{
BinaryOperator, DerivativeAxis as Axis, DerivativeControl as Ctrl, Expression, Handle,
ImageClass, ImageDimension as Dim, ImageQuery, MathFunction, Module, RelationalFunction ,
SampleLevel, Scalar, ScalarKind as Sk, Span, Type, TypeInner, UnaryOperator, VectorSize,
};
impl crate::ScalarKind {
const fn dummy_storage_format(&self) -> crate::StorageFormat {
match *self {
Sk::Sint => crate::StorageFormat::R16Sint,
Sk::Uint => crate::StorageFormat::R16Uint,
_ => crate::StorageFormat::R16Float,
}
}
}
impl Module {
/// Helper function, to create a function prototype for a builtin
fn add_builtin(&mut self, args: Vec<TypeInner>, builtin: MacroCall) -> Overload {
let mut parameters = Vec::with_capacity(args.len());
let mut parameters_info = Vec::with_capacity(args.len());
for arg in args {
parameters.push(self.types.insert(
Type {
name: None,
inner: arg,
},
Span::default(),
));
parameters_info.push(ParameterInfo {
qualifier: ParameterQualifier::In,
depth: false,
});
}
Overload {
parameters,
parameters_info,
kind: FunctionKind::Macro(builtin),
defined: false,
internal: true,
void: false,
}
}
}
const fn make_coords_arg(number_of_components: usize, kind: Sk) -> TypeInner {
let scalar = Scalar { kind, width: 4 };
match number_of_components {
1 => TypeInner::Scalar(scalar),
_ => TypeInner::Vector {
size: match number_of_components {
2 => VectorSize::Bi,
3 => VectorSize::Tri,
_ => VectorSize::Quad,
},
scalar,
},
}
}
/// Inject builtins into the declaration
///
/// This is done to not add a large startup cost and not increase memory
/// usage if it isn't needed.
pub fn inject_builtin(
declaration: &mut FunctionDeclaration,
module: &mut Module,
name: &str,
mut variations: BuiltinVariations,
) {
log::trace!(
"{} variations: {:?} {:?}",
name,
variations,
declaration.variations
);
// Don't regeneate variations
variations.remove(declaration.variations);
declaration.variations |= variations;
if variations.contains(BuiltinVariations::STANDARD) {
inject_standard_builtins(declaration, module, name)
}
if variations.contains(BuiltinVariations::DOUBLE) {
inject_double_builtin(declaration, module, name)
}
match name {
"texture"
| "textureGrad"
| "textureGradOffset"
| "textureLod"
| "textureLodOffset"
| "textureOffset"
| "textureProj"
| "textureProjGrad"
| "textureProjGradOffset"
| "textureProjLod"
| "textureProjLodOffset"
| "textureProjOffset" => {
let f = |kind, dim, arrayed, multi, shadow| {
for bits in 0..=0b11 {
let variant = bits & 0b1 != 0;
let bias = bits & 0b10 != 0;
let (proj, offset, level_type) = match name {
// texture(gsampler, gvec P, [float bias]);
"texture" => (false, false, TextureLevelType::None),
// textureGrad(gsampler, gvec P, gvec dPdx, gvec dPdy);
"textureGrad" => (false, false, TextureLevelType::Grad),
// textureGradOffset(gsampler, gvec P, gvec dPdx, gvec dPdy, ivec offset);
"textureGradOffset" => (false, true, TextureLevelType::Grad),
// textureLod(gsampler, gvec P, float lod);
"textureLod" => (false, false, TextureLevelType::Lod),
// textureLodOffset(gsampler, gvec P, float lod, ivec offset);
"textureLodOffset" => (false, true, TextureLevelType::Lod),
// textureOffset(gsampler, gvec+1 P, ivec offset, [float bias]);
"textureOffset" => (false, true, TextureLevelType::None),
// textureProj(gsampler, gvec+1 P, [float bias]);
"textureProj" => (true, false, TextureLevelType::None),
// textureProjGrad(gsampler, gvec+1 P, gvec dPdx, gvec dPdy);
"textureProjGrad" => (true, false, TextureLevelType::Grad),
// textureProjGradOffset(gsampler, gvec+1 P, gvec dPdx, gvec dPdy, ivec offset);
"textureProjGradOffset" => (true, true, TextureLevelType::Grad),
// textureProjLod(gsampler, gvec+1 P, float lod);
"textureProjLod" => (true, false, TextureLevelType::Lod),
// textureProjLodOffset(gsampler, gvec+1 P, gvec dPdx, gvec dPdy, ivec offset);
"textureProjLodOffset" => (true, true, TextureLevelType::Lod),
// textureProjOffset(gsampler, gvec+1 P, ivec offset, [float bias]);
"textureProjOffset" => (true, true, TextureLevelType::None),
_ => unreachable!(),
};
let builtin = MacroCall::Texture {
proj,
offset,
shadow,
level_type,
};
// Parse out the variant settings.
let grad = level_type == TextureLevelType::Grad;
let lod = level_type == TextureLevelType::Lod;
let supports_variant = proj && !shadow;
if variant && !supports_variant {
continue;
}
if bias && !matches!(level_type, TextureLevelType::None) {
continue;
}
// Proj doesn't work with arrayed or Cube
if proj && (arrayed || dim == Dim::Cube) {
continue;
}
// texture operations with offset are not supported for cube maps
if dim == Dim::Cube && offset {
continue;
}
// sampler2DArrayShadow can't be used in textureLod or in texture with bias
if (lod || bias) && arrayed && shadow && dim == Dim::D2 {
continue;
}
// TODO: glsl supports using bias with depth samplers but naga doesn't
if bias && shadow {
continue;
}
let class = match shadow {
true => ImageClass::Depth { multi },
false => ImageClass::Sampled { kind, multi },
};
let image = TypeInner::Image {
dim,
arrayed,
class,
};
let num_coords_from_dim = image_dims_to_coords_size(dim).min(3);
let mut num_coords = num_coords_from_dim;
if shadow && proj {
num_coords = 4;
} else if dim == Dim::D1 && shadow {
num_coords = 3;
} else if shadow {
num_coords += 1;
} else if proj {
if variant && num_coords == 4 {
// Normal form already has 4 components, no need to have a variant form.
continue;
} else if variant {
num_coords = 4;
} else {
num_coords += 1;
}
}
if !(dim == Dim::D1 && shadow) {
num_coords += arrayed as usize;
}
// Special case: texture(gsamplerCubeArrayShadow) kicks the shadow compare ref to a separate argument,
// since it would otherwise take five arguments. It also can't take a bias, nor can it be proj/grad/lod/offset
// (presumably because nobody asked for it, and implementation complexity?)
if num_coords >= 5 {
if lod || grad || offset || proj || bias {
continue;
}
debug_assert!(dim == Dim::Cube && shadow && arrayed);
}
debug_assert!(num_coords <= 5);
let vector = make_coords_arg(num_coords, Sk::Float);
let mut args = vec![image, vector];
if num_coords == 5 {
args.push(TypeInner::Scalar(Scalar::F32));
}
match level_type {
TextureLevelType::Lod => {
args.push(TypeInner::Scalar(Scalar::F32));
}
TextureLevelType::Grad => {
args.push(make_coords_arg(num_coords_from_dim, Sk::Float));
args.push(make_coords_arg(num_coords_from_dim, Sk::Float));
}
TextureLevelType::None => {}
};
if offset {
args.push(make_coords_arg(num_coords_from_dim, Sk::Sint));
}
if bias {
args.push(TypeInner::Scalar(Scalar::F32));
}
declaration
.overloads
.push(module.add_builtin(args, builtin));
}
};
texture_args_generator(TextureArgsOptions::SHADOW | variations.into(), f)
}
"textureSize" => {
let f = |kind, dim, arrayed, multi, shadow| {
let class = match shadow {
true => ImageClass::Depth { multi },
false => ImageClass::Sampled { kind, multi },
};
let image = TypeInner::Image {
dim,
arrayed,
class,
};
let mut args = vec![image];
if !multi {
args.push(TypeInner::Scalar(Scalar::I32))
}
declaration
.overloads
.push(module.add_builtin(args, MacroCall::TextureSize { arrayed }))
};
texture_args_generator(
TextureArgsOptions::SHADOW | TextureArgsOptions::MULTI | variations.into(),
f,
)
}
"textureQueryLevels" => {
let f = |kind, dim, arrayed, multi, shadow| {
let class = match shadow {
true => ImageClass::Depth { multi },
false => ImageClass::Sampled { kind, multi },
};
let image = TypeInner::Image {
dim,
arrayed,
class,
};
declaration
.overloads
.push(module.add_builtin(vec![image], MacroCall::TextureQueryLevels))
};
texture_args_generator(TextureArgsOptions::SHADOW | variations.into(), f)
}
"texelFetch" | "texelFetchOffset" => {
let offset = "texelFetchOffset" == name;
let f = |kind, dim, arrayed, multi, _shadow| {
// Cube images aren't supported
if let Dim::Cube = dim {
return;
}
let image = TypeInner::Image {
dim,
arrayed,
class: ImageClass::Sampled { kind, multi },
};
let dim_value = image_dims_to_coords_size(dim);
let coordinates = make_coords_arg(dim_value + arrayed as usize, Sk::Sint);
let mut args = vec![image, coordinates, TypeInner::Scalar(Scalar::I32)];
if offset {
args.push(make_coords_arg(dim_value, Sk::Sint));
}
declaration
.overloads
.push(module.add_builtin(args, MacroCall::ImageLoad { multi }))
};
// Don't generate shadow images since they aren't supported
texture_args_generator(TextureArgsOptions::MULTI | variations.into(), f)
}
"imageSize" => {
let f = |kind: Sk, dim, arrayed, _, _| {
// Naga doesn't support cube images and it's usefulness
// is questionable, so they won't be supported for now
if dim == Dim::Cube {
return;
}
let image = TypeInner::Image {
dim,
arrayed,
class: ImageClass::Storage {
format: kind.dummy_storage_format(),
access: crate::StorageAccess::empty(),
},
};
declaration
.overloads
.push(module.add_builtin(vec![image], MacroCall::TextureSize { arrayed }))
};
texture_args_generator(variations.into(), f)
}
"imageLoad" => {
let f = |kind: Sk, dim, arrayed, _, _| {
// Naga doesn't support cube images and it's usefulness
// is questionable, so they won't be supported for now
if dim == Dim::Cube {
return;
}
let image = TypeInner::Image {
dim,
arrayed,
class: ImageClass::Storage {
format: kind.dummy_storage_format(),
access: crate::StorageAccess::LOAD,
},
};
let dim_value = image_dims_to_coords_size(dim);
let mut coord_size = dim_value + arrayed as usize;
// > Every OpenGL API call that operates on cubemap array
// > textures takes layer-faces, not array layers
//
// So this means that imageCubeArray only takes a three component
// vector coordinate and the third component is a layer index.
if Dim::Cube == dim && arrayed {
coord_size = 3
}
let coordinates = make_coords_arg(coord_size, Sk::Sint);
let args = vec![image, coordinates];
declaration
.overloads
.push(module.add_builtin(args, MacroCall::ImageLoad { multi: false }))
};
// Don't generate shadow nor multisampled images since they aren't supported
texture_args_generator(variations.into(), f)
}
"imageStore" => {
let f = |kind: Sk, dim, arrayed, _, _| {
// Naga doesn't support cube images and it's usefulness
// is questionable, so they won't be supported for now
if dim == Dim::Cube {
return;
}
let image = TypeInner::Image {
dim,
arrayed,
class: ImageClass::Storage {
format: kind.dummy_storage_format(),
access: crate::StorageAccess::STORE,
},
};
let dim_value = image_dims_to_coords_size(dim);
let mut coord_size = dim_value + arrayed as usize;
// > Every OpenGL API call that operates on cubemap array
// > textures takes layer-faces, not array layers
//
// So this means that imageCubeArray only takes a three component
// vector coordinate and the third component is a layer index.
if Dim::Cube == dim && arrayed {
coord_size = 3
}
let coordinates = make_coords_arg(coord_size, Sk::Sint);
let args = vec![
image,
coordinates,
TypeInner::Vector {
size: VectorSize::Quad,
scalar: Scalar { kind, width: 4 },
},
];
let mut overload = module.add_builtin(args, MacroCall::ImageStore);
overload.void = true;
declaration.overloads.push(overload)
};
// Don't generate shadow nor multisampled images since they aren't supported
texture_args_generator(variations.into(), f)
}
_ => {}
}
}
/// Injects the builtins into declaration that don't need any special variations
fn inject_standard_builtins(
declaration: &mut FunctionDeclaration,
module: &mut Module,
name: &str,
) {
// Some samplers (sampler1D, etc...) can be float, int, or uint
let anykind_sampler = if name.starts_with("sampler") {
Some((name, Sk::Float))
} else if name.starts_with("usampler") {
Some((&name[1..], Sk::Uint))
} else if name.starts_with("isampler") {
Some((&name[1..], Sk::Sint))
} else {
None
};
if let Some((sampler, kind)) = anykind_sampler {
match sampler {
"sampler1D" | "sampler1DArray" | "sampler2D" | "sampler2DArray" | "sampler2DMS"
| "sampler2DMSArray" | "sampler3D" | "samplerCube" | "samplerCubeArray" => {
declaration.overloads.push(module.add_builtin(
vec![
TypeInner::Image {
dim: match sampler {
"sampler1D" | "sampler1DArray" => Dim::D1,
"sampler2D" | "sampler2DArray" | "sampler2DMS"
| "sampler2DMSArray" => Dim::D2,
"sampler3D" => Dim::D3,
_ => Dim::Cube,
},
arrayed: matches!(
sampler,
"sampler1DArray"
| "sampler2DArray"
| "sampler2DMSArray"
| "samplerCubeArray"
),
class: ImageClass::Sampled {
kind,
multi: matches!(sampler, "sampler2DMS" | "sampler2DMSArray"),
},
},
TypeInner::Sampler { comparison: false },
],
MacroCall::Sampler,
));
return;
}
_ => (),
}
}
match name {
// Shadow sampler can only be of kind `Sk::Float`
"sampler1DShadow"
| "sampler1DArrayShadow"
| "sampler2DShadow"
| "sampler2DArrayShadow"
| "samplerCubeShadow"
| "samplerCubeArrayShadow" => {
let dim = match name {
"sampler1DShadow" | "sampler1DArrayShadow" => Dim::D1,
"sampler2DShadow" | "sampler2DArrayShadow" => Dim::D2,
_ => Dim::Cube,
};
let arrayed = matches!(
name,
"sampler1DArrayShadow" | "sampler2DArrayShadow" | "samplerCubeArrayShadow"
);
for i in 0..2 {
let ty = TypeInner::Image {
dim,
arrayed,
class: match i {
0 => ImageClass::Sampled {
kind: Sk::Float,
multi: false,
},
_ => ImageClass::Depth { multi: false },
},
};
declaration.overloads.push(module.add_builtin(
vec![ty, TypeInner::Sampler { comparison: true }],
MacroCall::SamplerShadow,
))
}
}
"sin" | "exp" | "exp2" | "sinh" | "cos" | "cosh" | "tan" | "tanh" | "acos" | "asin"
| "log" | "log2" | "radians" | "degrees" | "asinh" | "acosh" | "atanh"
| "floatBitsToInt" | "floatBitsToUint" | "dFdx" | "dFdxFine" | "dFdxCoarse" | "dFdy"
| "dFdyFine" | "dFdyCoarse" | "fwidth" | "fwidthFine" | "fwidthCoarse" => {
// bits layout
// bit 0 through 1 - dims
for bits in 0..0b100 {
let size = match bits {
0b00 => None,
0b01 => Some(VectorSize::Bi),
0b10 => Some(VectorSize::Tri),
_ => Some(VectorSize::Quad),
};
let scalar = Scalar::F32;
declaration.overloads.push(module.add_builtin(
vec![match size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
}],
match name {
"sin" => MacroCall::MathFunction(MathFunction::Sin),
"exp" => MacroCall::MathFunction(MathFunction::Exp),
"exp2" => MacroCall::MathFunction(MathFunction::Exp2),
"sinh" => MacroCall::MathFunction(MathFunction::Sinh),
"cos" => MacroCall::MathFunction(MathFunction::Cos),
"cosh" => MacroCall::MathFunction(MathFunction::Cosh),
"tan" => MacroCall::MathFunction(MathFunction::Tan),
"tanh" => MacroCall::MathFunction(MathFunction::Tanh),
"acos" => MacroCall::MathFunction(MathFunction::Acos),
"asin" => MacroCall::MathFunction(MathFunction::Asin),
"log" => MacroCall::MathFunction(MathFunction::Log),
"log2" => MacroCall::MathFunction(MathFunction::Log2),
"asinh" => MacroCall::MathFunction(MathFunction::Asinh),
"acosh" => MacroCall::MathFunction(MathFunction::Acosh),
"atanh" => MacroCall::MathFunction(MathFunction::Atanh),
"radians" => MacroCall::MathFunction(MathFunction::Radians),
"degrees" => MacroCall::MathFunction(MathFunction::Degrees),
"floatBitsToInt" => MacroCall::BitCast(Sk::Sint),
"floatBitsToUint" => MacroCall::BitCast(Sk::Uint),
"dFdxCoarse" => MacroCall::Derivate(Axis::X, Ctrl::Coarse),
"dFdyCoarse" => MacroCall::Derivate(Axis::Y, Ctrl::Coarse),
"fwidthCoarse" => MacroCall::Derivate(Axis::Width, Ctrl::Coarse),
"dFdxFine" => MacroCall::Derivate(Axis::X, Ctrl::Fine),
"dFdyFine" => MacroCall::Derivate(Axis::Y, Ctrl::Fine),
"fwidthFine" => MacroCall::Derivate(Axis::Width, Ctrl::Fine),
"dFdx" => MacroCall::Derivate(Axis::X, Ctrl::None),
"dFdy" => MacroCall::Derivate(Axis::Y, Ctrl::None),
"fwidth" => MacroCall::Derivate(Axis::Width, Ctrl::None),
_ => unreachable!(),
},
))
}
}
"intBitsToFloat" | "uintBitsToFloat" => {
// bits layout
// bit 0 through 1 - dims
for bits in 0..0b100 {
let size = match bits {
0b00 => None,
0b01 => Some(VectorSize::Bi),
0b10 => Some(VectorSize::Tri),
_ => Some(VectorSize::Quad),
};
let scalar = match name {
"intBitsToFloat" => Scalar::I32,
_ => Scalar::U32,
};
declaration.overloads.push(module.add_builtin(
vec![match size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
}],
MacroCall::BitCast(Sk::Float),
))
}
}
"pow" => {
// bits layout
// bit 0 through 1 - dims
for bits in 0..0b100 {
let size = match bits {
0b00 => None,
0b01 => Some(VectorSize::Bi),
0b10 => Some(VectorSize::Tri),
_ => Some(VectorSize::Quad),
};
let scalar = Scalar::F32;
let ty = || match size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
};
declaration.overloads.push(
module
.add_builtin(vec![ty(), ty()], MacroCall::MathFunction(MathFunction::Pow)),
)
}
}
"abs" | "sign" => {
// bits layout
// bit 0 through 1 - dims
// bit 2 - float/sint
for bits in 0..0b1000 {
let size = match bits & 0b11 {
0b00 => None,
0b01 => Some(VectorSize::Bi),
0b10 => Some(VectorSize::Tri),
_ => Some(VectorSize::Quad),
};
let scalar = match bits >> 2 {
0b0 => Scalar::F32,
_ => Scalar::I32,
};
let args = vec![match size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
}];
declaration.overloads.push(module.add_builtin(
args,
MacroCall::MathFunction(match name {
"abs" => MathFunction::Abs,
"sign" => MathFunction::Sign,
_ => unreachable!(),
}),
))
}
}
"bitCount" | "bitfieldReverse" | "bitfieldExtract" | "bitfieldInsert" | "findLSB"
| "findMSB" => {
let fun = match name {
"bitCount" => MathFunction::CountOneBits,
"bitfieldReverse" => MathFunction::ReverseBits,
"bitfieldExtract" => MathFunction::ExtractBits,
"bitfieldInsert" => MathFunction::InsertBits,
"findLSB" => MathFunction::FirstTrailingBit,
"findMSB" => MathFunction::FirstLeadingBit,
_ => unreachable!(),
};
let mc = match fun {
MathFunction::ExtractBits => MacroCall::BitfieldExtract,
MathFunction::InsertBits => MacroCall::BitfieldInsert,
_ => MacroCall::MathFunction(fun),
};
// bits layout
// bit 0 - int/uint
// bit 1 through 2 - dims
for bits in 0..0b1000 {
let scalar = match bits & 0b1 {
0b0 => Scalar::I32,
_ => Scalar::U32,
};
let size = match bits >> 1 {
0b00 => None,
0b01 => Some(VectorSize::Bi),
0b10 => Some(VectorSize::Tri),
_ => Some(VectorSize::Quad),
};
let ty = || match size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
};
let mut args = vec![ty()];
match fun {
MathFunction::ExtractBits => {
args.push(TypeInner::Scalar(Scalar::I32));
args.push(TypeInner::Scalar(Scalar::I32));
}
MathFunction::InsertBits => {
args.push(ty());
args.push(TypeInner::Scalar(Scalar::I32));
args.push(TypeInner::Scalar(Scalar::I32));
}
_ => {}
}
// we need to cast the return type of findLsb / findMsb
let mc = if scalar.kind == Sk::Uint {
match mc {
MacroCall::MathFunction(MathFunction::FirstTrailingBit) => {
MacroCall::FindLsbUint
}
MacroCall::MathFunction(MathFunction::FirstLeadingBit) => {
MacroCall::FindMsbUint
}
mc => mc,
}
} else {
mc
};
declaration.overloads.push(module.add_builtin(args, mc))
}
}
"packSnorm4x8" | "packUnorm4x8" | "packSnorm2x16" | "packUnorm2x16" | "packHalf2x16" => {
let fun = match name {
"packSnorm4x8" => MathFunction::Pack4x8snorm,
"packUnorm4x8" => MathFunction::Pack4x8unorm,
"packSnorm2x16" => MathFunction::Pack2x16unorm,
"packUnorm2x16" => MathFunction::Pack2x16snorm,
"packHalf2x16" => MathFunction::Pack2x16float,
_ => unreachable!(),
};
let ty = match fun {
MathFunction::Pack4x8snorm | MathFunction::Pack4x8unorm => TypeInner::Vector {
size: VectorSize::Quad,
scalar: Scalar::F32,
},
MathFunction::Pack2x16unorm
| MathFunction::Pack2x16snorm
| MathFunction::Pack2x16float => TypeInner::Vector {
size: VectorSize::Bi,
scalar: Scalar::F32,
},
_ => unreachable!(),
};
let args = vec![ty];
declaration
.overloads
.push(module.add_builtin(args, MacroCall::MathFunction(fun)));
}
"unpackSnorm4x8" | "unpackUnorm4x8" | "unpackSnorm2x16" | "unpackUnorm2x16"
| "unpackHalf2x16" => {
let fun = match name {
"unpackSnorm4x8" => MathFunction::Unpack4x8snorm,
"unpackUnorm4x8" => MathFunction::Unpack4x8unorm,
"unpackSnorm2x16" => MathFunction::Unpack2x16snorm,
"unpackUnorm2x16" => MathFunction::Unpack2x16unorm,
"unpackHalf2x16" => MathFunction::Unpack2x16float,
_ => unreachable!(),
};
let args = vec![TypeInner::Scalar(Scalar::U32)];
declaration
.overloads
.push(module.add_builtin(args, MacroCall::MathFunction(fun)));
}
"atan" => {
// bits layout
// bit 0 - atan/atan2
// bit 1 through 2 - dims
for bits in 0..0b1000 {
let fun = match bits & 0b1 {
0b0 => MathFunction::Atan,
_ => MathFunction::Atan2,
};
let size = match bits >> 1 {
0b00 => None,
0b01 => Some(VectorSize::Bi),
0b10 => Some(VectorSize::Tri),
_ => Some(VectorSize::Quad),
};
let scalar = Scalar::F32;
let ty = || match size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
};
let mut args = vec![ty()];
if fun == MathFunction::Atan2 {
args.push(ty())
}
declaration
.overloads
.push(module.add_builtin(args, MacroCall::MathFunction(fun)))
}
}
"all" | "any" | "not" => {
// bits layout
// bit 0 through 1 - dims
for bits in 0..0b11 {
let size = match bits {
0b00 => VectorSize::Bi,
0b01 => VectorSize::Tri,
_ => VectorSize::Quad,
};
let args = vec![TypeInner::Vector {
size,
scalar: Scalar::BOOL,
}];
let fun = match name {
"all" => MacroCall::Relational(RelationalFunction::All),
"any" => MacroCall::Relational(RelationalFunction::Any),
"not" => MacroCall::Unary(UnaryOperator::LogicalNot),
_ => unreachable!(),
};
declaration.overloads.push(module.add_builtin(args, fun))
}
}
"lessThan" | "greaterThan" | "lessThanEqual" | "greaterThanEqual" => {
for bits in 0..0b1001 {
let (size, scalar) = match bits {
0b0000 => (VectorSize::Bi, Scalar::F32),
0b0001 => (VectorSize::Tri, Scalar::F32),
0b0010 => (VectorSize::Quad, Scalar::F32),
0b0011 => (VectorSize::Bi, Scalar::I32),
0b0100 => (VectorSize::Tri, Scalar::I32),
0b0101 => (VectorSize::Quad, Scalar::I32),
0b0110 => (VectorSize::Bi, Scalar::U32),
0b0111 => (VectorSize::Tri, Scalar::U32),
_ => (VectorSize::Quad, Scalar::U32),
};
let ty = || TypeInner::Vector { size, scalar };
let args = vec![ty(), ty()];
let fun = MacroCall::Binary(match name {
"lessThan" => BinaryOperator::Less,
"greaterThan" => BinaryOperator::Greater,
"lessThanEqual" => BinaryOperator::LessEqual,
"greaterThanEqual" => BinaryOperator::GreaterEqual,
_ => unreachable!(),
});
declaration.overloads.push(module.add_builtin(args, fun))
}
}
"equal" | "notEqual" => {
for bits in 0..0b1100 {
let (size, scalar) = match bits {
0b0000 => (VectorSize::Bi, Scalar::F32),
0b0001 => (VectorSize::Tri, Scalar::F32),
0b0010 => (VectorSize::Quad, Scalar::F32),
0b0011 => (VectorSize::Bi, Scalar::I32),
0b0100 => (VectorSize::Tri, Scalar::I32),
0b0101 => (VectorSize::Quad, Scalar::I32),
0b0110 => (VectorSize::Bi, Scalar::U32),
0b0111 => (VectorSize::Tri, Scalar::U32),
0b1000 => (VectorSize::Quad, Scalar::U32),
0b1001 => (VectorSize::Bi, Scalar::BOOL),
0b1010 => (VectorSize::Tri, Scalar::BOOL),
_ => (VectorSize::Quad, Scalar::BOOL),
};
let ty = || TypeInner::Vector { size, scalar };
let args = vec![ty(), ty()];
let fun = MacroCall::Binary(match name {
"equal" => BinaryOperator::Equal,
"notEqual" => BinaryOperator::NotEqual,
_ => unreachable!(),
});
declaration.overloads.push(module.add_builtin(args, fun))
}
}
"min" | "max" => {
// bits layout
// bit 0 through 1 - scalar kind
// bit 2 through 4 - dims
for bits in 0..0b11100 {
let scalar = match bits & 0b11 {
0b00 => Scalar::F32,
0b01 => Scalar::I32,
0b10 => Scalar::U32,
_ => continue,
};
let (size, second_size) = match bits >> 2 {
0b000 => (None, None),
0b001 => (Some(VectorSize::Bi), None),
0b010 => (Some(VectorSize::Tri), None),
0b011 => (Some(VectorSize::Quad), None),
0b100 => (Some(VectorSize::Bi), Some(VectorSize::Bi)),
0b101 => (Some(VectorSize::Tri), Some(VectorSize::Tri)),
_ => (Some(VectorSize::Quad), Some(VectorSize::Quad)),
};
let args = vec![
match size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
},
match second_size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
},
];
let fun = match name {
"max" => MacroCall::Splatted(MathFunction::Max, size, 1),
"min" => MacroCall::Splatted(MathFunction::Min, size, 1),
_ => unreachable!(),
};
declaration.overloads.push(module.add_builtin(args, fun))
}
}
"mix" => {
// bits layout
// bit 0 through 1 - dims
// bit 2 through 4 - types
//
// 0b10011 is the last element since splatted single elements
// were already added
for bits in 0..0b10011 {
let size = match bits & 0b11 {
0b00 => Some(VectorSize::Bi),
0b01 => Some(VectorSize::Tri),
0b10 => Some(VectorSize::Quad),
_ => None,
};
let (scalar, splatted, boolean) = match bits >> 2 {
0b000 => (Scalar::I32, false, true),
0b001 => (Scalar::U32, false, true),
0b010 => (Scalar::F32, false, true),
0b011 => (Scalar::F32, false, false),
_ => (Scalar::F32, true, false),
};
let ty = |scalar| match size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
};
let args = vec![
ty(scalar),
ty(scalar),
match (boolean, splatted) {
(true, _) => ty(Scalar::BOOL),
(_, false) => TypeInner::Scalar(scalar),
_ => ty(scalar),
},
];
declaration.overloads.push(module.add_builtin(
args,
match boolean {
true => MacroCall::MixBoolean,
false => MacroCall::Splatted(MathFunction::Mix, size, 2),
},
))
}
}
"clamp" => {
// bits layout
// bit 0 through 1 - float/int/uint
// bit 2 through 3 - dims
// bit 4 - splatted
//
// 0b11010 is the last element since splatted single elements
// were already added
for bits in 0..0b11011 {
let scalar = match bits & 0b11 {
0b00 => Scalar::F32,
0b01 => Scalar::I32,
0b10 => Scalar::U32,
_ => continue,
};
let size = match (bits >> 2) & 0b11 {
0b00 => Some(VectorSize::Bi),
0b01 => Some(VectorSize::Tri),
0b10 => Some(VectorSize::Quad),
_ => None,
};
let splatted = bits & 0b10000 == 0b10000;
let base_ty = || match size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
};
let limit_ty = || match splatted {
true => TypeInner::Scalar(scalar),
false => base_ty(),
};
let args = vec![base_ty(), limit_ty(), limit_ty()];
declaration
.overloads
.push(module.add_builtin(args, MacroCall::Clamp(size)))
}
}
"barrier" => declaration
.overloads
.push(module.add_builtin(Vec::new(), MacroCall::Barrier)),
// Add common builtins with floats
_ => inject_common_builtin(declaration, module, name, 4),
}
}
/// Injects the builtins into declaration that need doubles
fn inject_double_builtin(declaration: &mut FunctionDeclaration, module: &mut Module, name: &str) {
match name {
"abs" | "sign" => {
// bits layout
// bit 0 through 1 - dims
for bits in 0..0b100 {
let size = match bits {
0b00 => None,
0b01 => Some(VectorSize::Bi),
0b10 => Some(VectorSize::Tri),
_ => Some(VectorSize::Quad),
};
let scalar = Scalar::F64;
let args = vec![match size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
}];
declaration.overloads.push(module.add_builtin(
args,
MacroCall::MathFunction(match name {
"abs" => MathFunction::Abs,
"sign" => MathFunction::Sign,
_ => unreachable!(),
}),
))
}
}
"min" | "max" => {
// bits layout
// bit 0 through 2 - dims
for bits in 0..0b111 {
let (size, second_size) = match bits {
0b000 => (None, None),
0b001 => (Some(VectorSize::Bi), None),
0b010 => (Some(VectorSize::Tri), None),
0b011 => (Some(VectorSize::Quad), None),
0b100 => (Some(VectorSize::Bi), Some(VectorSize::Bi)),
0b101 => (Some(VectorSize::Tri), Some(VectorSize::Tri)),
_ => (Some(VectorSize::Quad), Some(VectorSize::Quad)),
};
let scalar = Scalar::F64;
let args = vec![
match size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
},
match second_size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
},
];
let fun = match name {
"max" => MacroCall::Splatted(MathFunction::Max, size, 1),
"min" => MacroCall::Splatted(MathFunction::Min, size, 1),
_ => unreachable!(),
};
declaration.overloads.push(module.add_builtin(args, fun))
}
}
"mix" => {
// bits layout
// bit 0 through 1 - dims
// bit 2 through 3 - splatted/boolean
//
// 0b1010 is the last element since splatted with single elements
// is equal to normal single elements
for bits in 0..0b1011 {
let size = match bits & 0b11 {
0b00 => Some(VectorSize::Quad),
0b01 => Some(VectorSize::Bi),
0b10 => Some(VectorSize::Tri),
_ => None,
};
let scalar = Scalar::F64;
let (splatted, boolean) = match bits >> 2 {
0b00 => (false, false),
0b01 => (false, true),
_ => (true, false),
};
let ty = |scalar| match size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
};
let args = vec![
ty(scalar),
ty(scalar),
match (boolean, splatted) {
(true, _) => ty(Scalar::BOOL),
(_, false) => TypeInner::Scalar(scalar),
_ => ty(scalar),
},
];
declaration.overloads.push(module.add_builtin(
args,
match boolean {
true => MacroCall::MixBoolean,
false => MacroCall::Splatted(MathFunction::Mix, size, 2),
},
))
}
}
"clamp" => {
// bits layout
// bit 0 through 1 - dims
// bit 2 - splatted
//
// 0b110 is the last element since splatted with single elements
// is equal to normal single elements
for bits in 0..0b111 {
let scalar = Scalar::F64;
let size = match bits & 0b11 {
0b00 => Some(VectorSize::Bi),
0b01 => Some(VectorSize::Tri),
0b10 => Some(VectorSize::Quad),
_ => None,
};
let splatted = bits & 0b100 == 0b100;
let base_ty = || match size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
};
let limit_ty = || match splatted {
true => TypeInner::Scalar(scalar),
false => base_ty(),
};
let args = vec![base_ty(), limit_ty(), limit_ty()];
declaration
.overloads
.push(module.add_builtin(args, MacroCall::Clamp(size)))
}
}
"lessThan" | "greaterThan" | "lessThanEqual" | "greaterThanEqual" | "equal"
| "notEqual" => {
let scalar = Scalar::F64;
for bits in 0..0b11 {
let size = match bits {
0b00 => VectorSize::Bi,
0b01 => VectorSize::Tri,
_ => VectorSize::Quad,
};
let ty = || TypeInner::Vector { size, scalar };
let args = vec![ty(), ty()];
let fun = MacroCall::Binary(match name {
"lessThan" => BinaryOperator::Less,
"greaterThan" => BinaryOperator::Greater,
"lessThanEqual" => BinaryOperator::LessEqual,
"greaterThanEqual" => BinaryOperator::GreaterEqual,
"equal" => BinaryOperator::Equal,
"notEqual" => BinaryOperator::NotEqual,
_ => unreachable!(),
});
declaration.overloads.push(module.add_builtin(args, fun))
}
}
// Add common builtins with doubles
_ => inject_common_builtin(declaration, module, name, 8),
}
}
/// Injects the builtins into declaration that can used either float or doubles
fn inject_common_builtin(
declaration: &mut FunctionDeclaration,
module: &mut Module,
name: &str,
float_width: crate::Bytes,
) {
let float_scalar = Scalar {
kind: Sk::Float,
width: float_width,
};
match name {
"ceil" | "round" | "roundEven" | "floor" | "fract" | "trunc" | "sqrt" | "inversesqrt"
| "normalize" | "length" | "isinf" | "isnan" => {
// bits layout
// bit 0 through 1 - dims
for bits in 0..0b100 {
let size = match bits {
0b00 => None,
0b01 => Some(VectorSize::Bi),
0b10 => Some(VectorSize::Tri),
_ => Some(VectorSize::Quad),
};
let args = vec![match size {
Some(size) => TypeInner::Vector {
size,
scalar: float_scalar,
},
None => TypeInner::Scalar(float_scalar),
}];
let fun = match name {
"ceil" => MacroCall::MathFunction(MathFunction::Ceil),
"round" | "roundEven" => MacroCall::MathFunction(MathFunction::Round),
"floor" => MacroCall::MathFunction(MathFunction::Floor),
"fract" => MacroCall::MathFunction(MathFunction::Fract),
"trunc" => MacroCall::MathFunction(MathFunction::Trunc),
"sqrt" => MacroCall::MathFunction(MathFunction::Sqrt),
"inversesqrt" => MacroCall::MathFunction(MathFunction::InverseSqrt),
"normalize" => MacroCall::MathFunction(MathFunction::Normalize),
"length" => MacroCall::MathFunction(MathFunction::Length),
"isinf" => MacroCall::Relational(RelationalFunction::IsInf),
"isnan" => MacroCall::Relational(RelationalFunction::IsNan),
_ => unreachable!(),
};
declaration.overloads.push(module.add_builtin(args, fun))
}
}
"dot" | "reflect" | "distance" | "ldexp" => {
// bits layout
// bit 0 through 1 - dims
for bits in 0..0b100 {
let size = match bits {
0b00 => None,
0b01 => Some(VectorSize::Bi),
0b10 => Some(VectorSize::Tri),
_ => Some(VectorSize::Quad),
};
let ty = |scalar| match size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
};
let fun = match name {
"dot" => MacroCall::MathFunction(MathFunction::Dot),
"reflect" => MacroCall::MathFunction(MathFunction::Reflect),
"distance" => MacroCall::MathFunction(MathFunction::Distance),
"ldexp" => MacroCall::MathFunction(MathFunction::Ldexp),
_ => unreachable!(),
};
let second_scalar = match fun {
MacroCall::MathFunction(MathFunction::Ldexp) => Scalar::I32,
_ => float_scalar,
};
declaration
.overloads
.push(module.add_builtin(vec![ty(float_scalar), ty(second_scalar)], fun))
}
}
"transpose" => {
// bits layout
// bit 0 through 3 - dims
for bits in 0..0b1001 {
let (rows, columns) = match bits {
0b0000 => (VectorSize::Bi, VectorSize::Bi),
0b0001 => (VectorSize::Bi, VectorSize::Tri),
0b0010 => (VectorSize::Bi, VectorSize::Quad),
0b0011 => (VectorSize::Tri, VectorSize::Bi),
0b0100 => (VectorSize::Tri, VectorSize::Tri),
0b0101 => (VectorSize::Tri, VectorSize::Quad),
0b0110 => (VectorSize::Quad, VectorSize::Bi),
0b0111 => (VectorSize::Quad, VectorSize::Tri),
_ => (VectorSize::Quad, VectorSize::Quad),
};
declaration.overloads.push(module.add_builtin(
vec![TypeInner::Matrix {
columns,
rows,
scalar: float_scalar,
}],
MacroCall::MathFunction(MathFunction::Transpose),
))
}
}
"inverse" | "determinant" => {
// bits layout
// bit 0 through 1 - dims
for bits in 0..0b11 {
let (rows, columns) = match bits {
0b00 => (VectorSize::Bi, VectorSize::Bi),
0b01 => (VectorSize::Tri, VectorSize::Tri),
_ => (VectorSize::Quad, VectorSize::Quad),
};
let args = vec![TypeInner::Matrix {
columns,
rows,
scalar: float_scalar,
}];
declaration.overloads.push(module.add_builtin(
args,
MacroCall::MathFunction(match name {
"inverse" => MathFunction::Inverse,
"determinant" => MathFunction::Determinant,
_ => unreachable!(),
}),
))
}
}
"mod" | "step" => {
// bits layout
// bit 0 through 2 - dims
for bits in 0..0b111 {
let (size, second_size) = match bits {
0b000 => (None, None),
0b001 => (Some(VectorSize::Bi), None),
0b010 => (Some(VectorSize::Tri), None),
0b011 => (Some(VectorSize::Quad), None),
0b100 => (Some(VectorSize::Bi), Some(VectorSize::Bi)),
0b101 => (Some(VectorSize::Tri), Some(VectorSize::Tri)),
_ => (Some(VectorSize::Quad), Some(VectorSize::Quad)),
};
let mut args = Vec::with_capacity(2);
let step = name == "step";
for i in 0..2 {
let maybe_size = match i == step as u32 {
true => size,
false => second_size,
};
args.push(match maybe_size {
Some(size) => TypeInner::Vector {
size,
scalar: float_scalar,
},
None => TypeInner::Scalar(float_scalar),
})
}
let fun = match name {
"mod" => MacroCall::Mod(size),
"step" => MacroCall::Splatted(MathFunction::Step, size, 0),
_ => unreachable!(),
};
declaration.overloads.push(module.add_builtin(args, fun))
}
}
// TODO: https://github.com/gfx-rs/naga/issues/2526
// "modf" | "frexp" => { ... }
"cross" => {
let args = vec![
TypeInner::Vector {
size: VectorSize::Tri,
scalar: float_scalar,
},
TypeInner::Vector {
size: VectorSize::Tri,
scalar: float_scalar,
},
];
declaration
.overloads
.push(module.add_builtin(args, MacroCall::MathFunction(MathFunction::Cross)))
}
"outerProduct" => {
// bits layout
// bit 0 through 3 - dims
for bits in 0..0b1001 {
let (size1, size2) = match bits {
0b0000 => (VectorSize::Bi, VectorSize::Bi),
0b0001 => (VectorSize::Bi, VectorSize::Tri),
0b0010 => (VectorSize::Bi, VectorSize::Quad),
0b0011 => (VectorSize::Tri, VectorSize::Bi),
0b0100 => (VectorSize::Tri, VectorSize::Tri),
0b0101 => (VectorSize::Tri, VectorSize::Quad),
0b0110 => (VectorSize::Quad, VectorSize::Bi),
0b0111 => (VectorSize::Quad, VectorSize::Tri),
_ => (VectorSize::Quad, VectorSize::Quad),
};
let args = vec![
TypeInner::Vector {
size: size1,
scalar: float_scalar,
},
TypeInner::Vector {
size: size2,
scalar: float_scalar,
},
];
declaration
.overloads
.push(module.add_builtin(args, MacroCall::MathFunction(MathFunction::Outer)))
}
}
"faceforward" | "fma" => {
// bits layout
// bit 0 through 1 - dims
for bits in 0..0b100 {
let size = match bits {
0b00 => None,
0b01 => Some(VectorSize::Bi),
0b10 => Some(VectorSize::Tri),
_ => Some(VectorSize::Quad),
};
let ty = || match size {
Some(size) => TypeInner::Vector {
size,
scalar: float_scalar,
},
None => TypeInner::Scalar(float_scalar),
};
let args = vec![ty(), ty(), ty()];
let fun = match name {
"faceforward" => MacroCall::MathFunction(MathFunction::FaceForward),
"fma" => MacroCall::MathFunction(MathFunction::Fma),
_ => unreachable!(),
};
declaration.overloads.push(module.add_builtin(args, fun))
}
}
"refract" => {
// bits layout
// bit 0 through 1 - dims
for bits in 0..0b100 {
let size = match bits {
0b00 => None,
0b01 => Some(VectorSize::Bi),
0b10 => Some(VectorSize::Tri),
_ => Some(VectorSize::Quad),
};
let ty = || match size {
Some(size) => TypeInner::Vector {
size,
scalar: float_scalar,
},
None => TypeInner::Scalar(float_scalar),
};
let args = vec![ty(), ty(), TypeInner::Scalar(Scalar::F32)];
declaration
.overloads
.push(module.add_builtin(args, MacroCall::MathFunction(MathFunction::Refract)))
}
}
"smoothstep" => {
// bit 0 - splatted
// bit 1 through 2 - dims
for bits in 0..0b1000 {
let splatted = bits & 0b1 == 0b1;
let size = match bits >> 1 {
0b00 => None,
0b01 => Some(VectorSize::Bi),
0b10 => Some(VectorSize::Tri),
_ => Some(VectorSize::Quad),
};
if splatted && size.is_none() {
continue;
}
let base_ty = || match size {
Some(size) => TypeInner::Vector {
size,
scalar: float_scalar,
},
None => TypeInner::Scalar(float_scalar),
};
let ty = || match splatted {
true => TypeInner::Scalar(float_scalar),
false => base_ty(),
};
declaration.overloads.push(module.add_builtin(
vec![ty(), ty(), base_ty()],
MacroCall::SmoothStep { splatted: size },
))
}
}
// The function isn't a builtin or we don't yet support it
_ => {}
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum TextureLevelType {
None,
Lod,
Grad,
}
/// A compiler defined builtin function
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum MacroCall {
Sampler,
SamplerShadow,
Texture {
proj: bool,
offset: bool,
shadow: bool,
level_type: TextureLevelType,
},
TextureSize {
arrayed: bool,
},
TextureQueryLevels,
ImageLoad {
multi: bool,
},
ImageStore,
MathFunction(MathFunction),
FindLsbUint,
FindMsbUint,
BitfieldExtract,
BitfieldInsert,
Relational(RelationalFunction),
Unary(UnaryOperator),
Binary(BinaryOperator),
Mod(Option<VectorSize>),
Splatted(MathFunction, Option<VectorSize>, usize),
MixBoolean,
Clamp(Option<VectorSize>),
BitCast(Sk),
Derivate(Axis, Ctrl),
Barrier,
/// SmoothStep needs a separate variant because it might need it's inputs
/// to be splatted depending on the overload
SmoothStep {
/// The size of the splat operation if some
splatted: Option<VectorSize>,
},
}
impl MacroCall {
/// Adds the necessary expressions and statements to the passed body and
/// finally returns the final expression with the correct result
pub fn call(
&self,
frontend: &mut Frontend,
ctx: &mut Context,
args: &mut [Handle<Expression>],
meta: Span,
) -> Result<Option<Handle<Expression>>> {
Ok(Some(match *self {
MacroCall::Sampler => {
ctx.samplers.insert(args[0], args[1]);
args[0]
}
MacroCall::SamplerShadow => {
sampled_to_depth(ctx, args[0], meta, &mut frontend.errors);
ctx.invalidate_expression(args[0], meta)?;
ctx.samplers.insert(args[0], args[1]);
args[0]
}
MacroCall::Texture {
proj,
offset,
shadow,
level_type,
} => {
let mut coords = args[1];
if proj {
let size = match *ctx.resolve_type(coords, meta)? {
TypeInner::Vector { size, .. } => size,
_ => unreachable!(),
};
let mut right = ctx.add_expression(
Expression::AccessIndex {
base: coords,
index: size as u32 - 1,
},
Span::default(),
)?;
let left = if let VectorSize::Bi = size {
ctx.add_expression(
Expression::AccessIndex {
base: coords,
index: 0,
},
Span::default(),
)?
} else {
let size = match size {
VectorSize::Tri => VectorSize::Bi,
_ => VectorSize::Tri,
};
right = ctx.add_expression(
Expression::Splat { size, value: right },
Span::default(),
)?;
ctx.vector_resize(size, coords, Span::default())?
};
coords = ctx.add_expression(
Expression::Binary {
op: BinaryOperator::Divide,
left,
right,
},
Span::default(),
)?;
}
let extra = args.get(2).copied();
let comps = frontend.coordinate_components(ctx, args[0], coords, extra, meta)?;
let mut num_args = 2;
if comps.used_extra {
num_args += 1;
};
// Parse out explicit texture level.
let mut level = match level_type {
TextureLevelType::None => SampleLevel::Auto,
TextureLevelType::Lod => {
num_args += 1;
if shadow {
log::warn!("Assuming LOD {:?} is zero", args[2],);
--> --------------------
--> maximum size reached
--> --------------------
[ zur Elbe Produktseite wechseln0.58Quellennavigators
Analyse erneut starten
]
|
2026-04-04
|