From 949487d92c1fde8735ded143c791f94c8117f5b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Tue, 25 Apr 2023 21:30:48 +0200 Subject: [PATCH] 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: current main This PR: this pr --- ## 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 --- Cargo.toml | 6 +++ crates/bevy_internal/Cargo.toml | 4 ++ crates/bevy_render/Cargo.toml | 7 +++- .../src/render_resource/pipeline_cache.rs | 4 ++ .../bevy_render/src/render_resource/shader.rs | 40 ++++++++++++++++--- docs/cargo_features.md | 2 + 6 files changed, 56 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ae769ee50f652..4e25ee76b4d89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 51fe644c92969..4c828dd848b68 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -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"] diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index e10f7f6da5841..a75130c133227 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -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"] } diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index 783d35abb2c78..f958ee443b811 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -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); } diff --git a/crates/bevy_render/src/render_resource/shader.rs b/crates/bevy_render/src/render_resource/shader.rs index a0b874c092f18..95116432b23f2 100644 --- a/crates/bevy_render/src/render_resource/shader.rs +++ b/crates/bevy_render/src/render_resource/shader.rs @@ -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), + #[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 { 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, 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 { naga::back::wgsl::write_string(&self.module, &self.module_info, WriterFlags::EXPLICIT_TYPES) } diff --git a/docs/cargo_features.md b/docs/cargo_features.md index d2a876d10f525..28d4b205d9ac1 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -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)|