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 # Include a default font, containing only ASCII characters, at the cost of a 20kB binary size increase
default_font = ["bevy_internal/default_font"] 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] [dependencies]
bevy_dylib = { path = "crates/bevy_dylib", version = "0.11.0-dev", default-features = false, optional = true } 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 } 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-vorbis = ["bevy_audio/symphonia-vorbis"]
symphonia-wav = ["bevy_audio/symphonia-wav"] 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 # Enable watching file system for asset hot reload
filesystem_watcher = ["bevy_asset/filesystem_watcher"] filesystem_watcher = ["bevy_asset/filesystem_watcher"]

View File

@ -18,6 +18,9 @@ bmp = ["image/bmp"]
webp = ["image/webp"] webp = ["image/webp"]
dds = ["ddsfile"] 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 # For ktx2 supercompression
zlib = ["flate2"] zlib = ["flate2"]
zstd = ["ruzstd"] zstd = ["ruzstd"]
@ -52,10 +55,10 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.11.0-dev" }
image = { version = "0.24", default-features = false } image = { version = "0.24", default-features = false }
# misc # misc
wgpu = { version = "0.15.0", features = ["spirv"] } wgpu = { version = "0.15.0" }
wgpu-hal = "0.15.1" wgpu-hal = "0.15.1"
codespan-reporting = "0.11.0" 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"] } serde = { version = "1", features = ["derive"] }
bitflags = "1.2.1" bitflags = "1.2.1"
smallvec = { version = "1.6", features = ["union", "const_generics"] } 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); let msg = error.emit_to_string(source);
error!("failed to process shader:\n{}", msg); error!("failed to process shader:\n{}", msg);
} }
#[cfg(feature = "shader_format_glsl")]
ShaderReflectError::GlslParse(errors) => { ShaderReflectError::GlslParse(errors) => {
let source = source let source = source
.get_glsl_source() .get_glsl_source()
@ -776,6 +777,7 @@ fn log_shader_error(source: &ProcessedShader, error: &AsModuleDescriptorError) {
error!("failed to process shader: \n{}", msg); error!("failed to process shader: \n{}", msg);
} }
#[cfg(feature = "shader_format_spirv")]
ShaderReflectError::SpirVParse(error) => { ShaderReflectError::SpirVParse(error) => {
error!("failed to process shader:\n{}", 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); error!("failed to process shader: \n{}", msg);
} }
}, },
#[cfg(feature = "shader_format_glsl")]
AsModuleDescriptorError::WgslConversion(error) => { AsModuleDescriptorError::WgslConversion(error) => {
error!("failed to convert shader to wgsl: \n{}", error); error!("failed to convert shader to wgsl: \n{}", error);
} }
#[cfg(feature = "shader_format_spirv")]
AsModuleDescriptorError::SpirVConversion(error) => { AsModuleDescriptorError::SpirVConversion(error) => {
error!("failed to convert shader to spirv: \n{}", 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_asset::{AssetLoader, AssetPath, Handle, LoadContext, LoadedAsset};
use bevy_reflect::TypeUuid; use bevy_reflect::TypeUuid;
use bevy_utils::{tracing::error, BoxedFuture, HashMap}; 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 once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use std::{borrow::Cow, marker::Copy, ops::Deref, path::PathBuf, str::FromStr}; use std::{borrow::Cow, marker::Copy, ops::Deref, path::PathBuf, str::FromStr};
use thiserror::Error; 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); define_atomic_id!(ShaderId);
@ -16,8 +20,10 @@ define_atomic_id!(ShaderId);
pub enum ShaderReflectError { pub enum ShaderReflectError {
#[error(transparent)] #[error(transparent)]
WgslParse(#[from] naga::front::wgsl::ParseError), WgslParse(#[from] naga::front::wgsl::ParseError),
#[cfg(feature = "shader_format_glsl")]
#[error("GLSL Parse Error: {0:?}")] #[error("GLSL Parse Error: {0:?}")]
GlslParse(Vec<naga::front::glsl::Error>), GlslParse(Vec<naga::front::glsl::Error>),
#[cfg(feature = "shader_format_spirv")]
#[error(transparent)] #[error(transparent)]
SpirVParse(#[from] naga::front::spv::Error), SpirVParse(#[from] naga::front::spv::Error),
#[error(transparent)] #[error(transparent)]
@ -120,12 +126,18 @@ impl ProcessedShader {
let module = match &self { let module = match &self {
// TODO: process macros here // TODO: process macros here
ProcessedShader::Wgsl(source) => naga::front::wgsl::parse_str(source)?, ProcessedShader::Wgsl(source) => naga::front::wgsl::parse_str(source)?,
#[cfg(feature = "shader_format_glsl")]
ProcessedShader::Glsl(source, shader_stage) => { ProcessedShader::Glsl(source, shader_stage) => {
let mut parser = naga::front::glsl::Parser::default(); let mut parser = naga::front::glsl::Parser::default();
parser parser
.parse(&naga::front::glsl::Options::from(*shader_stage), source) .parse(&naga::front::glsl::Options::from(*shader_stage), source)
.map_err(ShaderReflectError::GlslParse)? .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( ProcessedShader::SpirV(source) => naga::front::spv::parse_u8_slice(
source, source,
&naga::front::spv::Options { &naga::front::spv::Options {
@ -133,6 +145,10 @@ impl ProcessedShader {
..naga::front::spv::Options::default() ..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)] = &[ const CAPABILITIES: &[(Features, Capabilities)] = &[
(Features::PUSH_CONSTANTS, Capabilities::PUSH_CONSTANT), (Features::PUSH_CONSTANTS, Capabilities::PUSH_CONSTANT),
@ -172,7 +188,7 @@ impl ProcessedShader {
pub fn get_module_descriptor( pub fn get_module_descriptor(
&self, &self,
features: Features, _features: Features,
) -> Result<ShaderModuleDescriptor, AsModuleDescriptorError> { ) -> Result<ShaderModuleDescriptor, AsModuleDescriptorError> {
Ok(ShaderModuleDescriptor { Ok(ShaderModuleDescriptor {
label: None, label: None,
@ -182,18 +198,28 @@ impl ProcessedShader {
// Parse and validate the shader early, so that (e.g. while hot reloading) we can // 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 // display nicely formatted error messages instead of relying on just displaying the error string
// returned by wgpu upon creating the shader module. // returned by wgpu upon creating the shader module.
let _ = self.reflect(features)?; let _ = self.reflect(_features)?;
ShaderSource::Wgsl(source.clone()) ShaderSource::Wgsl(source.clone())
} }
#[cfg(feature = "shader_format_glsl")]
ProcessedShader::Glsl(_source, _stage) => { 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 // TODO: it probably makes more sense to convert this to spirv, but as of writing
// this comment, naga's spirv conversion is broken // this comment, naga's spirv conversion is broken
let wgsl = reflection.get_wgsl()?; let wgsl = reflection.get_wgsl()?;
ShaderSource::Wgsl(wgsl.into()) 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), 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 { pub enum AsModuleDescriptorError {
#[error(transparent)] #[error(transparent)]
ShaderReflectError(#[from] ShaderReflectError), ShaderReflectError(#[from] ShaderReflectError),
#[cfg(feature = "shader_format_glsl")]
#[error(transparent)] #[error(transparent)]
WgslConversion(#[from] naga::back::wgsl::Error), WgslConversion(#[from] naga::back::wgsl::Error),
#[cfg(feature = "shader_format_spirv")]
#[error(transparent)] #[error(transparent)]
SpirVConversion(#[from] naga::back::spv::Error), SpirVConversion(#[from] naga::back::spv::Error),
} }
@ -215,6 +243,7 @@ pub struct ShaderReflection {
} }
impl ShaderReflection { impl ShaderReflection {
#[cfg(feature = "shader_format_spirv")]
pub fn get_spirv(&self) -> Result<Vec<u32>, naga::back::spv::Error> { pub fn get_spirv(&self) -> Result<Vec<u32>, naga::back::spv::Error> {
naga::back::spv::write_vec( naga::back::spv::write_vec(
&self.module, &self.module,
@ -227,6 +256,7 @@ impl ShaderReflection {
) )
} }
#[cfg(feature = "shader_format_glsl")]
pub fn get_wgsl(&self) -> Result<String, naga::back::wgsl::Error> { pub fn get_wgsl(&self) -> Result<String, naga::back::wgsl::Error> {
naga::back::wgsl::write_string(&self.module, &self.module_info, WriterFlags::EXPLICIT_TYPES) 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)| |minimp3|MP3 audio format support (through minimp3)|
|mp3|MP3 audio format support| |mp3|MP3 audio format support|
|serialize|Enable serialization support through serde| |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| |subpixel_glyph_atlas|Enable rendering of font glyphs using subpixel accuracy|
|symphonia-aac|AAC audio format support (through symphonia)| |symphonia-aac|AAC audio format support (through symphonia)|
|symphonia-all|AAC, FLAC, MP3, MP4, OGG/VORBIS, and WAV audio formats support (through symphonia)| |symphonia-all|AAC, FLAC, MP3, MP4, OGG/VORBIS, and WAV audio formats support (through symphonia)|