veloren_voxygen/render/renderer/
compiler.rs

1use common_base::prof_span;
2use tracing::info;
3
4use crate::render::RenderError;
5
6pub(super) enum ShaderStage {
7    Vertex,
8    Fragment,
9}
10
11impl From<ShaderStage> for wgpu::naga::ShaderStage {
12    fn from(value: ShaderStage) -> Self {
13        match value {
14            ShaderStage::Vertex => wgpu::naga::ShaderStage::Vertex,
15            ShaderStage::Fragment => wgpu::naga::ShaderStage::Fragment,
16        }
17    }
18}
19
20impl From<ShaderStage> for shaderc::ShaderKind {
21    fn from(value: ShaderStage) -> Self {
22        match value {
23            ShaderStage::Vertex => shaderc::ShaderKind::Vertex,
24            ShaderStage::Fragment => shaderc::ShaderKind::Fragment,
25        }
26    }
27}
28
29pub(super) trait Compiler {
30    fn create_shader_module(
31        &mut self,
32        device: &wgpu::Device,
33        source: &str,
34        stage: ShaderStage,
35        name: &str,
36    ) -> Result<wgpu::ShaderModule, RenderError>;
37}
38
39pub(super) struct ShaderCCompiler {
40    compiler: shaderc::Compiler,
41    options: shaderc::CompileOptions<'static>,
42}
43
44impl ShaderCCompiler {
45    pub(super) fn new(
46        optimize: bool,
47        resolve_include: impl Fn(&str, &str) -> Result<String, String> + 'static,
48    ) -> Result<Self, RenderError> {
49        let compiler = shaderc::Compiler::new().ok_or(RenderError::ErrorInitializingCompiler)?;
50        let mut options =
51            shaderc::CompileOptions::new().ok_or(RenderError::ErrorInitializingCompiler)?;
52
53        if optimize {
54            options.set_optimization_level(shaderc::OptimizationLevel::Performance);
55            info!("Enabled optimization by shaderc.");
56        } else {
57            options.set_optimization_level(shaderc::OptimizationLevel::Zero);
58            info!("Disabled optimization by shaderc.");
59        }
60        options.set_forced_version_profile(430, shaderc::GlslProfile::Core);
61        // options.set_generate_debug_info();
62        options.set_include_callback(move |name, _, shader_name, _| {
63            Ok(shaderc::ResolvedInclude {
64                resolved_name: name.to_string(),
65                content: resolve_include(name, shader_name)?,
66            })
67        });
68
69        Ok(Self { compiler, options })
70    }
71}
72
73impl Compiler for ShaderCCompiler {
74    fn create_shader_module(
75        &mut self,
76        device: &wgpu::Device,
77        source: &str,
78        stage: ShaderStage,
79        name: &str,
80    ) -> Result<wgpu::ShaderModule, RenderError> {
81        prof_span!(_guard, "create_shader_modules");
82        use std::borrow::Cow;
83
84        let file_name = format!("{}.glsl", name);
85        let file_name = file_name.as_str();
86
87        let spv = self
88            .compiler
89            .compile_into_spirv(source, stage.into(), file_name, "main", Some(&self.options))
90            .map_err(|e| (file_name, e))?;
91
92        // Uncomment me to dump shaders to files
93        //
94        // std::fs::create_dir_all("dumpped-shaders").expect("Couldn't create shader
95        // dumps folders");
96        //
97        // let mut file = std::fs::File::create(format!("dumpped-shaders/{}.spv",
98        // file_name))     .expect("Couldn't open shader out");
99        //
100        // use std::io::Write;
101        //
102        // file.write(spv.as_binary_u8())
103        //     .expect("Couldn't write shader out");
104
105        // let label = [file_name, "\n\n", source].concat();
106
107        let descriptor = wgpu::ShaderModuleDescriptor {
108            label: Some(file_name),
109            source: wgpu::ShaderSource::SpirV(Cow::Borrowed(spv.as_binary())),
110        };
111        let runtimechecks = wgpu::ShaderRuntimeChecks::unchecked();
112        #[expect(unsafe_code)]
113        Ok(unsafe { device.create_shader_module_trusted(descriptor, runtimechecks) })
114    }
115}
116
117pub(super) struct WgpuCompiler {
118    reg: regex::Regex,
119    resolve_include: Box<dyn Fn(&str, &str) -> Result<String, String> + 'static>,
120}
121
122impl WgpuCompiler {
123    pub(super) fn new(
124        resolve_include: impl Fn(&str, &str) -> Result<String, String> + 'static,
125    ) -> Result<Self, RenderError> {
126        let reg = regex::Regex::new("(?mR)^#include +<(.+)>$").unwrap();
127        Ok(Self {
128            reg,
129            resolve_include: Box::new(resolve_include),
130        })
131    }
132}
133
134impl Compiler for WgpuCompiler {
135    fn create_shader_module(
136        &mut self,
137        device: &wgpu::Device,
138        source: &str,
139        stage: ShaderStage,
140        name: &str,
141    ) -> Result<wgpu::ShaderModule, RenderError> {
142        use std::borrow::Cow;
143
144        prof_span!(_guard, "create_shader_modules");
145
146        let label = name;
147
148        device.push_error_scope(wgpu::ErrorFilter::Validation);
149
150        // replace all `includes` recursivly
151        let mut source = Cow::Borrowed(source);
152        let source = loop {
153            let resolve_includes = self.reg.replace_all(&source, |cap: &regex::Captures| {
154                (self.resolve_include)(cap.get(1).unwrap().as_str(), name).unwrap() //TODO unwrap! replace with https://docs.rs/regex/latest/regex/struct.Regex.html#fallibility
155            });
156
157            match resolve_includes {
158                Cow::Borrowed(source) => break source,
159                Cow::Owned(s) => source = Cow::Owned(s),
160            }
161        };
162
163        let descriptor = wgpu::ShaderModuleDescriptor {
164            label: Some(label),
165            source: wgpu::ShaderSource::Glsl {
166                shader: Cow::Borrowed(source),
167                stage: stage.into(),
168                defines: &[],
169            },
170        };
171        let runtimechecks = wgpu::ShaderRuntimeChecks::unchecked();
172        #[expect(unsafe_code)]
173        let shader = unsafe { device.create_shader_module_trusted(descriptor, runtimechecks) };
174
175        let rt = tokio::runtime::Runtime::new().unwrap();
176
177        if let Some(error) = rt.block_on(device.pop_error_scope()) {
178            Err(RenderError::ShaderWgpuError(label.to_owned(), error))
179        } else {
180            Ok(shader)
181        }
182    }
183}