Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 2 additions & 93 deletions naga/src/front/glsl/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,89 +464,10 @@ fn inject_standard_builtins(
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;
}
_ => (),
}
}
// `sampler2D(tex, samp)` constructors are handled in constructor_many() — the lexer
// emits CombinedSamplerTypeName, so they never arrive here as function identifiers.

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"
Expand Down Expand Up @@ -1543,8 +1464,6 @@ pub enum TextureLevelType {
/// A compiler defined builtin function
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum MacroCall {
Sampler,
SamplerShadow,
Texture {
proj: bool,
offset: bool,
Expand Down Expand Up @@ -1593,16 +1512,6 @@ impl MacroCall {
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,
Expand Down
29 changes: 29 additions & 0 deletions naga/src/front/glsl/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,35 @@ impl<'a> Context<'a> {
for &(ref name, lookup) in frontend.global_variables.iter() {
this.add_global(name, lookup)?
}

// For each combined image-sampler uniform (e.g. `sampler2D u_tex`), add an
// entry to `ctx.samplers` mapping the image expression to its implicit
// companion sampler expression. This enables `texture(u_tex, uv)` to be
// lowered to `textureSample(u_tex, u_tex_sampler, uv)` without requiring the
// caller to write `texture(sampler2D(u_tex, u_samp), uv)`.
//
// The image global's expression was appended by the `add_global` loop above.
// The implicit sampler global was NOT added to `frontend.global_variables` (it
// is anonymous from the GLSL source's perspective), so we add its expression
// here and immediately map it.
for &(image_handle, sampler_handle) in frontend.combined_samplers.iter() {
// Locate the expression that was already created for the image global.
let maybe_image_expr = this
.expressions
.iter()
.find(|&(_, expr)| *expr == Expression::GlobalVariable(image_handle))
.map(|(h, _)| h);

if let Some(image_expr) = maybe_image_expr {
let span = this.module.global_variables.get_span(sampler_handle);
// The implicit sampler global was not added to frontend.global_variables,
// so we add its expression here and populate the samplers map.
let sampler_expr =
this.add_expression(Expression::GlobalVariable(sampler_handle), span)?;
this.samplers.insert(image_expr, sampler_expr);
}
}

this.is_const = is_const;

Ok(this)
Expand Down
20 changes: 20 additions & 0 deletions naga/src/front/glsl/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,26 @@ impl Frontend {
.map(|member| scalar_components(&ctx.module.types[member.ty].inner))
.collect::<Vec<_>>(),
),
TypeInner::Image {
class: crate::ImageClass::Sampled { .. },
..
} if args.len() == 2 => {
let (image, _) = args[0];
let (sampler, _) = args[1];
ctx.samplers.insert(image, sampler);
return Ok(image);
}
TypeInner::Image {
class: crate::ImageClass::Depth { .. },
..
} if args.len() == 2 => {
let (image, image_meta) = args[0];
let (sampler, _) = args[1];
sampled_to_depth(ctx, image, image_meta, &mut self.errors);
ctx.invalidate_expression(image, image_meta)?;
ctx.samplers.insert(image, sampler);
return Ok(image);
}
_ => {
return Err(Error {
kind: ErrorKind::SemanticError("Constructor: Too many arguments".into()),
Expand Down
5 changes: 4 additions & 1 deletion naga/src/front/glsl/lex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use pp_rs::{
use super::{
ast::Precision,
token::{Directive, DirectiveKind, Token, TokenValue},
types::parse_type,
types::{is_combined_sampler, parse_type},
};
use crate::{FastHashMap, Span, StorageAccess};

Expand Down Expand Up @@ -112,6 +112,9 @@ impl Iterator for Lexer<'_> {
"void" => TokenValue::Void,
"struct" => TokenValue::Struct,
word => match parse_type(word) {
Some(t) if is_combined_sampler(word) => {
TokenValue::CombinedSamplerTypeName(t)
}
Some(t) => TokenValue::TypeName(t),
None => TokenValue::Identifier(String::from(word)),
},
Expand Down
14 changes: 13 additions & 1 deletion naga/src/front/glsl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ pub use token::TokenValue;

use alloc::{string::String, vec::Vec};

use crate::{proc::Layouter, FastHashMap, FastHashSet, Handle, Module, ShaderStage, Span, Type};
use crate::{
proc::Layouter, FastHashMap, FastHashSet, GlobalVariable, Handle, Module, ShaderStage, Span,
Type,
};
use ast::{EntryArg, FunctionDeclaration, GlobalLookup};
use parser::ParsingContext;

Expand Down Expand Up @@ -174,6 +177,14 @@ pub struct Frontend {

entry_args: Vec<EntryArg>,

/// Maps each combined-sampler image global (e.g. from `sampler2D` / `sampler2DShadow`
/// uniforms) to an implicit paired sampler global that was synthesized for it.
///
/// When `texture(u_tex, uv)` is called with a combined-sampler uniform, the texture
/// call machinery looks up `ctx.samplers[image_expr]` to find the WGSL sampler.
/// These pairs get translated into `ctx.samplers` entries in `Context::new`.
pub(crate) combined_samplers: Vec<(Handle<GlobalVariable>, Handle<GlobalVariable>)>,

layouter: Layouter,

errors: Vec<Error>,
Expand All @@ -187,6 +198,7 @@ impl Frontend {
self.lookup_type.clear();
self.global_variables.clear();
self.entry_args.clear();
self.combined_samplers.clear();
self.layouter.clear();
}

Expand Down
64 changes: 64 additions & 0 deletions naga/src/front/glsl/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,66 @@ impl<'source> ParsingContext<'source> {
self.parse_external_declaration(frontend, &mut ctx)?;
}

// Fix up bindings for all implicit samplers that were synthesised for combined
// image-sampler uniforms (`sampler2D`, `sampler2DShadow`, …). The sampler
// globals were initially created with the same binding as their paired image,
// which would cause `BindingCollision` validation errors. Now that all
// declarations have been processed we can see the full binding landscape and
// assign each implicit sampler the next free slot on its descriptor set.
//
// We do this in a single pass: for each set we collect the highest binding
// currently allocated, then hand out sequential slots for the samplers.
if !frontend.combined_samplers.is_empty() {
// Collect the set of implicit sampler handles so we can exclude them
// from the "already allocated" scan.
let implicit_sampler_handles: alloc::collections::BTreeSet<_> =
frontend.combined_samplers.iter().map(|&(_, s)| s).collect();

// Build a map: group -> (one past the highest binding currently in use),
// counting only the non-implicit globals.
let mut group_next_binding: crate::FastHashMap<u32, u32> =
crate::FastHashMap::default();
for (handle, var) in ctx.module.global_variables.iter() {
if implicit_sampler_handles.contains(&handle) {
continue;
}
if let Some(ref rb) = var.binding {
let entry = group_next_binding.entry(rb.group).or_insert(0);
if rb.binding + 1 > *entry {
*entry = rb.binding + 1;
}
}
}

// Assign the implicit samplers non-conflicting bindings in the same group
// as their paired image, after all the declared bindings.
for &(image_handle, sampler_handle) in frontend.combined_samplers.iter() {
let group = ctx
.module
.global_variables
.get_mut(image_handle)
.binding
.as_ref()
.map(|rb| rb.group)
.unwrap_or(0);

let next = group_next_binding.entry(group).or_insert(0);
if let Some(ref mut rb) =
ctx.module.global_variables.get_mut(sampler_handle).binding
{
rb.group = group;
rb.binding = *next;
} else {
ctx.module.global_variables.get_mut(sampler_handle).binding =
Some(crate::ResourceBinding {
group,
binding: *next,
});
}
*next += 1;
}
}

// Add an `EntryPoint` to `parser.module` for `main`, if a
// suitable overload exists. Error out if we can't find one.
if let Some(declaration) = frontend.lookup_function.get("main") {
Expand Down Expand Up @@ -411,6 +471,9 @@ pub struct DeclarationContext<'ctx, 'qualifiers, 'a> {
external: bool,
is_inside_loop: bool,
ctx: &'ctx mut Context<'a>,
/// `true` when the current declaration's type was a combined image-sampler type
/// name in GLSL source (`sampler2D`, `isampler3D`, `sampler2DShadow`, …).
is_combined_sampler: bool,
}

impl DeclarationContext<'_, '_, '_> {
Expand All @@ -428,6 +491,7 @@ impl DeclarationContext<'_, '_, '_> {
name: Some(name),
init,
meta,
is_combined_sampler: self.is_combined_sampler,
};

match self.external {
Expand Down
12 changes: 11 additions & 1 deletion naga/src/front/glsl/parser/declarations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,15 @@ impl ParsingContext<'_> {

if self.peek_type_name(frontend) {
// This branch handles variables and function prototypes and if
// external is true also function definitions
// external is true also function definitions.
//
// Check whether the upcoming token is a combined image-sampler type
// name (`sampler2D`, `isampler3D`, `sampler2DShadow`, …) before
// consuming it — after `parse_type` the token has been consumed and
// the flag cannot be recovered from the produced `Handle<Type>` alone.
let is_combined_sampler = self
.peek(frontend)
.is_some_and(|t| matches!(t.value, TokenValue::CombinedSamplerTypeName(_)));
let (ty, mut meta) = self.parse_type(frontend, ctx)?;

let token = self.bump(frontend)?;
Expand Down Expand Up @@ -403,6 +411,7 @@ impl ParsingContext<'_> {
external,
is_inside_loop,
ctx,
is_combined_sampler,
};

self.backtrack(token_fallthrough)?;
Expand Down Expand Up @@ -597,6 +606,7 @@ impl ParsingContext<'_> {
name,
init: None,
meta,
is_combined_sampler: false,
},
)?;

Expand Down
1 change: 1 addition & 0 deletions naga/src/front/glsl/parser/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ impl ParsingContext<'_> {
name: Some(name),
init: None,
meta,
is_combined_sampler: false,
};

let pointer = frontend.add_local_var(ctx, decl)?;
Expand Down
14 changes: 12 additions & 2 deletions naga/src/front/glsl/parser/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,15 @@ impl ParsingContext<'_> {
let token = self.bump(frontend)?;
let mut handle = match token.value {
TokenValue::Void => return Ok((None, token.meta)),
TokenValue::TypeName(ty) => ctx.module.types.insert(ty, token.meta),
// Combined sampler types (sampler2D, isampler3D, sampler2DShadow, …) carry
// the same `Image` inner type as their `textureXX` counterparts but are
// tokenised separately so variable declarations can synthesise a paired
// implicit sampler. Here they are inserted into the type arena the same way
// as any other type name — the `CombinedSamplerTypeName` distinction is only
// relevant at global-variable-declaration time (see `add_global_var`).
TokenValue::TypeName(ty) | TokenValue::CombinedSamplerTypeName(ty) => {
ctx.module.types.insert(ty, token.meta)
}
TokenValue::Struct => {
let mut meta = token.meta;
let ty_name = self.expect_ident(frontend)?.0;
Expand Down Expand Up @@ -390,7 +398,9 @@ impl ParsingContext<'_> {

pub fn peek_type_name(&mut self, frontend: &mut Frontend) -> bool {
self.peek(frontend).is_some_and(|t| match t.value {
TokenValue::TypeName(_) | TokenValue::Void => true,
TokenValue::TypeName(_) | TokenValue::CombinedSamplerTypeName(_) | TokenValue::Void => {
true
}
TokenValue::Struct => true,
TokenValue::Identifier(ref ident) => frontend.lookup_type.contains_key(ident),
_ => false,
Expand Down
Loading
Loading