From 2942dc0cb216704f04128094113fb55abf08e816 Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Wed, 5 Mar 2025 12:59:13 -0800 Subject: [PATCH] [naga wgsl] Move code to generate WGSL for types into `common`. Move the `TypeContext` trait from `naga::back::wgsl` into `naga::common::wgsl`. Adjust imports and publicity markers as needed. There should be no changes to behavior in this commit, only code motion. --- naga/src/back/wgsl/writer.rs | 248 +-------------------------------- naga/src/common/wgsl/mod.rs | 4 + naga/src/common/wgsl/types.rs | 253 ++++++++++++++++++++++++++++++++++ 3 files changed, 258 insertions(+), 247 deletions(-) create mode 100644 naga/src/common/wgsl/types.rs diff --git a/naga/src/back/wgsl/writer.rs b/naga/src/back/wgsl/writer.rs index df0b38f961..ea96fcb6fa 100644 --- a/naga/src/back/wgsl/writer.rs +++ b/naga/src/back/wgsl/writer.rs @@ -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::{ @@ -431,252 +431,6 @@ impl Writer { } } -/// A context for printing Naga IR types as WGSL. -/// -/// This trait's default methods [`write_type`] and -/// [`write_type_inner`] do the work of formatting types as WGSL. -/// Implementors must provide the remaining methods, to customize -/// behavior for the context at hand. -/// -/// For example, the WGSL backend would provide an implementation of -/// [`type_name`] that handles hygienic renaming, whereas the WGSL -/// front end would simply show the name that was given in the source. -/// -/// [`write_type`]: TypeContext::write_type -/// [`write_type_inner`]: TypeContext::write_type_inner -/// [`type_name`]: TypeContext::type_name -trait TypeContext { - /// Return the [`Type`] referred to by `handle`. - /// - /// [`Type`]: crate::Type - fn lookup_type(&self, handle: Handle) -> &crate::Type; - - /// Return the name to be used for the type referred to by - /// `handle`. - fn type_name(&self, handle: Handle) -> &str; - - /// Write the WGSL form of `override` to `out`. - fn write_override(&self, r#override: Handle, out: &mut W) - -> core::fmt::Result; - - /// Write the type `ty` as it would appear in a value's declaration. - /// - /// Write the type referred to by `ty` in `module` as it would appear in - /// a `var`, `let`, etc. declaration, or in a function's argument list. - fn write_type(&self, handle: Handle, out: &mut W) -> core::fmt::Result { - let ty = self.lookup_type(handle); - match ty.inner { - TypeInner::Struct { .. } => out.write_str(self.type_name(handle))?, - ref other => self.write_type_inner(other, out)?, - } - - Ok(()) - } - - /// Write the [`TypeInner`] `inner` as it would appear in a value's declaration. - /// - /// Write `inner` as it would appear in a `var`, `let`, etc. - /// declaration, or in a function's argument list. - /// - /// Note that this cannot handle writing [`Struct`] types: those - /// must be referred to by name, but the name isn't available in - /// [`TypeInner`]. - /// - /// [`Struct`]: TypeInner::Struct - fn write_type_inner(&self, inner: &TypeInner, out: &mut W) -> core::fmt::Result { - fn unwrap_to_wgsl(value: T) -> &'static str { - value.try_to_wgsl().unwrap_or_else(|| { - unreachable!( - "validation should have forbidden {}: {value:?}", - T::DESCRIPTION - ); - }) - } - - match *inner { - TypeInner::Vector { size, scalar } => write!( - out, - "vec{}<{}>", - common::vector_size_str(size), - unwrap_to_wgsl(scalar), - )?, - TypeInner::Sampler { comparison: false } => { - write!(out, "sampler")?; - } - TypeInner::Sampler { comparison: true } => { - write!(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 { "" }, - unwrap_to_wgsl(crate::Scalar { kind, width: 4 }), - "", - ), - 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!( - out, - "texture_{class_str}{multisampled_str}{dim_str}{arrayed_str}" - )?; - - if !format_str.is_empty() { - write!(out, "<{format_str}{storage_str}>")?; - } - } - TypeInner::Scalar(scalar) => { - write!(out, "{}", unwrap_to_wgsl(scalar))?; - } - TypeInner::Atomic(scalar) => { - write!(out, "atomic<{}>", unwrap_to_wgsl(scalar))?; - } - TypeInner::Array { - base, - size, - stride: _, - } => { - // More info https://gpuweb.github.io/gpuweb/wgsl/#array-types - // array -- Constant array - // array -- Dynamic array - write!(out, "array<")?; - match size { - crate::ArraySize::Constant(len) => { - self.write_type(base, out)?; - write!(out, ", {len}")?; - } - crate::ArraySize::Pending(r#override) => { - self.write_override(r#override, out)?; - } - crate::ArraySize::Dynamic => { - self.write_type(base, out)?; - } - } - write!(out, ">")?; - } - TypeInner::BindingArray { base, size } => { - // More info https://github.com/gpuweb/gpuweb/issues/2105 - write!(out, "binding_array<")?; - match size { - crate::ArraySize::Constant(len) => { - self.write_type(base, out)?; - write!(out, ", {len}")?; - } - crate::ArraySize::Pending(r#override) => { - self.write_override(r#override, out)?; - } - crate::ArraySize::Dynamic => { - self.write_type(base, out)?; - } - } - write!(out, ">")?; - } - TypeInner::Matrix { - columns, - rows, - scalar, - } => { - write!( - out, - "mat{}x{}<{}>", - common::vector_size_str(columns), - common::vector_size_str(rows), - unwrap_to_wgsl(scalar) - )?; - } - 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!(out, "ptr<{space}, ")?; - } - self.write_type(base, out)?; - if address.is_some() { - if let Some(access) = maybe_access { - write!(out, ", {access}")?; - } - write!(out, ">")?; - } - } - TypeInner::ValuePointer { - size: None, - scalar, - space, - } => { - let (address, maybe_access) = address_space_str(space); - if let Some(space) = address { - write!(out, "ptr<{}, {}", space, unwrap_to_wgsl(scalar))?; - if let Some(access) = maybe_access { - write!(out, ", {access}")?; - } - write!(out, ">")?; - } else { - unreachable!("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!( - out, - "ptr<{}, vec{}<{}>", - space, - common::vector_size_str(size), - unwrap_to_wgsl(scalar) - )?; - if let Some(access) = maybe_access { - write!(out, ", {access}")?; - } - write!(out, ">")?; - } else { - unreachable!("ValuePointer to AddressSpace::Handle {inner:?}"); - } - write!(out, ">")?; - } - TypeInner::AccelerationStructure { vertex_return } => { - let caps = if vertex_return { "" } else { "" }; - write!(out, "acceleration_structure{}", caps)? - } - _ => unreachable!("invalid TypeInner"), - } - - Ok(()) - } -} - struct WriterTypeContext<'m> { module: &'m Module, names: &'m crate::FastHashMap, diff --git a/naga/src/common/wgsl/mod.rs b/naga/src/common/wgsl/mod.rs index 8a9c6b5b7a..d3418d923c 100644 --- a/naga/src/common/wgsl/mod.rs +++ b/naga/src/common/wgsl/mod.rs @@ -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"; diff --git a/naga/src/common/wgsl/types.rs b/naga/src/common/wgsl/types.rs new file mode 100644 index 0000000000..2a25302ee5 --- /dev/null +++ b/naga/src/common/wgsl/types.rs @@ -0,0 +1,253 @@ +//! Code for formatting Naga IR types as WGSL source code. + +use super::{address_space_str, ToWgsl, TryToWgsl}; +use crate::common; +use crate::{Handle, TypeInner}; + +use core::fmt::Write; + +/// A context for printing Naga IR types as WGSL. +/// +/// This trait's default methods [`write_type`] and +/// [`write_type_inner`] do the work of formatting types as WGSL. +/// Implementors must provide the remaining methods, to customize +/// behavior for the context at hand. +/// +/// For example, the WGSL backend would provide an implementation of +/// [`type_name`] that handles hygienic renaming, whereas the WGSL +/// front end would simply show the name that was given in the source. +/// +/// [`write_type`]: TypeContext::write_type +/// [`write_type_inner`]: TypeContext::write_type_inner +/// [`type_name`]: TypeContext::type_name +pub trait TypeContext { + /// Return the [`Type`] referred to by `handle`. + /// + /// [`Type`]: crate::Type + fn lookup_type(&self, handle: Handle) -> &crate::Type; + + /// Return the name to be used for the type referred to by + /// `handle`. + fn type_name(&self, handle: Handle) -> &str; + + /// Write the WGSL form of `override` to `out`. + fn write_override(&self, r#override: Handle, out: &mut W) + -> core::fmt::Result; + + /// Write the type `ty` as it would appear in a value's declaration. + /// + /// Write the type referred to by `ty` in `module` as it would appear in + /// a `var`, `let`, etc. declaration, or in a function's argument list. + fn write_type(&self, handle: Handle, out: &mut W) -> core::fmt::Result { + let ty = self.lookup_type(handle); + match ty.inner { + TypeInner::Struct { .. } => out.write_str(self.type_name(handle))?, + ref other => self.write_type_inner(other, out)?, + } + + Ok(()) + } + + /// Write the [`TypeInner`] `inner` as it would appear in a value's declaration. + /// + /// Write `inner` as it would appear in a `var`, `let`, etc. + /// declaration, or in a function's argument list. + /// + /// Note that this cannot handle writing [`Struct`] types: those + /// must be referred to by name, but the name isn't available in + /// [`TypeInner`]. + /// + /// [`Struct`]: TypeInner::Struct + fn write_type_inner(&self, inner: &TypeInner, out: &mut W) -> core::fmt::Result { + fn unwrap_to_wgsl(value: T) -> &'static str { + value.try_to_wgsl().unwrap_or_else(|| { + unreachable!( + "validation should have forbidden {}: {value:?}", + T::DESCRIPTION + ); + }) + } + + match *inner { + TypeInner::Vector { size, scalar } => write!( + out, + "vec{}<{}>", + common::vector_size_str(size), + unwrap_to_wgsl(scalar), + )?, + TypeInner::Sampler { comparison: false } => { + write!(out, "sampler")?; + } + TypeInner::Sampler { comparison: true } => { + write!(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 { "" }, + unwrap_to_wgsl(crate::Scalar { kind, width: 4 }), + "", + ), + 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!( + out, + "texture_{class_str}{multisampled_str}{dim_str}{arrayed_str}" + )?; + + if !format_str.is_empty() { + write!(out, "<{format_str}{storage_str}>")?; + } + } + TypeInner::Scalar(scalar) => { + write!(out, "{}", unwrap_to_wgsl(scalar))?; + } + TypeInner::Atomic(scalar) => { + write!(out, "atomic<{}>", unwrap_to_wgsl(scalar))?; + } + TypeInner::Array { + base, + size, + stride: _, + } => { + // More info https://gpuweb.github.io/gpuweb/wgsl/#array-types + // array -- Constant array + // array -- Dynamic array + write!(out, "array<")?; + match size { + crate::ArraySize::Constant(len) => { + self.write_type(base, out)?; + write!(out, ", {len}")?; + } + crate::ArraySize::Pending(r#override) => { + self.write_override(r#override, out)?; + } + crate::ArraySize::Dynamic => { + self.write_type(base, out)?; + } + } + write!(out, ">")?; + } + TypeInner::BindingArray { base, size } => { + // More info https://github.com/gpuweb/gpuweb/issues/2105 + write!(out, "binding_array<")?; + match size { + crate::ArraySize::Constant(len) => { + self.write_type(base, out)?; + write!(out, ", {len}")?; + } + crate::ArraySize::Pending(r#override) => { + self.write_override(r#override, out)?; + } + crate::ArraySize::Dynamic => { + self.write_type(base, out)?; + } + } + write!(out, ">")?; + } + TypeInner::Matrix { + columns, + rows, + scalar, + } => { + write!( + out, + "mat{}x{}<{}>", + common::vector_size_str(columns), + common::vector_size_str(rows), + unwrap_to_wgsl(scalar) + )?; + } + 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!(out, "ptr<{space}, ")?; + } + self.write_type(base, out)?; + if address.is_some() { + if let Some(access) = maybe_access { + write!(out, ", {access}")?; + } + write!(out, ">")?; + } + } + TypeInner::ValuePointer { + size: None, + scalar, + space, + } => { + let (address, maybe_access) = address_space_str(space); + if let Some(space) = address { + write!(out, "ptr<{}, {}", space, unwrap_to_wgsl(scalar))?; + if let Some(access) = maybe_access { + write!(out, ", {access}")?; + } + write!(out, ">")?; + } else { + unreachable!("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!( + out, + "ptr<{}, vec{}<{}>", + space, + common::vector_size_str(size), + unwrap_to_wgsl(scalar) + )?; + if let Some(access) = maybe_access { + write!(out, ", {access}")?; + } + write!(out, ">")?; + } else { + unreachable!("ValuePointer to AddressSpace::Handle {inner:?}"); + } + write!(out, ">")?; + } + TypeInner::AccelerationStructure { vertex_return } => { + let caps = if vertex_return { "" } else { "" }; + write!(out, "acceleration_structure{}", caps)? + } + _ => unreachable!("invalid TypeInner"), + } + + Ok(()) + } +}