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