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); letmut num_coords = num_coords_from_dim;
if shadow && proj {
num_coords = 4;
} elseif dim == Dim::D1 && shadow {
num_coords = 3;
} elseif shadow {
num_coords += 1;
} elseif proj { if variant && num_coords == 4 { // Normal form already has 4 components, no need to have a variant form. continue;
} elseif 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); letmut args = vec![image, vector];
if num_coords == 5 {
args.push(TypeInner::Scalar(Scalar::F32));
}
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;
}
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 dim_value = image_dims_to_coords_size(dim); letmut 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);
// 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 dim_value = image_dims_to_coords_size(dim); letmut 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);
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 },
},
};
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),
};
letmut 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!(),
};
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),
};
letmut 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!(),
};
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()];
/// 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 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),
};
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
_ => {}
}
}
if shadow {
log::debug!( "Assuming gradients {:?} and {:?} are not greater than 1",
args[2],
args[3],
);
SampleLevel::Zero
} else {
SampleLevel::Gradient {
x: args[2],
y: args[3],
}
}
}
};
let texture_offset = match offset { true => { let offset_arg = args[num_args];
num_args += 1;
Some(offset_arg)
} false => None,
};
// Now go back and look for optional bias arg (if available) iflet TextureLevelType::None = level_type {
level = args
.get(num_args)
.copied()
.map_or(SampleLevel::Auto, SampleLevel::Bias);
}
if arrayed { letmut components = Vec::with_capacity(4);
let size = match *ctx.resolve_type(expr, meta)? {
TypeInner::Vector { size: ori_size, .. } => {
for index in 0..(ori_size as u32) {
components.push(ctx.add_expression(
Expression::AccessIndex { base: expr, index },
Span::default(),
)?)
}
/// Helper struct for texture calls with the separate components from the vector argument /// /// Obtained by calling [`coordinate_components`](Frontend::coordinate_components) #[derive(Debug)] struct CoordComponents {
coordinate: Handle<Expression>,
depth_ref: Option<Handle<Expression>>,
array_index: Option<Handle<Expression>>,
used_extra: bool,
}
impl Frontend { /// Helper function for texture calls, splits the vector argument into it's components
fn coordinate_components(
&mutself,
ctx: &mut Context,
image: Handle<Expression>,
coord: Handle<Expression>,
extra: Option<Handle<Expression>>,
meta: Span,
) -> Result<CoordComponents> { iflet TypeInner::Image {
dim,
arrayed,
class,
} = *ctx.resolve_type(image, meta)?
{ let image_size = match dim {
Dim::D1 => None,
Dim::D2 => Some(VectorSize::Bi),
Dim::D3 => Some(VectorSize::Tri),
Dim::Cube => Some(VectorSize::Tri),
}; let coord_size = match *ctx.resolve_type(coord, meta)? {
TypeInner::Vector { size, .. } => Some(size),
_ => None,
}; let (shadow, storage) = match class {
ImageClass::Depth { .. } => (true, false),
ImageClass::Storage { .. } => (false, true),
ImageClass::Sampled { .. } => (false, false),
ImageClass::External => unreachable!(),
};
/// Helper function to cast a expression holding a sampled image to a /// depth image. pub fn sampled_to_depth(
ctx: &mut Context,
image: Handle<Expression>,
meta: Span,
errors: &mut Vec<Error>,
) { // Get the a mutable type handle of the underlying image storage let ty = match ctx[image] {
Expression::GlobalVariable(handle) => &mut ctx.module.global_variables.get_mut(handle).ty,
Expression::FunctionArgument(i) => { // Mark the function argument as carrying a depth texture
ctx.parameters_info[i as usize].depth = true; // NOTE: We need to later also change the parameter type
&mut ctx.arguments[i as usize].ty
}
_ => { // Only globals and function arguments are allowed to carry an image return errors.push(Error {
kind: ErrorKind::SemanticError("Not a valid texture expression".into()),
meta,
});
}
};
match ctx.module.types[*ty].inner { // Update the image class to depth in case it already isn't
TypeInner::Image {
class,
dim,
arrayed,
} => match class {
ImageClass::Sampled { multi, .. } => {
*ty = ctx.module.types.insert( Type {
name: None,
inner: TypeInner::Image {
dim,
arrayed,
class: ImageClass::Depth { multi },
},
},
Span::default(),
)
}
ImageClass::Depth { .. } => {} // Other image classes aren't allowed to be transformed to depth
ImageClass::Storage { .. } => errors.push(Error {
kind: ErrorKind::SemanticError("Not a texture".into()),
meta,
}),
ImageClass::External => unreachable!(),
},
_ => errors.push(Error {
kind: ErrorKind::SemanticError("Not a texture".into()),
meta,
}),
};
// Copy the handle to allow borrowing the `ctx` again let ty = *ty;
// If the image was passed through a function argument we also need to change // the corresponding parameter iflet Expression::FunctionArgument(i) = ctx[image] {
ctx.parameters[i as usize] = ty;
}
}
impl From<BuiltinVariations> for TextureArgsOptions {
fn from(variations: BuiltinVariations) -> Self { letmut options = TextureArgsOptions::empty(); if variations.contains(BuiltinVariations::STANDARD) {
options |= TextureArgsOptions::STANDARD
} if variations.contains(BuiltinVariations::CUBE_TEXTURES_ARRAY) {
options |= TextureArgsOptions::CUBE_ARRAY
} if variations.contains(BuiltinVariations::D2_MULTI_TEXTURES_ARRAY) {
options |= TextureArgsOptions::D2_MULTI_ARRAY
}
options
}
}
/// Helper function to generate the image components for texture/image builtins /// /// Calls the passed function `f` with: /// ```text /// f(ScalarKind, ImageDimension, arrayed, multi, shadow) /// ``` /// /// `options` controls extra image variants generation like multisampling and depth, /// see the struct documentation
fn texture_args_generator(
options: TextureArgsOptions, mut f: impl FnMut(crate::ScalarKind, Dim, bool, bool, bool),
) {
for kind in [Sk::Float, Sk::Uint, Sk::Sint].iter().copied() {
for dim in [Dim::D1, Dim::D2, Dim::D3, Dim::Cube].iter().copied() {
for arrayed in [false, true].iter().copied() { if dim == Dim::Cube && arrayed { if !options.contains(TextureArgsOptions::CUBE_ARRAY) { continue;
}
} elseif Dim::D2 == dim
&& options.contains(TextureArgsOptions::MULTI)
&& arrayed
&& options.contains(TextureArgsOptions::D2_MULTI_ARRAY)
{ // multisampling for sampler2DMSArray
f(kind, dim, arrayed, true, false);
} elseif !options.contains(TextureArgsOptions::STANDARD) { continue;
}
f(kind, dim, arrayed, false, false);
// 3D images can't be neither arrayed nor shadow // so we break out early, this way arrayed will always // be false and we won't hit the shadow branch iflet Dim::D3 = dim { break;
}
if Dim::D2 == dim && options.contains(TextureArgsOptions::MULTI) && !arrayed { // multisampling
f(kind, dim, arrayed, true, false);
}
/// Helper functions used to convert from a image dimension into a integer representing the /// number of components needed for the coordinates vector (1 means scalar instead of vector) const fn image_dims_to_coords_size(dim: Dim) -> usize { match dim {
Dim::D1 => 1,
Dim::D2 => 2,
_ => 3,
}
}
Messung V0.5 in Prozent
¤ 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.0.48Bemerkung:
(vorverarbeitet am 2026-10-12)
¤
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.