make glsl and spirv support optional (#8491)

# Objective

- Reduce compilation time

## Solution

- Make `spirv` and `glsl` shader format support optional. They are not
needed for Bevy shaders.
- on my mac (where shaders are compiled to `msl`), this reduces the
total build time by 2 to 5 seconds, improvement should be even better
with less cores

There is a big reduction in compile time for `naga`, and small
improvements on `wgpu` and `bevy_render`

This PR with optional shader formats enabled timings:
<img width="1478" alt="current main"
src="https://user-images.githubusercontent.com/8672791/234347032-cbd5c276-a9b0-49c3-b793-481677391c18.png">

This PR:
<img width="1479" alt="this pr"
src="https://user-images.githubusercontent.com/8672791/234347059-a67412a9-da8d-4356-91d8-7b0ae84ca100.png">


---

## Migration Guide

- If you want to use shaders in `spirv`, enable the
`shader_format_spirv` feature
- If you want to use shaders in `glsl`, enable the `shader_format_glsl`
feature
This commit is contained in:
François 2023-04-25 21:30:48 +02:00 committed by GitHub
parent dea91e94d6
commit 949487d92c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 56 additions and 7 deletions

View file

@ -229,6 +229,12 @@ glam_assert = ["bevy_internal/glam_assert"]
# Include a default font, containing only ASCII characters, at the cost of a 20kB binary size increase
default_font = ["bevy_internal/default_font"]
# Enable support for shaders in GLSL
shader_format_glsl = ["bevy_internal/shader_format_glsl"]
# Enable support for shaders in SPIR-V
shader_format_spirv = ["bevy_internal/shader_format_spirv"]
[dependencies]
bevy_dylib = { path = "crates/bevy_dylib", version = "0.11.0-dev", default-features = false, optional = true }
bevy_internal = { path = "crates/bevy_internal", version = "0.11.0-dev", default-features = false }

View file

@ -57,6 +57,10 @@ symphonia-isomp4 = ["bevy_audio/symphonia-isomp4"]
symphonia-vorbis = ["bevy_audio/symphonia-vorbis"]
symphonia-wav = ["bevy_audio/symphonia-wav"]
# Shader formats
shader_format_glsl = ["bevy_render/shader_format_glsl"]
shader_format_spirv = ["bevy_render/shader_format_spirv"]
# Enable watching file system for asset hot reload
filesystem_watcher = ["bevy_asset/filesystem_watcher"]

View file

@ -18,6 +18,9 @@ bmp = ["image/bmp"]
webp = ["image/webp"]
dds = ["ddsfile"]
shader_format_glsl = ["naga/glsl-in", "naga/wgsl-out"]
shader_format_spirv = ["wgpu/spirv", "naga/spv-in", "naga/spv-out"]
# For ktx2 supercompression
zlib = ["flate2"]
zstd = ["ruzstd"]
@ -52,10 +55,10 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.11.0-dev" }
image = { version = "0.24", default-features = false }
# misc
wgpu = { version = "0.15.0", features = ["spirv"] }
wgpu = { version = "0.15.0" }
wgpu-hal = "0.15.1"
codespan-reporting = "0.11.0"
naga = { version = "0.11.0", features = ["glsl-in", "spv-in", "spv-out", "wgsl-in", "wgsl-out"] }
naga = { version = "0.11.0", features = ["wgsl-in"] }
serde = { version = "1", features = ["derive"] }
bitflags = "1.2.1"
smallvec = { version = "1.6", features = ["union", "const_generics"] }

View file

@ -752,6 +752,7 @@ fn log_shader_error(source: &ProcessedShader, error: &AsModuleDescriptorError) {
let msg = error.emit_to_string(source);
error!("failed to process shader:\n{}", msg);
}
#[cfg(feature = "shader_format_glsl")]
ShaderReflectError::GlslParse(errors) => {
let source = source
.get_glsl_source()
@ -776,6 +777,7 @@ fn log_shader_error(source: &ProcessedShader, error: &AsModuleDescriptorError) {
error!("failed to process shader: \n{}", msg);
}
#[cfg(feature = "shader_format_spirv")]
ShaderReflectError::SpirVParse(error) => {
error!("failed to process shader:\n{}", error);
}
@ -818,9 +820,11 @@ fn log_shader_error(source: &ProcessedShader, error: &AsModuleDescriptorError) {
error!("failed to process shader: \n{}", msg);
}
},
#[cfg(feature = "shader_format_glsl")]
AsModuleDescriptorError::WgslConversion(error) => {
error!("failed to convert shader to wgsl: \n{}", error);
}
#[cfg(feature = "shader_format_spirv")]
AsModuleDescriptorError::SpirVConversion(error) => {
error!("failed to convert shader to spirv: \n{}", error);
}

View file

@ -3,12 +3,16 @@ use crate::define_atomic_id;
use bevy_asset::{AssetLoader, AssetPath, Handle, LoadContext, LoadedAsset};
use bevy_reflect::TypeUuid;
use bevy_utils::{tracing::error, BoxedFuture, HashMap};
use naga::{back::wgsl::WriterFlags, valid::Capabilities, valid::ModuleInfo, Module};
#[cfg(feature = "shader_format_glsl")]
use naga::back::wgsl::WriterFlags;
use naga::{valid::Capabilities, valid::ModuleInfo, Module};
use once_cell::sync::Lazy;
use regex::Regex;
use std::{borrow::Cow, marker::Copy, ops::Deref, path::PathBuf, str::FromStr};
use thiserror::Error;
use wgpu::{util::make_spirv, Features, ShaderModuleDescriptor, ShaderSource};
#[cfg(feature = "shader_format_spirv")]
use wgpu::util::make_spirv;
use wgpu::{Features, ShaderModuleDescriptor, ShaderSource};
define_atomic_id!(ShaderId);
@ -16,8 +20,10 @@ define_atomic_id!(ShaderId);
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)]
@ -120,12 +126,18 @@ impl ProcessedShader {
let module = match &self {
// TODO: process macros here
ProcessedShader::Wgsl(source) => naga::front::wgsl::parse_str(source)?,
#[cfg(feature = "shader_format_glsl")]
ProcessedShader::Glsl(source, shader_stage) => {
let mut parser = naga::front::glsl::Parser::default();
parser
.parse(&naga::front::glsl::Options::from(*shader_stage), source)
.map_err(ShaderReflectError::GlslParse)?
}
#[cfg(not(feature = "shader_format_glsl"))]
ProcessedShader::Glsl(_source, _shader_stage) => {
unimplemented!("Enable feature \"shader_format_glsl\" to use GLSL shaders")
}
#[cfg(feature = "shader_format_spirv")]
ProcessedShader::SpirV(source) => naga::front::spv::parse_u8_slice(
source,
&naga::front::spv::Options {
@ -133,6 +145,10 @@ impl ProcessedShader {
..naga::front::spv::Options::default()
},
)?,
#[cfg(not(feature = "shader_format_spirv"))]
ProcessedShader::SpirV(_source) => {
unimplemented!("Enable feature \"shader_format_spirv\" to use SPIR-V shaders")
}
};
const CAPABILITIES: &[(Features, Capabilities)] = &[
(Features::PUSH_CONSTANTS, Capabilities::PUSH_CONSTANT),
@ -172,7 +188,7 @@ impl ProcessedShader {
pub fn get_module_descriptor(
&self,
features: Features,
_features: Features,
) -> Result<ShaderModuleDescriptor, AsModuleDescriptorError> {
Ok(ShaderModuleDescriptor {
label: None,
@ -182,18 +198,28 @@ impl ProcessedShader {
// Parse and validate the shader early, so that (e.g. while hot reloading) we can
// display nicely formatted error messages instead of relying on just displaying the error string
// returned by wgpu upon creating the shader module.
let _ = self.reflect(features)?;
let _ = self.reflect(_features)?;
ShaderSource::Wgsl(source.clone())
}
#[cfg(feature = "shader_format_glsl")]
ProcessedShader::Glsl(_source, _stage) => {
let reflection = self.reflect(features)?;
let reflection = self.reflect(_features)?;
// TODO: it probably makes more sense to convert this to spirv, but as of writing
// this comment, naga's spirv conversion is broken
let wgsl = reflection.get_wgsl()?;
ShaderSource::Wgsl(wgsl.into())
}
#[cfg(not(feature = "shader_format_glsl"))]
ProcessedShader::Glsl(_source, _stage) => {
unimplemented!("Enable feature \"shader_format_glsl\" to use GLSL shaders")
}
#[cfg(feature = "shader_format_spirv")]
ProcessedShader::SpirV(source) => make_spirv(source),
#[cfg(not(feature = "shader_format_spirv"))]
ProcessedShader::SpirV(_source) => {
unimplemented!()
}
},
})
}
@ -203,8 +229,10 @@ impl ProcessedShader {
pub enum AsModuleDescriptorError {
#[error(transparent)]
ShaderReflectError(#[from] ShaderReflectError),
#[cfg(feature = "shader_format_glsl")]
#[error(transparent)]
WgslConversion(#[from] naga::back::wgsl::Error),
#[cfg(feature = "shader_format_spirv")]
#[error(transparent)]
SpirVConversion(#[from] naga::back::spv::Error),
}
@ -215,6 +243,7 @@ pub struct ShaderReflection {
}
impl ShaderReflection {
#[cfg(feature = "shader_format_spirv")]
pub fn get_spirv(&self) -> Result<Vec<u32>, naga::back::spv::Error> {
naga::back::spv::write_vec(
&self.module,
@ -227,6 +256,7 @@ impl ShaderReflection {
)
}
#[cfg(feature = "shader_format_glsl")]
pub fn get_wgsl(&self) -> Result<String, naga::back::wgsl::Error> {
naga::back::wgsl::write_string(&self.module, &self.module_info, WriterFlags::EXPLICIT_TYPES)
}

View file

@ -57,6 +57,8 @@ The default feature set enables most of the expected features of a game engine,
|minimp3|MP3 audio format support (through minimp3)|
|mp3|MP3 audio format support|
|serialize|Enable serialization support through serde|
|shader_format_glsl|Enable support for shaders in GLSL|
|shader_format_spirv|Enable support for shaders in SPIR-V|
|subpixel_glyph_atlas|Enable rendering of font glyphs using subpixel accuracy|
|symphonia-aac|AAC audio format support (through symphonia)|
|symphonia-all|AAC, FLAC, MP3, MP4, OGG/VORBIS, and WAV audio formats support (through symphonia)|