glsl-in: inject samplerCubeArray builtins on use

Arrayed cube images require a special capabilities in some backends, so
like how we already do with doubles, we now only inject them if a call
uses one as an argument.

This required some refractoring on the builtins handling of variations
but now the interface is cleaner and adding new variations (if needed)
should be easier.
This commit is contained in:
João Capucho 2022-02-18 22:37:31 +00:00 committed by Dzmitry Malyshau
parent 07f9cf670c
commit 66d59eb7ac
3 changed files with 233 additions and 163 deletions

View File

@ -62,14 +62,26 @@ pub struct Overload {
pub void: bool,
}
bitflags::bitflags! {
/// Tracks the variations of the builtin already generated, this is needed because some
/// builtins overloads can't be generated unless explicitly used, since they might cause
/// uneeded capabilities to be requested
#[derive(Default)]
pub struct BuiltinVariations: u32 {
/// Request the standard overloads
const STANDARD = 1 << 0;
/// Request overloads that use the double type
const DOUBLE = 1 << 1;
/// Request overloads that use samplerCubeArray(Shadow)
const CUBE_TEXTURES_ARRAY = 1 << 2;
}
}
#[derive(Debug, Default)]
pub struct FunctionDeclaration {
pub overloads: Vec<Overload>,
/// Whether or not this function has the name of a builtin.
pub builtin: bool,
/// If [`builtin`](Self::builtin) is true, this field indicates whether
/// this function already has double overloads added or not. Otherwise, it is unused.
pub double: bool,
/// Tracks the builtin overload variations that were already generated
pub variations: BuiltinVariations,
}
#[derive(Debug)]

View File

