mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Shader packs: geometry stages whose varyings travel in interface blocks
A shadow program with a geometry stage is folded into one vertex stage (translate/geometry.rs), which reads both stages' declarations one variable at a time. An `out` interface block in the vertex stage was read as a single declaration: each member after the first had its type taken for a variable name, and that type (`float`...) was then renamed throughout the vertex stage, so the program failed with "`far` is `float` in one stage and `hz_vs_float` in the other" and the shadow map stayed clear. - Both stages' blocks become plain varyings first (interface_blocks), and `flatten` now also lowers a block with an instance array, the form of a geometry stage's input (`vIn[i].m` reads `hz_<block>_m[i]`). - A geometry stage's input and output blocks may share a name (GLSL 4.60, 4.3.9): its inputs read as `hz_in_<block>_<member>`, so they no longer collide with its outputs. - Statement reading takes every qualifier the translator knows (`sample`, `precise`): one it missed made a type a renamed name the same way. Part of #6812 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
898042ced8
commit
d5418e0137
2 files changed
+154
-53
No files matched your search
@@ -12,7 +12,7 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::fmt::Write;
|
||||
|
||||
use super::scan::significant;
|
||||
use super::scan::{QUALIFIERS, STORAGE, significant};
|
||||
use super::{Error, GbuffersInputs, Program, Target};
|
||||
use crate::Preprocessed;
|
||||
use crate::macros::lex;
|
||||
@@ -158,22 +158,9 @@ fn classify(tokens: &[&str], range: std::ops::Range<usize>) -> Statement {
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
let quals = [
|
||||
"in",
|
||||
"out",
|
||||
"attribute",
|
||||
"varying",
|
||||
"flat",
|
||||
"smooth",
|
||||
"noperspective",
|
||||
"centroid",
|
||||
"invariant",
|
||||
"highp",
|
||||
"mediump",
|
||||
"lowp",
|
||||
"const",
|
||||
];
|
||||
while k < words.len() && quals.contains(&words[k]) {
|
||||
// Every qualifier the translator knows (`sample`, `precise`...), in any order (GLSL
|
||||
// 4.60, 4.12): one it missed would be taken for the type, and the type for a name.
|
||||
while k < words.len() && (STORAGE.contains(&words[k]) || QUALIFIERS.contains(&words[k])) {
|
||||
k += 1;
|
||||
}
|
||||
match words.get(k) {
|
||||
@@ -246,8 +233,14 @@ fn combine(
|
||||
message,
|
||||
}]
|
||||
};
|
||||
let gs_tokens = lex(&geometry.code);
|
||||
let vs_tokens = lex(&vertex.code);
|
||||
// Interface blocks first become plain varyings (`interface_blocks`), the vertex stage's
|
||||
// `out` block and the geometry stage's `in` array alike: what follows reads declarations
|
||||
// of one type each. The geometry stage's input blocks read as `hz_in_<block>_<member>`,
|
||||
// apart from its output blocks, which may have the same names.
|
||||
let gs_code = super::interface_blocks::flatten_as(&geometry.code, "in_");
|
||||
let vs_code = super::interface_blocks::flatten(&vertex.code);
|
||||
let gs_tokens = lex(gs_code.as_deref().unwrap_or(&geometry.code));
|
||||
let vs_tokens = lex(vs_code.as_deref().unwrap_or(&vertex.code));
|
||||
let gs = statements(&gs_tokens);
|
||||
let vs = statements(&vs_tokens);
|
||||
|
||||
@@ -429,8 +422,12 @@ fn combine(
|
||||
}
|
||||
main.push_str(" hz_vs_main();\n");
|
||||
for (name, _) in &vs_outs {
|
||||
if gs_ins.iter().any(|(n, _)| n == name) {
|
||||
writeln!(main, " {name}[hz_i] = hz_vs_{name};").unwrap();
|
||||
let block = name.strip_prefix("hz_").map(|m| format!("hz_in_{m}"));
|
||||
if let Some((input, _)) = gs_ins
|
||||
.iter()
|
||||
.find(|(n, _)| n == name || Some(n) == block.as_ref())
|
||||
{
|
||||
writeln!(main, " {input}[hz_i] = hz_vs_{name};").unwrap();
|
||||
}
|
||||
}
|
||||
main.push_str(" hz_gl_in[hz_i].p = hz_vs_Position;\n }\n hz_gs_main();\n");
|
||||
@@ -583,17 +580,8 @@ mod tests {
|
||||
use crate::pack::tests::zip;
|
||||
use crate::{Macros, Pack, preprocess};
|
||||
|
||||
#[test]
|
||||
fn a_geometry_stage_folds_into_the_vertex_stage() {
|
||||
let vsh = "#version 430\nout vec3 vPos;\nout vec4 vColor;\nvoid main() {\n \
|
||||
vPos = gl_Vertex.xyz;\n vColor = gl_Color * float(gl_InstanceID + 1);\n \
|
||||
gl_Position = ftransform();\n}\n";
|
||||
let gsh = "#version 430\nlayout(triangles) in;\nlayout(triangle_strip, max_vertices = 4) out;\n\
|
||||
in vec3 vPos[];\nin vec4 vColor[];\nout vec4 gColor;\nvoid main() {\n \
|
||||
for (int i = 0; i < 3; i++) {\n gColor = vColor[i];\n \
|
||||
gl_Position = gl_in[i].gl_Position;\n EmitVertex();\n }\n \
|
||||
gl_Position = vec4(vPos[0], 1.0);\n EmitVertex();\n EndPrimitive();\n}\n";
|
||||
let fsh = "#version 430\nin vec4 gColor;\nvoid main() {\n gl_FragData[0] = gColor;\n}\n";
|
||||
/// `vsh`, `gsh` and `fsh` (a pack's shadow program) folded and translated.
|
||||
fn fold(vsh: &str, gsh: &str, fsh: &str) -> Result<(Program, u32), Vec<Error>> {
|
||||
let pack = Pack::from_zip_bytes(
|
||||
"P.zip",
|
||||
zip(&[
|
||||
@@ -617,19 +605,73 @@ mod tests {
|
||||
.into(),
|
||||
index: "uint(@)".into(),
|
||||
};
|
||||
let (program, triangles) = translate_geometry(
|
||||
translate_geometry(
|
||||
&pp("shadow.vsh"),
|
||||
&pp("shadow.gsh"),
|
||||
&pp("shadow.fsh"),
|
||||
&inputs,
|
||||
)
|
||||
.unwrap_or_else(|e| panic!("{e:?}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_geometry_stage_folds_into_the_vertex_stage() {
|
||||
let vsh = "#version 430\nout vec3 vPos;\nout vec4 vColor;\nvoid main() {\n \
|
||||
vPos = gl_Vertex.xyz;\n vColor = gl_Color * float(gl_InstanceID + 1);\n \
|
||||
gl_Position = ftransform();\n}\n";
|
||||
let gsh = "#version 430\nlayout(triangles) in;\nlayout(triangle_strip, max_vertices = 4) out;\n\
|
||||
in vec3 vPos[];\nin vec4 vColor[];\nout vec4 gColor;\nvoid main() {\n \
|
||||
for (int i = 0; i < 3; i++) {\n gColor = vColor[i];\n \
|
||||
gl_Position = gl_in[i].gl_Position;\n EmitVertex();\n }\n \
|
||||
gl_Position = vec4(vPos[0], 1.0);\n EmitVertex();\n EndPrimitive();\n}\n";
|
||||
let fsh = "#version 430\nin vec4 gColor;\nvoid main() {\n gl_FragData[0] = gColor;\n}\n";
|
||||
let (program, triangles) = fold(vsh, gsh, fsh).unwrap_or_else(|e| panic!("{e:?}"));
|
||||
// A strip of four corners is two triangles.
|
||||
assert_eq!(triangles, 2);
|
||||
assert_eq!(program.vertex.entry_points.len(), 1);
|
||||
assert_eq!(program.fragment.entry_points.len(), 1);
|
||||
}
|
||||
|
||||
/// Stages that pass their varyings in interface blocks (GLSL 4.60, 4.3.9), members with
|
||||
/// their own qualifiers: the vertex stage's `out` block, the geometry stage's `in` block
|
||||
/// array and `out` block, the fragment stage's `in` block. The geometry stage's two blocks
|
||||
/// share a name, as an input and an output block may (4.3.9).
|
||||
#[test]
|
||||
fn interface_blocks_pass_between_the_stages() {
|
||||
let vsh = "#version 430\nuniform float far;\n\
|
||||
out VertexData {\n vec4 color;\n flat float id;\n vec2 uv;\n} vOut;\n\
|
||||
void main() {\n vOut.color = gl_Color;\n vOut.id = far;\n \
|
||||
vOut.uv = gl_Vertex.xy;\n gl_Position = ftransform();\n}\n";
|
||||
let gsh = "#version 430\nlayout(triangles) in;\nlayout(triangle_strip, max_vertices = 3) out;\n\
|
||||
in VertexData {\n vec4 color;\n flat float id;\n vec2 uv;\n} vIn[];\n\
|
||||
out VertexData {\n vec4 color;\n flat float id;\n} gOut;\n\
|
||||
void main() {\n for (int i = 0; i < 3; i++) {\n \
|
||||
gOut.color = vIn[i].color * vIn[i].uv.x;\n gOut.id = vIn[0].id;\n \
|
||||
gl_Position = gl_in[i].gl_Position;\n EmitVertex();\n }\n \
|
||||
EndPrimitive();\n}\n";
|
||||
let fsh = "#version 430\nuniform float far;\n\
|
||||
in VertexData {\n vec4 color;\n flat float id;\n} gIn;\n\
|
||||
void main() {\n gl_FragData[0] = gIn.color * gIn.id / far;\n}\n";
|
||||
let (program, triangles) = fold(vsh, gsh, fsh).unwrap_or_else(|e| panic!("{e:?}"));
|
||||
assert_eq!(triangles, 1);
|
||||
assert_eq!(program.fragment.entry_points.len(), 1);
|
||||
}
|
||||
|
||||
/// An output qualified `sample` declares no variable named after its type (GLSL 4.60, 4.3).
|
||||
#[test]
|
||||
fn sample_outputs_rename_no_type() {
|
||||
let vsh = "#version 430\nuniform float far;\n\
|
||||
out sample float vDepth;\nvoid main() {\n vDepth = far;\n \
|
||||
gl_Position = ftransform();\n}\n";
|
||||
let gsh = "#version 430\nlayout(triangles) in;\nlayout(triangle_strip, max_vertices = 3) out;\n\
|
||||
in float vDepth[];\nout float gDepth;\nvoid main() {\n \
|
||||
for (int i = 0; i < 3; i++) {\n gDepth = vDepth[i];\n \
|
||||
gl_Position = gl_in[i].gl_Position;\n EmitVertex();\n }\n \
|
||||
EndPrimitive();\n}\n";
|
||||
let fsh = "#version 430\nuniform float far;\nin float gDepth;\n\
|
||||
void main() {\n gl_FragData[0] = vec4(gDepth / far);\n}\n";
|
||||
fold(vsh, gsh, fsh).unwrap_or_else(|e| panic!("{e:?}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn writes_run_once_and_keep_their_else() {
|
||||
let code =
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
//! type, found Interpolation(Flat)"). Each member of such a block becomes a varying of its own,
|
||||
//! `hz_<block>_<member>` (the name the next stage's block gives it too: stages match blocks by
|
||||
//! block name and members by name), and `instance.member` reads it. A block without an instance
|
||||
//! name keeps its members' names. Blocks with an instance array (a geometry stage's input) and
|
||||
//! blocks whose instance is used whole are left as written. Lines stay where they were.
|
||||
//! name keeps its members' names. Of a block with an instance array (a geometry stage's input,
|
||||
//! `in VertexData { vec2 uv; } vIn[];`), each member becomes an array, `vIn[i].uv` reading
|
||||
//! `hz_VertexData_uv[i]`. Blocks whose instance is used whole are left as written. Lines stay
|
||||
//! where they were.
|
||||
|
||||
use super::scan::significant;
|
||||
use crate::macros::lex;
|
||||
@@ -36,10 +38,19 @@ struct Block {
|
||||
name: String,
|
||||
members: Vec<(usize, Vec<(usize, String)>)>,
|
||||
instance: Option<(usize, String)>,
|
||||
/// The instance's array suffix (`[]`, `[3]`), empty when it is not an array.
|
||||
array: String,
|
||||
}
|
||||
|
||||
/// `code` with its plain `in`/`out` interface blocks as varyings, or `None` when it has none.
|
||||
pub(super) fn flatten(code: &str) -> Option<String> {
|
||||
flatten_as(code, "")
|
||||
}
|
||||
|
||||
/// `flatten`, the members of an `in` block with an instance named `hz_<inputs><block>_<member>`:
|
||||
/// a geometry stage's input and output blocks may share their name (GLSL 4.60, 4.3.9), and the
|
||||
/// geometry stage reads its inputs under names of its own.
|
||||
pub(super) fn flatten_as(code: &str, inputs: &str) -> Option<String> {
|
||||
let mut tokens: Vec<String> = lex(code).into_iter().map(str::to_owned).collect();
|
||||
let blocks = find_blocks(&tokens);
|
||||
if blocks.is_empty() {
|
||||
@@ -48,17 +59,25 @@ pub(super) fn flatten(code: &str) -> Option<String> {
|
||||
let mut changed = false;
|
||||
for b in blocks {
|
||||
let inside = |i: usize| (b.start..=b.end).contains(&i);
|
||||
let prefix = if b.storage == "in" { inputs } else { "" };
|
||||
let renamed = |member: &str| match &b.instance {
|
||||
Some(_) => format!("hz_{}_{member}", b.name),
|
||||
Some(_) => format!("hz_{prefix}{}_{member}", b.name),
|
||||
None => member.to_owned(),
|
||||
};
|
||||
// Every use of the instance outside the block must be `instance.member`.
|
||||
// Every use of the instance outside the block must be `instance.member`, or
|
||||
// `instance[i].member` for an instance array.
|
||||
let mut reads = Vec::new();
|
||||
if let Some((_, instance)) = &b.instance {
|
||||
let mut whole = false;
|
||||
for i in (0..tokens.len()).filter(|&i| !inside(i) && tokens[i] == *instance) {
|
||||
let next = next_significant(&tokens, i + 1);
|
||||
let member = next
|
||||
let index = match b.array.is_empty() {
|
||||
true => Some(i),
|
||||
false => next_significant(&tokens, i + 1)
|
||||
.filter(|&o| tokens[o] == "[")
|
||||
.and_then(|o| closing(&tokens, o)),
|
||||
};
|
||||
let member = index
|
||||
.and_then(|x| next_significant(&tokens, x + 1))
|
||||
.filter(|&d| tokens[d] == ".")
|
||||
.and_then(|d| next_significant(&tokens, d + 1));
|
||||
let known = member.filter(|&m| {
|
||||
@@ -66,8 +85,8 @@ pub(super) fn flatten(code: &str) -> Option<String> {
|
||||
.iter()
|
||||
.any(|(_, names)| names.iter().any(|(_, n)| *n == tokens[m]))
|
||||
});
|
||||
match (next, known) {
|
||||
(Some(_), Some(m)) => reads.push((i, m)),
|
||||
match (index, known) {
|
||||
(Some(x), Some(m)) => reads.push((i, x, m)),
|
||||
_ => whole = true,
|
||||
}
|
||||
}
|
||||
@@ -75,10 +94,11 @@ pub(super) fn flatten(code: &str) -> Option<String> {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (i, member) in reads {
|
||||
for (i, index, member) in reads {
|
||||
tokens[i] = renamed(&tokens[member]);
|
||||
// `.member` goes, with the spaces around it but not its line breaks.
|
||||
for t in &mut tokens[i + 1..=member] {
|
||||
// `.member` goes, with the spaces around it but not its line breaks; an index stays
|
||||
// (`v[i].m` is `hz_B_m[i]`).
|
||||
for t in &mut tokens[index + 1..=member] {
|
||||
t.retain(|c| c == '\n');
|
||||
}
|
||||
}
|
||||
@@ -91,7 +111,8 @@ pub(super) fn flatten(code: &str) -> Option<String> {
|
||||
for (first, names) in &b.members {
|
||||
tokens[*first] = format!("{} {}", b.storage, tokens[*first]);
|
||||
for (at, name) in names {
|
||||
tokens[*at] = renamed(name);
|
||||
// Of an instance array, each member is an array of the instance's size, outermost.
|
||||
tokens[*at] = renamed(name) + &b.array;
|
||||
}
|
||||
}
|
||||
changed = true;
|
||||
@@ -111,6 +132,24 @@ fn next_significant(tokens: &[String], from: usize) -> Option<usize> {
|
||||
(from..tokens.len()).find(|&i| significant(&tokens[i]))
|
||||
}
|
||||
|
||||
/// The `]` that closes the `[` at `open`.
|
||||
fn closing(tokens: &[String], open: usize) -> Option<usize> {
|
||||
let mut depth = 0usize;
|
||||
for (j, t) in tokens.iter().enumerate().skip(open) {
|
||||
match t.as_str() {
|
||||
"[" => depth += 1,
|
||||
"]" => {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
return Some(j);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// The `in`/`out` interface blocks at global scope that can be flattened.
|
||||
fn find_blocks(tokens: &[String]) -> Vec<Block> {
|
||||
let mut out = Vec::new();
|
||||
@@ -235,10 +274,17 @@ fn block(tokens: &[String], start: usize, open: usize) -> Option<Block> {
|
||||
.filter(|&j| significant(&tokens[j]))
|
||||
.collect();
|
||||
let end = (close + 1..tokens.len()).find(|&j| tokens[j] == ";")?;
|
||||
let instance = match after.as_slice() {
|
||||
[] => None,
|
||||
[n] if super::scan::is_ident(&tokens[*n]) => Some((*n, tokens[*n].clone())),
|
||||
_ => return None, // an instance array, or something else
|
||||
let (instance, array) = match after.as_slice() {
|
||||
[] => (None, String::new()),
|
||||
// `vIn[]`, `vIn[3]`: a geometry stage's input (GLSL 4.60, 4.3.9).
|
||||
[n, rest @ ..]
|
||||
if super::scan::is_ident(&tokens[*n])
|
||||
&& (rest.is_empty() || tokens[rest[0]] == "[" && tokens[*rest.last()?] == "]") =>
|
||||
{
|
||||
let array = rest.iter().map(|&j| tokens[j].as_str()).collect();
|
||||
(Some((*n, tokens[*n].clone())), array)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
if members.is_empty() {
|
||||
return None;
|
||||
@@ -252,6 +298,7 @@ fn block(tokens: &[String], start: usize, open: usize) -> Option<Block> {
|
||||
name,
|
||||
members,
|
||||
instance,
|
||||
array,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -286,14 +333,26 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_instance_array_makes_each_member_an_array() {
|
||||
let code = "in VertexData {\n flat int id;\n vec2 st[2];\n} vIn[];\n\
|
||||
void main() { int i = vIn[0].id; vec2 a = vIn[i + 1] . st[1]; }\n";
|
||||
let out = flatten(code).unwrap();
|
||||
assert_eq!(out.lines().count(), code.lines().count());
|
||||
assert_eq!(
|
||||
out.split_whitespace().collect::<Vec<_>>().join(" "),
|
||||
"in flat int hz_VertexData_id[]; in vec2 hz_VertexData_st[][2]; void main() { \
|
||||
int i = hz_VertexData_id[0]; vec2 a = hz_VertexData_st[i + 1][1]; }"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other_blocks_stay_as_written() {
|
||||
for code in [
|
||||
// Uniform and storage blocks, a geometry stage's instance array, an instance
|
||||
// used whole.
|
||||
// Uniform and storage blocks, an instance used whole.
|
||||
"uniform Frame { float t; } frame;\nvoid main() {}\n",
|
||||
"buffer Data { float v[]; } data;\nvoid main() {}\n",
|
||||
"in VertexData { flat int id; } vIn[];\nvoid main() {}\n",
|
||||
"in VertexData { flat int id; } vIn[];\nvoid f(VertexData d) {}\nvoid main() { f(vIn[0]); }\n",
|
||||
"out VertexData { flat int id; } vOut;\nvoid f(int x) {}\nvoid main() { vOut = vOut; }\n",
|
||||
] {
|
||||
assert_eq!(flatten(code), None, "{code}");
|
||||
|
||||
Reference in new issue
Block a user