Skip to content

Commit

Permalink
[glsl-out] Implement degenerate switch to loop transform for glsl-out
Browse files Browse the repository at this point in the history
  • Loading branch information
Imberflur committed May 3, 2024
1 parent 9b4bb35 commit 4c594bc
Show file tree
Hide file tree
Showing 10 changed files with 301 additions and 140 deletions.
17 changes: 9 additions & 8 deletions naga/src/back/continue_forward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ enum Nesting {
/// * When entering an inner loop, increment the depth.
/// * When exiting the loop, decrement the depth (and pop if it reaches 0).
Loop { depth: u32 },
/// Currently nested in at least one switch.
/// Currently nested in at least one switch that needs to forward continues.
///
/// (including ones transformed into `do {} while(false)` loops).
/// This includes switches transformed into `do {} while(false)` loops, but doesn't need to
/// include regular switches in backends that can support `continue` within switches.
///
/// `continue` should be forwarded to surrounding loop.
///
Expand All @@ -30,7 +31,7 @@ enum Nesting {
Switch { depth: u32, variable_id: u32 },
}

pub(crate) enum ExitSwitchOp {
pub(crate) enum ExitControlFlow {
None,
/// Emit `if (continue_variable) { continue; }`
Continue {
Expand Down Expand Up @@ -124,12 +125,12 @@ impl ContinueCtx {

/// Updates internal state and returns whether this switch needs to be followed by a statement
/// to forward continues.
pub fn exit_switch(&mut self) -> ExitSwitchOp {
pub fn exit_switch(&mut self) -> ExitControlFlow {
match self.stack.last_mut() {
None => ExitSwitchOp::None,
None => ExitControlFlow::None,
Some(&mut Nesting::Loop { .. }) => {
log::error!("Unexpected loop state when exiting switch");
ExitSwitchOp::None
ExitControlFlow::None
}
Some(&mut Nesting::Switch {
ref mut depth,
Expand All @@ -138,9 +139,9 @@ impl ContinueCtx {
*depth -= 1;
if *depth == 0 {
self.stack.pop();
ExitSwitchOp::Continue { variable_id }
ExitControlFlow::Continue { variable_id }
} else {
ExitSwitchOp::Break { variable_id }
ExitControlFlow::Break { variable_id }
}
}
}
Expand Down
157 changes: 111 additions & 46 deletions naga/src/back/glsl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,11 @@ pub struct Writer<'a, W> {
named_expressions: crate::NamedExpressions,
/// Set of expressions that need to be baked to avoid unnecessary repetition in output
need_bake_expressions: back::NeedBakeExpressions,
/// Information about nesting of loops and switches.
///
/// Used for forwarding continue statements in switches that have been
/// transformed to `do {} while(false);` loops.
continue_ctx: back::continue_forward::ContinueCtx,
/// How many views to render to, if doing multiview rendering.
multiview: Option<std::num::NonZeroU32>,
/// Mapping of varying variables to their location. Needed for reflections.
Expand Down Expand Up @@ -620,6 +625,7 @@ impl<'a, W: Write> Writer<'a, W> {
block_id: IdGenerator::default(),
named_expressions: Default::default(),
need_bake_expressions: Default::default(),
continue_ctx: back::continue_forward::ContinueCtx::default(),
varying: Default::default(),
};

Expand Down Expand Up @@ -2077,61 +2083,110 @@ impl<'a, W: Write> Writer<'a, W> {
selector,
ref cases,
} => {
// TODO: Do this
let l2 = level.next();
// Some glsl consumers may not handle switches with a single body correctly.
// So write this as a `do {} while(false);` loop instead.
// This is the same fix as for FXC in hlsl, although here it wasn't specifically
// tested against any problematic consumers.

// Start the switch
write!(self.out, "{level}")?;
write!(self.out, "switch(")?;
self.write_expr(selector, ctx)?;
writeln!(self.out, ") {{")?;
// This is the same fix as for FXC in hlsl, although here it
// wasn't specifically tested against any problematic consumers.
// See https://github.com/gfx-rs/wgpu/issues/4514
let one_body = cases
.iter()
.rev()
.skip(1)
.all(|case| case.fall_through && case.body.is_empty());
if one_body {
// See docs of `back::continue_forward` module. For glsl
// we only need to handle switches transformed into loops.
if let Some(variable_id) = self.continue_ctx.enter_switch() {
writeln!(
self.out,
"{level}bool {}{variable_id} = false;",
back::CONTINUE_PREFIX,
)?;
};
writeln!(self.out, "{level}do {{")?;
// Note: Expressions have no side-effects so we don't need to emit selector expression.

// Write all cases
let l2 = level.next();
for case in cases {
match case.value {
crate::SwitchValue::I32(value) => write!(self.out, "{l2}case {value}:")?,
crate::SwitchValue::U32(value) => write!(self.out, "{l2}case {value}u:")?,
crate::SwitchValue::Default => {
if cases.len() == 1 {
let uint = matches!(
ctx.resolve_type(selector, &self.module.types).scalar_kind(),
Some(crate::ScalarKind::Uint)
);
if uint {
writeln!(self.out, "{l2}case 0u:")?;
} else {
writeln!(self.out, "{l2}case 0:")?;
// Body
if let Some(case) = cases.last() {
for sta in case.body.iter() {
self.write_stmt(sta, ctx, l2)?;
}
}
// End do-while
writeln!(self.out, "{level}}} while(false);")?;

// Handle any forwarded continue statements.
use back::continue_forward::ExitControlFlow;
let op = match self.continue_ctx.exit_switch() {
ExitControlFlow::None => None,
ExitControlFlow::Continue { variable_id } => {
Some(("continue", variable_id))
}
ExitControlFlow::Break { variable_id } => Some(("break", variable_id)),
};
if let Some((control_flow, id)) = op {
writeln!(self.out, "{level}if ({}{id}) {{", back::CONTINUE_PREFIX)?;
writeln!(self.out, "{l2}{control_flow};")?;
writeln!(self.out, "{level}}}")?;
}
} else {
// Start the switch
write!(self.out, "{level}")?;
write!(self.out, "switch(")?;
self.write_expr(selector, ctx)?;
writeln!(self.out, ") {{")?;

// Write all cases
for case in cases {
match case.value {
crate::SwitchValue::I32(value) => {
write!(self.out, "{l2}case {value}:")?
}
crate::SwitchValue::U32(value) => {
write!(self.out, "{l2}case {value}u:")?
}
crate::SwitchValue::Default => {
if cases.len() == 1 {
let uint = matches!(
ctx.resolve_type(selector, &self.module.types)
.scalar_kind(),
Some(crate::ScalarKind::Uint)
);
if uint {
writeln!(self.out, "{l2}case 0u:")?;
} else {
writeln!(self.out, "{l2}case 0:")?;
}
}
write!(self.out, "{l2}default:")?;
}
write!(self.out, "{l2}default:")?;
}
}

let write_block_braces = !(case.fall_through && case.body.is_empty());
if write_block_braces {
writeln!(self.out, " {{")?;
} else {
writeln!(self.out)?;
}
let write_block_braces = !(case.fall_through && case.body.is_empty());
if write_block_braces {
writeln!(self.out, " {{")?;
} else {
writeln!(self.out)?;
}

for sta in case.body.iter() {
self.write_stmt(sta, ctx, l2.next())?;
}
for sta in case.body.iter() {
self.write_stmt(sta, ctx, l2.next())?;
}

if !case.fall_through && case.body.last().map_or(true, |s| !s.is_terminator()) {
writeln!(self.out, "{}break;", l2.next())?;
}
if !case.fall_through
&& case.body.last().map_or(true, |s| !s.is_terminator())
{
writeln!(self.out, "{}break;", l2.next())?;
}

if write_block_braces {
writeln!(self.out, "{l2}}}")?;
if write_block_braces {
writeln!(self.out, "{l2}}}")?;
}
}
}

writeln!(self.out, "{level}}}")?
writeln!(self.out, "{level}}}")?
}
}
// Loops in naga IR are based on wgsl loops, glsl can emulate the behaviour by using a
// while true loop and appending the continuing block to the body resulting on:
Expand All @@ -2148,6 +2203,7 @@ impl<'a, W: Write> Writer<'a, W> {
ref continuing,
break_if,
} => {
self.continue_ctx.enter_loop();
if !continuing.is_empty() || break_if.is_some() {
let gate_name = self.namer.call("loop_init");
writeln!(self.out, "{level}bool {gate_name} = true;")?;
Expand All @@ -2173,7 +2229,8 @@ impl<'a, W: Write> Writer<'a, W> {
for sta in body {
self.write_stmt(sta, ctx, level.next())?;
}
writeln!(self.out, "{level}}}")?
writeln!(self.out, "{level}}}")?;
self.continue_ctx.exit_loop();
}
// Break, continue and return as written as in C
// `break;`
Expand All @@ -2183,8 +2240,16 @@ impl<'a, W: Write> Writer<'a, W> {
}
// `continue;`
Statement::Continue => {
write!(self.out, "{level}")?;
writeln!(self.out, "continue;")?
if let Some(variable_id) = self.continue_ctx.needs_forwarding() {
writeln!(
self.out,
"{level}{}{variable_id} = true;",
back::CONTINUE_PREFIX
)?;
writeln!(self.out, "{level}break;")?
} else {
writeln!(self.out, "{level}continue;")?
}
}
// `return expr;`, `expr` is optional
Statement::Return { value } => {
Expand Down
9 changes: 5 additions & 4 deletions naga/src/back/hlsl/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1510,11 +1510,12 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> {
writeln!(self.out, "{level}}}")?;
}

use back::continue_forward::ExitSwitchOp;
// Handle any forwarded continue statements.
use back::continue_forward::ExitControlFlow;
let op = match self.continue_ctx.exit_switch() {
ExitSwitchOp::None => None,
ExitSwitchOp::Continue { variable_id } => Some(("continue", variable_id)),
ExitSwitchOp::Break { variable_id } => Some(("break", variable_id)),
ExitControlFlow::None => None,
ExitControlFlow::Continue { variable_id } => Some(("continue", variable_id)),
ExitControlFlow::Break { variable_id } => Some(("break", variable_id)),
};
if let Some((control_flow, id)) = op {
writeln!(self.out, "{level}if ({}{id}) {{", back::CONTINUE_PREFIX)?;
Expand Down
18 changes: 18 additions & 0 deletions naga/tests/in/control-flow.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -114,5 +114,23 @@ fn loop_switch_continue_nesting(x: i32, y: i32, z: i32) {
}
default: {}
}


// Degenerate switch with continue
switch y {
default: {
continue;
}
}
// Nested degenerate switch with continue
switch y {
case 1, default: {
switch z {
default: {
continue;
}
}
}
}
}
}
40 changes: 27 additions & 13 deletions naga/tests/out/glsl/control-flow.main.Compute.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@ layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;


void switch_default_break(int i) {
switch(i) {
case 0:
default: {
break;
}
}
do {
break;
} while(false);
}

void switch_case_break() {
Expand Down Expand Up @@ -72,6 +69,27 @@ void loop_switch_continue_nesting(int x_1, int y, int z) {
break;
}
}
bool _continue0 = false;
do {
_continue0 = true;
break;
} while(false);
if (_continue0) {
continue;
}
bool _continue1 = false;
do {
do {
_continue1 = true;
break;
} while(false);
if (_continue1) {
break;
}
} while(false);
if (_continue1) {
continue;
}
}
return;
}
Expand All @@ -83,13 +101,9 @@ void main() {
barrier();
memoryBarrierShared();
barrier();
switch(1) {
case 0:
default: {
pos = 1;
break;
}
}
do {
pos = 1;
} while(false);
int _e4 = pos;
switch(_e4) {
case 1: {
Expand Down
10 changes: 3 additions & 7 deletions naga/tests/out/glsl/degenerate-switch.fs_main.Fragment.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,9 @@ layout(location = 0) out vec4 _fs2p_location0;

void main() {
float x = 0.0;
switch(0) {
case 0:
default: {
x = 1.0;
break;
}
}
do {
x = 1.0;
} while(false);
float _e4 = x;
_fs2p_location0 = vec4(_e4);
return;
Expand Down

0 comments on commit 4c594bc

Please sign in to comment.