use std::{borrow::Cow, error::Error, fmt};
use codespan_reporting::{
diagnostic::{Diagnostic, Label},
files::SimpleFile,
term::{self, termcolor::Buffer},
};
use naga::FastHashMap;
use crate::backend::Library;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ShaderStage {
Vertex,
Fragment,
Compute,
}
impl fmt::Display for ShaderStage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ShaderStage::Vertex => write!(f, "vertex"),
ShaderStage::Fragment => write!(f, "fragment"),
ShaderStage::Compute => write!(f, "compute"),
}
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct ShaderStages : u32 {
const VERTEX = 1 << ShaderStage::Vertex as u32;
const FRAGMENT = 1 << ShaderStage::Fragment as u32;
const COMPUTE = 1 << ShaderStage::Compute as u32;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ShaderLanguage {
SpirV,
Wgsl,
Glsl { stage: ShaderStage },
Msl,
}
impl fmt::Display for ShaderLanguage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ShaderLanguage::SpirV => write!(f, "SPIR-V"),
ShaderLanguage::Wgsl => write!(f, "WGSL"),
ShaderLanguage::Glsl { stage } => write!(f, "GLSL {}", stage),
ShaderLanguage::Msl => write!(f, "MSL"),
}
}
}
#[derive(Clone, Debug, PartialEq, Hash)]
pub struct ShaderSource<'a> {
pub code: Cow<'a, [u8]>,
pub filename: Option<&'a str>,
pub language: ShaderLanguage,
}
impl<'a> ShaderSource<'a> {
pub fn wgsl(code: impl Into<Cow<'a, str>>) -> Self {
ShaderSource {
code: match code.into() {
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
Cow::Owned(s) => Cow::Owned(s.into_bytes()),
},
filename: None,
language: ShaderLanguage::Wgsl,
}
}
pub fn glsl_vert(code: impl Into<Cow<'a, str>>) -> Self {
ShaderSource {
code: match code.into() {
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
Cow::Owned(s) => Cow::Owned(s.into_bytes()),
},
filename: None,
language: ShaderLanguage::Glsl {
stage: ShaderStage::Vertex,
},
}
}
pub fn glsl_frag(code: impl Into<Cow<'a, str>>) -> Self {
ShaderSource {
code: match code.into() {
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
Cow::Owned(s) => Cow::Owned(s.into_bytes()),
},
filename: None,
language: ShaderLanguage::Glsl {
stage: ShaderStage::Fragment,
},
}
}
pub fn glsl_comp(code: impl Into<Cow<'a, str>>) -> Self {
ShaderSource {
code: match code.into() {
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
Cow::Owned(s) => Cow::Owned(s.into_bytes()),
},
filename: None,
language: ShaderLanguage::Glsl {
stage: ShaderStage::Compute,
},
}
}
pub fn msl(code: impl Into<Cow<'a, str>>) -> Self {
ShaderSource {
code: match code.into() {
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
Cow::Owned(s) => Cow::Owned(s.into_bytes()),
},
filename: None,
language: ShaderLanguage::Msl,
}
}
}
#[macro_export]
macro_rules! include_shader_source {
($lang:tt $filename:expr) => {
$crate::for_macro::ShaderSource {
code: ::std::borrow::Cow::Borrowed(::std::include_bytes!($filename)),
filename: ::std::option::Option::Some($filename),
language: $crate::ShaderLanguage::$lang,
}
};
}
#[derive(Clone, Debug, PartialEq, Hash)]
pub enum LibraryInput<'a> {
Source(ShaderSource<'a>),
}
impl<'a> LibraryInput<'a> {
pub fn wgsl(code: impl Into<Cow<'a, str>>) -> Self {
LibraryInput::Source(ShaderSource::wgsl(code))
}
pub fn glsl_vert(code: impl Into<Cow<'a, str>>) -> Self {
LibraryInput::Source(ShaderSource::glsl_vert(code))
}
pub fn glsl_frag(code: impl Into<Cow<'a, str>>) -> Self {
LibraryInput::Source(ShaderSource::glsl_frag(code))
}
pub fn glsl_comp(code: impl Into<Cow<'a, str>>) -> Self {
LibraryInput::Source(ShaderSource::glsl_comp(code))
}
pub fn msl(code: impl Into<Cow<'a, str>>) -> Self {
LibraryInput::Source(ShaderSource::msl(code))
}
}
impl<'a> From<ShaderSource<'a>> for LibraryInput<'a> {
#[inline(always)]
fn from(source: ShaderSource<'a>) -> Self {
LibraryInput::Source(source)
}
}
#[macro_export]
macro_rules! include_library {
($lang:tt $filename:expr) => {
$crate::for_macro::LibraryInput::Source($crate::include_shader_source!($lang $filename))
};
}
#[derive(Clone, Debug, PartialEq, Hash)]
pub struct LibraryDesc<'a> {
pub name: &'a str,
pub input: LibraryInput<'a>,
}
#[derive(Clone)]
pub struct Shader<'a> {
pub library: Library,
pub entry: Cow<'a, str>,
}
#[derive(Debug)]
pub enum ShaderLibraryError {
NonUtf8(std::str::Utf8Error),
ParseSpirV(naga::front::spv::Error),
ParseWgsl(naga::front::wgsl::ParseError),
ParseGlsl(naga::front::glsl::ParseErrors),
ValidationFailed,
#[cfg(any(windows, all(unix, not(any(target_os = "macos", target_os = "ios")))))]
GenSpirV(naga::back::spv::Error),
#[cfg(any(target_os = "macos", target_os = "ios"))]
GenMsl(naga::back::msl::Error),
}
impl fmt::Display for ShaderLibraryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ShaderLibraryError::NonUtf8(err) => write!(f, "non-utf8: {}", err),
ShaderLibraryError::ParseSpirV(err) => write!(f, "parse SPIR-V: {}", err),
ShaderLibraryError::ParseWgsl(err) => write!(f, "parse WGSL: {}", err),
ShaderLibraryError::ParseGlsl(err) => write!(f, "parse GLSL: {}", err),
ShaderLibraryError::ValidationFailed => write!(f, "validation failed"),
#[cfg(any(windows, all(unix, not(any(target_os = "macos", target_os = "ios")))))]
ShaderLibraryError::GenSpirV(err) => write!(f, "generate SPIR-V: {}", err),
#[cfg(any(target_os = "macos", target_os = "ios"))]
ShaderLibraryError::GenMsl(err) => write!(f, "generate MSL: {}", err),
}
}
}
pub(crate) fn parse_shader<'a>(
code: &'a [u8],
filename: Option<&str>,
lang: ShaderLanguage,
) -> Result<(naga::Module, naga::valid::ModuleInfo, Option<&'a str>), ShaderLibraryError> {
let mut source_code = None;
let module = match lang {
ShaderLanguage::SpirV => {
naga::front::spv::parse_u8_slice(code, &naga::front::spv::Options::default())
.map_err(ShaderLibraryError::ParseSpirV)?
}
ShaderLanguage::Msl => {
unimplemented!("Compilation from MSL is not supported")
}
ShaderLanguage::Wgsl => {
let code = std::str::from_utf8(code).map_err(ShaderLibraryError::NonUtf8)?;
source_code = Some(code);
naga::front::wgsl::parse_str(code).map_err(ShaderLibraryError::ParseWgsl)?
}
ShaderLanguage::Glsl { stage } => {
let code = std::str::from_utf8(code).map_err(ShaderLibraryError::NonUtf8)?;
source_code = Some(code);
naga::front::glsl::Frontend::default()
.parse(
&naga::front::glsl::Options {
defines: FastHashMap::default(),
stage: match stage {
ShaderStage::Vertex => naga::ShaderStage::Vertex,
ShaderStage::Fragment => naga::ShaderStage::Fragment,
ShaderStage::Compute => naga::ShaderStage::Compute,
},
},
code,
)
.map_err(ShaderLibraryError::ParseGlsl)?
}
};
let flags = naga::valid::ValidationFlags::all();
let caps = naga::valid::Capabilities::all();
let info = naga::valid::Validator::new(flags, caps)
.validate(&module)
.map_err(|e| {
emit_annotated_error(
&e,
filename.and_then(|filename| {
std::str::from_utf8(code)
.ok()
.map(|source| (filename, source))
}),
);
ShaderLibraryError::ValidationFailed
})?;
Ok((module, info, source_code))
}
fn emit_annotated_error<E: std::error::Error>(
error: &naga::WithSpan<E>,
file: Option<(&str, &str)>,
) {
if let Some((filename, source)) = file {
let files = SimpleFile::new(filename, source);
let config = term::Config::default();
let mut writer = Buffer::no_color();
let diagnostic = Diagnostic::error().with_labels(
error
.spans()
.map(|(span, desc)| {
Label::primary((), span.to_range().unwrap()).with_message(desc.to_owned())
})
.collect(),
);
term::emit_to_io_write(&mut writer, &config, &files, &diagnostic)
.expect("cannot write error");
if let Ok(s) = std::str::from_utf8(writer.as_slice()) {
tracing::event!(
target: "naga",
tracing::Level::ERROR,
error = error.as_inner().to_string(),
diagnostic = s,
);
return;
}
}
tracing::event!(
target: "naga",
tracing::Level::ERROR,
error = error.as_inner().to_string(),
);
}