bevy/crates/bevy_render/src/render_resource/shader.rs
Joseph d5d355ae1f
Fix the clippy::explicit_iter_loop lint (#9834)
# Objective

Replace instances of

```rust
for x in collection.iter{_mut}() {
```

with

```rust
for x in &{mut} collection {
```

This also changes CI to no longer suppress this lint. Note that since
this lint only shows up when using clippy in pedantic mode, it was
probably unnecessary to suppress this lint in the first place.
2023-09-19 03:35:22 +00:00

331 lines
10 KiB
Rust

use super::ShaderDefVal;
use crate::define_atomic_id;
use bevy_asset::{anyhow, io::Reader, Asset, AssetLoader, AssetPath, Handle, LoadContext};
use bevy_reflect::TypePath;
use bevy_utils::{tracing::error, BoxedFuture};
use futures_lite::AsyncReadExt;
use std::{borrow::Cow, marker::Copy};
use thiserror::Error;
define_atomic_id!(ShaderId);
#[derive(Error, Debug)]
pub enum ShaderReflectError {
#[error(transparent)]
WgslParse(#[from] naga::front::wgsl::ParseError),
#[cfg(feature = "shader_format_glsl")]
#[error("GLSL Parse Error: {0:?}")]
GlslParse(Vec<naga::front::glsl::Error>),
#[cfg(feature = "shader_format_spirv")]
#[error(transparent)]
SpirVParse(#[from] naga::front::spv::Error),
#[error(transparent)]
Validation(#[from] naga::WithSpan<naga::valid::ValidationError>),
}
/// A shader, as defined by its [`ShaderSource`](wgpu::ShaderSource) and [`ShaderStage`](naga::ShaderStage)
/// This is an "unprocessed" shader. It can contain preprocessor directives.
#[derive(Asset, TypePath, Debug, Clone)]
pub struct Shader {
pub path: String,
pub source: Source,
pub import_path: ShaderImport,
pub imports: Vec<ShaderImport>,
// extra imports not specified in the source string
pub additional_imports: Vec<naga_oil::compose::ImportDefinition>,
// any shader defs that will be included when this module is used
pub shader_defs: Vec<ShaderDefVal>,
}
impl Shader {
fn preprocess(source: &str, path: &str) -> (ShaderImport, Vec<ShaderImport>) {
let (import_path, imports, _) = naga_oil::compose::get_preprocessor_data(source);
let import_path = import_path
.map(ShaderImport::Custom)
.unwrap_or_else(|| ShaderImport::AssetPath(path.to_owned()));
let imports = imports
.into_iter()
.map(|import| {
if import.import.starts_with('\"') {
let import = import
.import
.chars()
.skip(1)
.take_while(|c| *c != '\"')
.collect();
ShaderImport::AssetPath(import)
} else {
ShaderImport::Custom(import.import)
}
})
.collect();
(import_path, imports)
}
pub fn from_wgsl(source: impl Into<Cow<'static, str>>, path: impl Into<String>) -> Shader {
let source = source.into();
let path = path.into();
let (import_path, imports) = Shader::preprocess(&source, &path);
Shader {
path,
imports,
import_path,
source: Source::Wgsl(source),
additional_imports: Default::default(),
shader_defs: Default::default(),
}
}
pub fn from_wgsl_with_defs(
source: impl Into<Cow<'static, str>>,
path: impl Into<String>,
shader_defs: Vec<ShaderDefVal>,
) -> Shader {
Self {
shader_defs,
..Self::from_wgsl(source, path)
}
}
pub fn from_glsl(
source: impl Into<Cow<'static, str>>,
stage: naga::ShaderStage,
path: impl Into<String>,
) -> Shader {
let source = source.into();
let path = path.into();
let (import_path, imports) = Shader::preprocess(&source, &path);
Shader {
path,
imports,
import_path,
source: Source::Glsl(source, stage),
additional_imports: Default::default(),
shader_defs: Default::default(),
}
}
pub fn from_spirv(source: impl Into<Cow<'static, [u8]>>, path: impl Into<String>) -> Shader {
let path = path.into();
Shader {
path: path.clone(),
imports: Vec::new(),
import_path: ShaderImport::AssetPath(path),
source: Source::SpirV(source.into()),
additional_imports: Default::default(),
shader_defs: Default::default(),
}
}
pub fn set_import_path<P: Into<String>>(&mut self, import_path: P) {
self.import_path = ShaderImport::Custom(import_path.into());
}
#[must_use]
pub fn with_import_path<P: Into<String>>(mut self, import_path: P) -> Self {
self.set_import_path(import_path);
self
}
#[inline]
pub fn import_path(&self) -> &ShaderImport {
&self.import_path
}
pub fn imports(&self) -> impl ExactSizeIterator<Item = &ShaderImport> {
self.imports.iter()
}
}
impl<'a> From<&'a Shader> for naga_oil::compose::ComposableModuleDescriptor<'a> {
fn from(shader: &'a Shader) -> Self {
let shader_defs = shader
.shader_defs
.iter()
.map(|def| match def {
ShaderDefVal::Bool(name, b) => {
(name.clone(), naga_oil::compose::ShaderDefValue::Bool(*b))
}
ShaderDefVal::Int(name, i) => {
(name.clone(), naga_oil::compose::ShaderDefValue::Int(*i))
}
ShaderDefVal::UInt(name, i) => {
(name.clone(), naga_oil::compose::ShaderDefValue::UInt(*i))
}
})
.collect();
let as_name = match &shader.import_path {
ShaderImport::AssetPath(asset_path) => Some(format!("\"{asset_path}\"")),
ShaderImport::Custom(_) => None,
};
naga_oil::compose::ComposableModuleDescriptor {
source: shader.source.as_str(),
file_path: &shader.path,
language: (&shader.source).into(),
additional_imports: &shader.additional_imports,
shader_defs,
as_name,
}
}
}
impl<'a> From<&'a Shader> for naga_oil::compose::NagaModuleDescriptor<'a> {
fn from(shader: &'a Shader) -> Self {
naga_oil::compose::NagaModuleDescriptor {
source: shader.source.as_str(),
file_path: &shader.path,
shader_type: (&shader.source).into(),
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub enum Source {
Wgsl(Cow<'static, str>),
Glsl(Cow<'static, str>, naga::ShaderStage),
SpirV(Cow<'static, [u8]>),
// TODO: consider the following
// PrecompiledSpirVMacros(HashMap<HashSet<String>, Vec<u32>>)
// NagaModule(Module) ... Module impls Serialize/Deserialize
}
impl Source {
pub fn as_str(&self) -> &str {
match self {
Source::Wgsl(s) | Source::Glsl(s, _) => s,
Source::SpirV(_) => panic!("spirv not yet implemented"),
}
}
}
impl From<&Source> for naga_oil::compose::ShaderLanguage {
fn from(value: &Source) -> Self {
match value {
Source::Wgsl(_) => naga_oil::compose::ShaderLanguage::Wgsl,
Source::Glsl(_, _) => naga_oil::compose::ShaderLanguage::Glsl,
Source::SpirV(_) => panic!("spirv not yet implemented"),
}
}
}
impl From<&Source> for naga_oil::compose::ShaderType {
fn from(value: &Source) -> Self {
match value {
Source::Wgsl(_) => naga_oil::compose::ShaderType::Wgsl,
Source::Glsl(_, naga::ShaderStage::Vertex) => naga_oil::compose::ShaderType::GlslVertex,
Source::Glsl(_, naga::ShaderStage::Fragment) => {
naga_oil::compose::ShaderType::GlslFragment
}
Source::Glsl(_, naga::ShaderStage::Compute) => {
panic!("glsl compute not yet implemented")
}
Source::SpirV(_) => panic!("spirv not yet implemented"),
}
}
}
#[derive(Default)]
pub struct ShaderLoader;
impl AssetLoader for ShaderLoader {
type Asset = Shader;
type Settings = ();
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a Self::Settings,
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<Shader, anyhow::Error>> {
Box::pin(async move {
let ext = load_context.path().extension().unwrap().to_str().unwrap();
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let shader = match ext {
"spv" => Shader::from_spirv(bytes, load_context.path().to_string_lossy()),
"wgsl" => Shader::from_wgsl(
String::from_utf8(bytes)?,
load_context.path().to_string_lossy(),
),
"vert" => Shader::from_glsl(
String::from_utf8(bytes)?,
naga::ShaderStage::Vertex,
load_context.path().to_string_lossy(),
),
"frag" => Shader::from_glsl(
String::from_utf8(bytes)?,
naga::ShaderStage::Fragment,
load_context.path().to_string_lossy(),
),
"comp" => Shader::from_glsl(
String::from_utf8(bytes)?,
naga::ShaderStage::Compute,
load_context.path().to_string_lossy(),
),
_ => panic!("unhandled extension: {ext}"),
};
// collect file dependencies
for import in &shader.imports {
if let ShaderImport::AssetPath(asset_path) = import {
// TODO: should we just allow this handle to be dropped?
let _handle: Handle<Shader> = load_context.load(asset_path);
}
}
Ok(shader)
})
}
fn extensions(&self) -> &[&str] {
&["spv", "wgsl", "vert", "frag", "comp"]
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum ShaderImport {
AssetPath(String),
Custom(String),
}
impl ShaderImport {
pub fn module_name(&self) -> Cow<'_, String> {
match self {
ShaderImport::AssetPath(s) => Cow::Owned(format!("\"{s}\"")),
ShaderImport::Custom(s) => Cow::Borrowed(s),
}
}
}
/// A reference to a shader asset.
pub enum ShaderRef {
/// Use the "default" shader for the current context.
Default,
/// A handle to a shader stored in the [`Assets<Shader>`](bevy_asset::Assets) resource
Handle(Handle<Shader>),
/// An asset path leading to a shader
Path(AssetPath<'static>),
}
impl From<Handle<Shader>> for ShaderRef {
fn from(handle: Handle<Shader>) -> Self {
Self::Handle(handle)
}
}
impl From<AssetPath<'static>> for ShaderRef {
fn from(path: AssetPath<'static>) -> Self {
Self::Path(path)
}
}
impl From<&'static str> for ShaderRef {
fn from(path: &'static str) -> Self {
Self::Path(AssetPath::from(path))
}
}