@ -1,11 +1,14 @@
use super::{
ast::{FunctionDeclaration, FunctionKind, Overload, ParameterInfo, ParameterQualifier},
ast::{
BuiltinVariations, FunctionDeclaration, FunctionKind, Overload, ParameterInfo,
ParameterQualifier,
},
context::Context,
Error, ErrorKind, Parser, Result,
};
use crate::{
BinaryOperator, Block, Constant, DerivativeAxis, Expression, Handle, ImageClass,
ImageDimension, ImageQuery, MathFunction, Module, RelationalFunction, SampleLevel,
ImageDimension as Dim, ImageQuery, MathFunction, Module, RelationalFunction, SampleLevel,
ScalarKind as Sk, Span, Type, TypeInner, VectorSize,
};
@ -67,85 +70,30 @@ fn make_coords_arg(number_of_components: usize, kind: Sk) -> TypeInner {
}
}
/// Inject builtins into
/// 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.
///
/// This version does not add builtins with arguments using the double type
/// [`inject_double_builtin`](inject_double_builtin) for builtins
/// using the double type
pub fn inject_builtin(declaration: &mut FunctionDeclaration, module: &mut Module, name: &str) {
use crate::ImageDimension as Dim;
pub fn inject_builtin(
declaration: &mut FunctionDeclaration,
module: &mut Module,
name: &str,
mut variations: BuiltinVariations,
) {
// 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)
}
declaration.builtin = true;
let width = 4;
match name {
"sampler1D" | "sampler1DArray" | "sampler2D" | "sampler2DArray" | "sampler2DMS"
| "sampler2DMSArray" | "sampler3D" | "samplerCube" | "samplerCubeArray" => {
declaration.overloads.push(module.add_builtin(
vec![
TypeInner::Image {
dim: match name {
"sampler1D" | "sampler1DArray" => Dim::D1,
"sampler2D" | "sampler2DArray" | "sampler2DMS" | "sampler2DMSArray" => {
Dim::D2
}
"sampler3D" => Dim::D3,
_ => Dim::Cube,
},
arrayed: matches!(
name,
"sampler1DArray"
| "sampler2DArray"
| "sampler2DMSArray"
| "samplerCubeArray"
),
class: ImageClass::Sampled {
kind: Sk::Float,
multi: matches!(name, "sampler2DMS" | "sampler2DMSArray"),
},
},
TypeInner::Sampler { comparison: false },
],
MacroCall::Sampler,
))
}
"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,
))
}
}
"texture"
| "textureGrad"
| "textureGradOffset"
@ -273,7 +221,7 @@ pub fn inject_builtin(declaration: &mut FunctionDeclaration, module: &mut Module
if lod || grad || offset || proj || bias {
continue;
}
debug_assert!(dim == ImageDimension::Cube && shadow && arrayed);
debug_assert!(dim == Dim::Cube && shadow && arrayed);
}
debug_assert!(num_coords <= 5);
@ -318,7 +266,7 @@ pub fn inject_builtin(declaration: &mut FunctionDeclaration, module: &mut Module
}
};
texture_args_generator(TextureArgsOptions::SHADOW, f)
texture_args_generator(TextureArgsOptions::SHADOW | variations.into(), f)
}
"textureSize" => {
let f = |kind, dim, arrayed, multi, shadow| {
@ -347,7 +295,10 @@ pub fn inject_builtin(declaration: &mut FunctionDeclaration, module: &mut Module
.push(module.add_builtin(args, MacroCall::TextureSize { arrayed }))
};
texture_args_generator(TextureArgsOptions::all(), f)
texture_args_generator(
TextureArgsOptions::SHADOW | TextureArgsOptions::MULTI | variations.into(),
f,
)
}
"texelFetch" => {
let f = |kind, dim, arrayed, multi, _shadow| {
@ -380,7 +331,7 @@ pub fn inject_builtin(declaration: &mut FunctionDeclaration, module: &mut Module
};
// Don't generate shadow images since they aren't supported
texture_args_generator(TextureArgsOptions::MULTI, f)
texture_args_generator(TextureArgsOptions::MULTI | variations.into(), f)
}
"imageSize" => {
let f = |kind: Sk, dim, arrayed, _, _| {
@ -404,7 +355,7 @@ pub fn inject_builtin(declaration: &mut FunctionDeclaration, module: &mut Module
.push(module.add_builtin(vec![image], MacroCall::TextureSize { arrayed }))
};
texture_args_generator(TextureArgsOptions::empty(), f)
texture_args_generator(variations.into(), f)
}
"imageLoad" => {
let f = |kind: Sk, dim, arrayed, _, _| {
@ -443,7 +394,7 @@ pub fn inject_builtin(declaration: &mut FunctionDeclaration, module: &mut Module
};
// Don't generate shadow nor multisampled images since they aren't supported
texture_args_generator(TextureArgsOptions::empty(), f)
texture_args_generator(variations.into(), f)
}
"imageStore" => {
let f = |kind: Sk, dim, arrayed, _, _| {
@ -490,7 +441,84 @@ pub fn inject_builtin(declaration: &mut FunctionDeclaration, module: &mut Module
};
// Don't generate shadow nor multisampled images since they aren't supported
texture_args_generator(TextureArgsOptions::empty(), f)
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,
) {
let width = 4;
match name {
"sampler1D" | "sampler1DArray" | "sampler2D" | "sampler2DArray" | "sampler2DMS"
| "sampler2DMSArray" | "sampler3D" | "samplerCube" | "samplerCubeArray" => {
declaration.overloads.push(module.add_builtin(
vec![
TypeInner::Image {
dim: match name {
"sampler1D" | "sampler1DArray" => Dim::D1,
"sampler2D" | "sampler2DArray" | "sampler2DMS" | "sampler2DMSArray" => {
Dim::D2
}
"sampler3D" => Dim::D3,
_ => Dim::Cube,
},
arrayed: matches!(
name,
"sampler1DArray"
| "sampler2DArray"
| "sampler2DMSArray"
| "samplerCubeArray"
),
class: ImageClass::Sampled {
kind: Sk::Float,
multi: matches!(name, "sampler2DMS" | "sampler2DMSArray"),
},
},
TypeInner::Sampler { comparison: false },
],
MacroCall::Sampler,
))
}
"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"
@ -961,13 +989,8 @@ pub fn inject_builtin(declaration: &mut FunctionDeclaration, module: &mut Module
}
}
/// Double version of [`inject_builtin`](inject_builtin)
pub fn inject_double_builtin(
declaration: &mut FunctionDeclaration,
module: &mut Module,
name: &str,
) {
declaration.double = true;
/// Injects the builtins into declaration that need doubles
fn inject_double_builtin(declaration: &mut FunctionDeclaration, module: &mut Module, name: &str) {
let width = 8;
match name {
"abs" | "sign" => {
@ -1114,6 +1137,7 @@ pub fn inject_double_builtin(
}
}
/// Injects the builtins into declaration that can used either float or doubles
fn inject_common_builtin(
declaration: &mut FunctionDeclaration,
module: &mut Module,
@ -1512,7 +1536,7 @@ fn inject_common_builtin(
}
}
// The function isn't a builtin or we don't yet support it
_ => declaration.builtin = false,
_ => {}
}
}
@ -2028,10 +2052,10 @@ impl Parser {
} = *self.resolve_type(ctx, image, meta)?
{
let image_size = match dim {
ImageDimension::D1 => None,
ImageDimension::D2 => Some(VectorSize::Bi),
ImageDimension::D3 => Some(VectorSize::Tri),
ImageDimension::Cube => Some(VectorSize::Tri),
Dim::D1 => None,
Dim::D2 => Some(VectorSize::Bi),
Dim::D3 => Some(VectorSize::Tri),
Dim::Cube => Some(VectorSize::Tri),
};
let coord_size = match *self.resolve_type(ctx, coord, meta)? {
TypeInner::Vector { size, .. } => Some(size),
@ -2060,7 +2084,7 @@ impl Parser {
let mut coord_index = image_size.map_or(1, |s| s as u32);
let array_index = if arrayed && !(storage && dim == ImageDimension::Cube) {
let array_index = if arrayed && !(storage && dim == Dim::Cube) {
let index = coord_index;
coord_index += 1;
@ -2174,6 +2198,23 @@ bitflags::bitflags! {
const MULTI = 1 << 0;
/// Generates shadow variants of images
const SHADOW = 1 << 1;
/// Generates standard images
const STANDARD = 1 << 2;
/// Generates cube arrayed images
const CUBE_ARRAY = 1 << 3;
}
}
impl From<BuiltinVariations> for TextureArgsOptions {
fn from(variations: BuiltinVariations) -> Self {
let mut options = TextureArgsOptions::empty();
if variations.contains(BuiltinVariations::STANDARD) {
options |= TextureArgsOptions::STANDARD
}
if variations.contains(BuiltinVariations::CUBE_TEXTURES_ARRAY) {
options |= TextureArgsOptions::CUBE_ARRAY
}
options
}
}
@ -2188,13 +2229,19 @@ bitflags::bitflags! {
/// see the struct documentation
fn texture_args_generator(
options: TextureArgsOptions,
mut f: impl FnMut(crate::ScalarKind, ImageDimension, bool, bool, bool),
mut f: impl FnMut(crate::ScalarKind, Dim, bool, bool, bool),
) {
use crate::ImageDimension as Dim;
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;
}
} else if !options.contains(TextureArgsOptions::STANDARD) {
continue;
}
f(kind, dim, arrayed, false, false);
// 3D images can't be neither arrayed nor shadow
@ -2220,10 +2267,10 @@ fn texture_args_generator(
/// 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)
fn image_dims_to_coords_size(dim: ImageDimension) -> usize {
fn image_dims_to_coords_size(dim: Dim) -> usize {
match dim {
ImageDimension::D1 => 1,
ImageDimension::D2 => 2,
Dim::D1 => 1,
Dim::D2 => 2,
_ => 3,
}
}

View File

@ -1,6 +1,6 @@
use super::{
ast::*,
builtins::{inject_builtin, inject_double_builtin, sampled_to_depth},
builtins::{inject_builtin, sampled_to_depth},
context::{Context, ExprPos, StmtContext},
error::{Error, ErrorKind},
types::scalar_components,
@ -602,26 +602,26 @@ impl Parser {
raw_args: &[Handle<HirExpr>],
meta: Span,
) -> Result<Option<Handle<Expression>>> {
// If the name for the function hasn't yet been initialized check if any
// builtin can be injected.
if self.lookup_function.get(&name).is_none() {
let declaration = self.lookup_function.entry(name.clone()).or_default();
inject_builtin(declaration, &mut self.module, &name);
// Grow the typifier to be able to index it later without needing
// to hold the context mutably
for &(expr, span) in args.iter() {
self.typifier_grow(ctx, expr, span)?;
}
// Check if any argument uses a double type
let has_double = args
.iter()
.any(|&(expr, meta)| self.resolve_type(ctx, expr, meta).map_or(false, is_double));
// Check if the passed arguments require any special variations
let mut variations = builtin_required_variations(
args.iter()
.map(|&(expr, _)| ctx.typifier.get(expr, &self.module.types)),
);
// At this point a declaration is guaranteed
let declaration = self.lookup_function.get_mut(&name).unwrap();
// Initiate the declaration if it wasn't previously initialized and inject builtins
let declaration = self.lookup_function.entry(name.clone()).or_insert_with(|| {
variations |= BuiltinVariations::STANDARD;
Default::default()
});
inject_builtin(declaration, &mut self.module, &name, variations);
if declaration.builtin && !declaration.double && has_double {
inject_double_builtin(declaration, &mut self.module, &name);
}
// Borrow again but without mutability
// Borrow again but without mutability, at this point a declaration is guaranteed
let declaration = self.lookup_function.get(&name).unwrap();
// Possibly contains the overload to be used in the call
@ -1026,11 +1026,6 @@ impl Parser {
mut body: Block,
meta: Span,
) {
if self.lookup_function.get(&name).is_none() {
let declaration = self.lookup_function.entry(name.clone()).or_default();
inject_builtin(declaration, &mut self.module, &name);
}
ensure_block_returns(&mut body);
let void = result.is_none();
@ -1041,7 +1036,16 @@ impl Parser {
..
} = self;
let declaration = lookup_function.entry(name.clone()).or_default();
// Check if the passed arguments require any special variations
let mut variations =
builtin_required_variations(ctx.parameters.iter().map(|&arg| &module.types[arg].inner));
// Initiate the declaration if it wasn't previously initialized and inject builtins
let declaration = lookup_function.entry(name.clone()).or_insert_with(|| {
variations |= BuiltinVariations::STANDARD;
Default::default()
});
inject_builtin(declaration, module, &name, variations);
let Context {
expressions,
@ -1052,15 +1056,6 @@ impl Parser {
..
} = ctx;
if declaration.builtin
&& !declaration.double
&& parameters
.iter()
.any(|ty| is_double(&module.types[*ty].inner))
{
inject_double_builtin(declaration, module, &name);
}
let function = Function {
name: Some(name),
arguments,
@ -1122,11 +1117,6 @@ impl Parser {
result: Option<FunctionResult>,
meta: Span,
) {
if self.lookup_function.get(&name).is_none() {
let declaration = self.lookup_function.entry(name.clone()).or_default();
inject_builtin(declaration, &mut self.module, &name);
}
let void = result.is_none();
let &mut Parser {
@ -1135,7 +1125,16 @@ impl Parser {
..
} = self;
let declaration = lookup_function.entry(name.clone()).or_default();
// Check if the passed arguments require any special variations
let mut variations =
builtin_required_variations(ctx.parameters.iter().map(|&arg| &module.types[arg].inner));
// Initiate the declaration if it wasn't previously initialized and inject builtins
let declaration = lookup_function.entry(name.clone()).or_insert_with(|| {
variations |= BuiltinVariations::STANDARD;
Default::default()
});
inject_builtin(declaration, module, &name, variations);
let Context {
arguments,
@ -1144,15 +1143,6 @@ impl Parser {
..
} = ctx;
if declaration.builtin
&& !declaration.double
&& parameters
.iter()
.any(|ty| is_double(&module.types[*ty].inner))
{
inject_double_builtin(declaration, module, &name);
}
let function = Function {
name: Some(name),
arguments,
@ -1404,16 +1394,6 @@ impl Parser {
}
}
fn is_double(ty: &TypeInner) -> bool {
match *ty {
TypeInner::ValuePointer { kind, width, .. }
| TypeInner::Scalar { kind, width }
| TypeInner::Vector { kind, width, .. } => kind == ScalarKind::Float && width == 8,
TypeInner::Matrix { width, .. } => width == 8,
_ => false,
}
}
/// Helper enum containing the type of conversion need for a call
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
enum Conversion {
@ -1498,3 +1478,34 @@ fn conversion(target: &TypeInner, source: &TypeInner) -> Option<Conversion> {
},
)
}
/// Helper method returning all the non standard builtin variations needed
/// to process the function call with the passed arguments
fn builtin_required_variations<'a>(args: impl Iterator<Item = &'a TypeInner>) -> BuiltinVariations {
let mut variations = BuiltinVariations::empty();
for ty in args {
match *ty {
TypeInner::ValuePointer { kind, width, .. }
| TypeInner::Scalar { kind, width }
| TypeInner::Vector { kind, width, .. } => {
if kind == ScalarKind::Float && width == 8 {
variations |= BuiltinVariations::DOUBLE
}
}
TypeInner::Matrix { width, .. } => {
if width == 8 {
variations |= BuiltinVariations::DOUBLE
}
}
TypeInner::Image { dim, arrayed, .. } => {
if dim == crate::ImageDimension::Cube && arrayed {
variations |= BuiltinVariations::CUBE_TEXTURES_ARRAY
}
}
_ => {}
}
}
variations
}