Shader packs: struct types without a name

GLSL lets a declaration define its struct type in place without naming it
(GLSL 4.60, 4.1.8): `shared struct { uint sum; uint tile[8][8]; } lds;`.
naga's frontend wants a name ("Expected identifier, found LeftBrace"), which
RenderPearl v2.7.2's compute programs (deferred, deferred1, composite,
composite3) hit. Each such type now gets one (`hz_struct<n>`) before the
stages are read.

Part of #7171

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Arslan ArmanulyandClaude Opus 5.5 committed 2026-11-02 17:27:14 +05:00
1 parent fbb17c980e
commit 5e2c8f5641
2 files changed
+71 -2

No files matched your search

@@ -0,0 +1,44 @@
//! Struct types without a name. GLSL lets a declaration define its struct type in place without
//! naming it (GLSL 4.60, 4.1.8: `shared struct { uint sum; uint tile[64]; } lds;`); naga's
//! frontend wants a name there ("Expected identifier, found LeftBrace"). Each such type gets
//! one, `hz_struct<n>`; nothing else changes.
use super::scan::significant;
use crate::macros::lex;
/// `code` with a name for each struct type declared without one, or `None` when it has none.
pub(super) fn name(code: &str) -> Option<String> {
let tokens = lex(code);
let mut out = String::with_capacity(code.len() + 64);
let mut named = 0;
for (i, t) in tokens.iter().enumerate() {
out.push_str(t);
let opens = (i + 1..tokens.len())
.find(|&j| significant(tokens[j]))
.is_some_and(|j| tokens[j] == "{");
if *t == "struct" && opens {
out.push_str(&format!(" hz_struct{named}"));
named += 1;
}
}
(named > 0).then_some(out)
}
#[cfg(test)]
mod tests {
use super::name;
#[test]
fn struct_types_without_a_name_get_one() {
let code = "shared struct {\n uint sum;\n} lds;\nstruct Light { vec3 c; };\n\
struct\n{ float a; } x, y[2];\n";
assert_eq!(
name(code).as_deref(),
Some(
"shared struct hz_struct0 {\n uint sum;\n} lds;\nstruct Light { vec3 c; };\n\
struct hz_struct1\n{ float a; } x, y[2];\n"
)
);
assert_eq!(name("struct Light { vec3 c; };\n"), None);
}
}
+27 -2
View File
@@ -27,6 +27,7 @@ use naga::valid::{Capabilities, ValidationFlags, Validator};
use crate::Preprocessed;
mod anonymous_structs;
mod atomic_vectors;
mod geometry;
mod image_parameters;
@@ -239,9 +240,14 @@ fn translate(
} else {
GBUFFERS_MATRICES.to_owned()
};
// Interface blocks travel as plain varyings (`interface_blocks`).
// Struct types without a name get one (`anonymous_structs`), and interface blocks travel
// as plain varyings (`interface_blocks`).
let flat = |pp: &Preprocessed| {
interface_blocks::flatten(&pp.code).map(|code| Preprocessed { code, ..pp.clone() })
let named = anonymous_structs::name(&pp.code);
let code = named.as_deref().unwrap_or(&pp.code);
interface_blocks::flatten(code)
.or(named)
.map(|code| Preprocessed { code, ..pp.clone() })
};
let (vertex_flat, fragment_flat) = (flat(vertex), flat(fragment));
let vertex = vertex_flat.as_ref().unwrap_or(vertex);
@@ -625,6 +631,11 @@ fn resources(
/// Translates a compute program (`.csh`).
pub fn translate_compute(compute: &Preprocessed) -> Result<ComputeProgram, Vec<Error>> {
let named = anonymous_structs::name(&compute.code).map(|code| Preprocessed {
code,
..compute.clone()
});
let compute = named.as_ref().unwrap_or(compute);
let split = atomic_vectors::rewrite(compute);
let compute = split.as_ref().unwrap_or(compute);
let mut c = Stage::scan(compute, ShaderStage::Compute);
@@ -2157,6 +2168,20 @@ mod tests {
}));
}
/// A `shared` variable whose struct type has no name (GLSL 4.60, 4.1.8), as compute
/// programs keep their work group's tiles.
#[test]
fn a_shared_struct_without_a_type_name_compiles() {
let csh = "#version 460\nlayout(local_size_x = 8, local_size_y = 8) in;\n\
shared struct {\n uint sum;\n uint tile[gl_WorkGroupSize.x][gl_WorkGroupSize.y];\n} lds;\n\
layout(r32ui) uniform uimage2D colorimg4;\nvoid main() {\n \
lds.tile[gl_LocalInvocationID.x][gl_LocalInvocationID.y] = gl_LocalInvocationIndex;\n \
barrier();\n imageStore(colorimg4, ivec2(gl_GlobalInvocationID.xy), uvec4(lds.tile[1][1]));\n}\n";
let pack = Pack::from_zip_bytes("P.zip", zip(&[("shaders/deferred_a.csh", csh)])).unwrap();
translate_compute(&preprocess(&pack, "deferred_a.csh", &Macros::standard()))
.unwrap_or_else(|e| panic!("{e:?}"));
}
#[test]
fn buffer_blocks_bind_at_their_binding() {
let csh = "#version 430\nlayout(local_size_x = 1) in;\n\