Skip to content

[naga] Move WGSL type formatting code to common::wgsl #7280

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 12, 2025
261 changes: 48 additions & 213 deletions naga/src/back/wgsl/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use core::fmt::Write;

use super::Error;
use super::ToWgslIfImplemented as _;
use crate::back::wgsl::polyfill::InversePolyfill;
use crate::{back::wgsl::polyfill::InversePolyfill, common::wgsl::TypeContext};
use crate::{
back::{self, Baked},
common::{
Expand Down Expand Up @@ -393,9 +393,15 @@ impl<W: Write> Writer<W> {
}

/// Helper method used to write structs
/// Write the full declaration of a struct type.
///
/// # Notes
/// Ends in a newline
/// Write out a definition of the struct type referred to by
/// `handle` in `module`. The output will be an instance of the
/// `struct_decl` production in the WGSL grammar.
///
/// Use `members` as the list of `handle`'s members. (This
/// function is usually called after matching a `TypeInner`, so
/// the callers already have the members at hand.)
fn write_struct(
&mut self,
module: &Module,
Expand All @@ -419,227 +425,37 @@ impl<W: Write> Writer<W> {
writeln!(self.out)?;
}

write!(self.out, "}}")?;

writeln!(self.out)?;
writeln!(self.out, "}}")?;

Ok(())
}

/// Helper method used to write non image/sampler types
///
/// # Notes
/// Adds no trailing or leading whitespace
fn write_type(&mut self, module: &Module, ty: Handle<crate::Type>) -> BackendResult {
let inner = &module.types[ty].inner;
match *inner {
TypeInner::Struct { .. } => {
write!(self.out, "{}", self.names[&NameKey::Type(ty)])?;
}
ref other => self.write_value_type(module, other)?,
}
// This actually can't be factored out into a nice constructor method,
// because the borrow checker needs to be able to see that the borrows
// of `self.names` and `self.out` are disjoint.
let type_context = WriterTypeContext {
module,
names: &self.names,
};
type_context.write_type(ty, &mut self.out)?;

Ok(())
}

/// Helper method used to write value types
///
/// # Notes
/// Adds no trailing or leading whitespace
fn write_value_type(&mut self, module: &Module, inner: &TypeInner) -> BackendResult {
match *inner {
TypeInner::Vector { size, scalar } => write!(
self.out,
"vec{}<{}>",
common::vector_size_str(size),
scalar.to_wgsl_if_implemented()?,
)?,
TypeInner::Sampler { comparison: false } => {
write!(self.out, "sampler")?;
}
TypeInner::Sampler { comparison: true } => {
write!(self.out, "sampler_comparison")?;
}
TypeInner::Image {
dim,
arrayed,
class,
} => {
// More about texture types: https://gpuweb.github.io/gpuweb/wgsl/#sampled-texture-type
use crate::ImageClass as Ic;

let dim_str = dim.to_wgsl();
let arrayed_str = if arrayed { "_array" } else { "" };
let (class_str, multisampled_str, format_str, storage_str) = match class {
Ic::Sampled { kind, multi } => (
"",
if multi { "multisampled_" } else { "" },
crate::Scalar { kind, width: 4 }.to_wgsl_if_implemented()?,
"",
),
Ic::Depth { multi } => {
("depth_", if multi { "multisampled_" } else { "" }, "", "")
}
Ic::Storage { format, access } => (
"storage_",
"",
format.to_wgsl(),
if access.contains(crate::StorageAccess::ATOMIC) {
",atomic"
} else if access
.contains(crate::StorageAccess::LOAD | crate::StorageAccess::STORE)
{
",read_write"
} else if access.contains(crate::StorageAccess::LOAD) {
",read"
} else {
",write"
},
),
};
write!(
self.out,
"texture_{class_str}{multisampled_str}{dim_str}{arrayed_str}"
)?;

if !format_str.is_empty() {
write!(self.out, "<{format_str}{storage_str}>")?;
}
}
TypeInner::Scalar(scalar) => {
write!(self.out, "{}", scalar.to_wgsl_if_implemented()?)?;
}
TypeInner::Atomic(scalar) => {
write!(self.out, "atomic<{}>", scalar.to_wgsl_if_implemented()?)?;
}
TypeInner::Array {
base,
size,
stride: _,
} => {
// More info https://gpuweb.github.io/gpuweb/wgsl/#array-types
// array<A, 3> -- Constant array
// array<A> -- Dynamic array
write!(self.out, "array<")?;
match size {
crate::ArraySize::Constant(len) => {
self.write_type(module, base)?;
write!(self.out, ", {len}")?;
}
crate::ArraySize::Pending(_) => {
unreachable!();
}
crate::ArraySize::Dynamic => {
self.write_type(module, base)?;
}
}
write!(self.out, ">")?;
}
TypeInner::BindingArray { base, size } => {
// More info https://github.com/gpuweb/gpuweb/issues/2105
write!(self.out, "binding_array<")?;
match size {
crate::ArraySize::Constant(len) => {
self.write_type(module, base)?;
write!(self.out, ", {len}")?;
}
crate::ArraySize::Pending(_) => {
unreachable!();
}
crate::ArraySize::Dynamic => {
self.write_type(module, base)?;
}
}
write!(self.out, ">")?;
}
TypeInner::Matrix {
columns,
rows,
scalar,
} => {
write!(
self.out,
"mat{}x{}<{}>",
common::vector_size_str(columns),
common::vector_size_str(rows),
scalar.to_wgsl_if_implemented()?
)?;
}
TypeInner::Pointer { base, space } => {
let (address, maybe_access) = address_space_str(space);
// Everything but `AddressSpace::Handle` gives us a `address` name, but
// Naga IR never produces pointers to handles, so it doesn't matter much
// how we write such a type. Just write it as the base type alone.
if let Some(space) = address {
write!(self.out, "ptr<{space}, ")?;
}
self.write_type(module, base)?;
if address.is_some() {
if let Some(access) = maybe_access {
write!(self.out, ", {access}")?;
}
write!(self.out, ">")?;
}
}
TypeInner::ValuePointer {
size: None,
scalar,
space,
} => {
let (address, maybe_access) = address_space_str(space);
if let Some(space) = address {
write!(
self.out,
"ptr<{}, {}",
space,
scalar.to_wgsl_if_implemented()?
)?;
if let Some(access) = maybe_access {
write!(self.out, ", {access}")?;
}
write!(self.out, ">")?;
} else {
return Err(Error::Unimplemented(format!(
"ValuePointer to AddressSpace::Handle {inner:?}"
)));
}
}
TypeInner::ValuePointer {
size: Some(size),
scalar,
space,
} => {
let (address, maybe_access) = address_space_str(space);
if let Some(space) = address {
write!(
self.out,
"ptr<{}, vec{}<{}>",
space,
common::vector_size_str(size),
scalar.to_wgsl_if_implemented()?
)?;
if let Some(access) = maybe_access {
write!(self.out, ", {access}")?;
}
write!(self.out, ">")?;
} else {
return Err(Error::Unimplemented(format!(
"ValuePointer to AddressSpace::Handle {inner:?}"
)));
}
write!(self.out, ">")?;
}
TypeInner::AccelerationStructure { vertex_return } => {
let caps = if vertex_return { "<vertex_return>" } else { "" };
write!(self.out, "acceleration_structure{}", caps)?
}
_ => {
return Err(Error::Unimplemented(format!("write_value_type {inner:?}")));
}
}
fn write_type_inner(&mut self, module: &Module, inner: &TypeInner) -> BackendResult {
// This actually can't be factored out into a nice constructor method,
// because the borrow checker needs to be able to see that the borrows
// of `self.names` and `self.out` are disjoint.
let type_context = WriterTypeContext {
module,
names: &self.names,
};
type_context.write_type_inner(inner, &mut self.out)?;

Ok(())
}

/// Helper method used to write statements
///
/// # Notes
Expand Down Expand Up @@ -1190,7 +1006,7 @@ impl<W: Write> Writer<W> {
self.write_type(module, handle)?;
}
proc::TypeResolution::Value(ref inner) => {
self.write_value_type(module, inner)?;
self.write_type_inner(module, inner)?;
}
}
}
Expand Down Expand Up @@ -1904,6 +1720,25 @@ impl<W: Write> Writer<W> {
}
}

struct WriterTypeContext<'m> {
module: &'m Module,
names: &'m crate::FastHashMap<NameKey, String>,
}

impl<W: Write> TypeContext<W> for WriterTypeContext<'_> {
fn lookup_type(&self, handle: Handle<crate::Type>) -> &crate::Type {
&self.module.types[handle]
}

fn type_name(&self, handle: Handle<crate::Type>) -> &str {
self.names[&NameKey::Type(handle)].as_str()
}

fn write_override(&self, _: Handle<crate::Override>, _: &mut W) -> core::fmt::Result {
unreachable!("overrides should be validated out");
}
}

fn map_binding_to_attribute(binding: &crate::Binding) -> Vec<Attribute> {
match *binding {
crate::Binding::BuiltIn(built_in) => {
Expand Down
4 changes: 4 additions & 0 deletions naga/src/common/wgsl.rs → naga/src/common/wgsl/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
//! Code shared between the WGSL front and back ends.

mod types;

use core::fmt::{self, Display, Formatter};

use crate::diagnostic_filter::{
FilterableTriggeringRule, Severity, StandardFilterableTriggeringRule,
};

pub use types::TypeContext;

impl Severity {
const ERROR: &'static str = "error";
const WARNING: &'static str = "warning";
Expand Down
Loading