Skip to main content

fyrox_graphics_gl/
program.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use crate::{
22    server::{GlGraphicsServer, GlKind},
23    ToGlConstant,
24};
25use fyrox_graphics::gpu_program::GpuShader;
26use fyrox_graphics::{
27    core::log::{Log, MessageKind},
28    error::FrameworkError,
29    gpu_program::{
30        GpuProgramTrait, GpuShaderTrait, SamplerKind, ShaderKind, ShaderPropertyKind,
31        ShaderResourceDefinition, ShaderResourceKind,
32    },
33};
34use glow::HasContext;
35use std::{marker::PhantomData, ops::Range, rc::Weak};
36
37fn glsl_sampler_name(sampler: SamplerKind) -> &'static str {
38    match sampler {
39        SamplerKind::Sampler1D => "sampler1D",
40        SamplerKind::Sampler2D => "sampler2D",
41        SamplerKind::Sampler3D => "sampler3D",
42        SamplerKind::SamplerCube => "samplerCube",
43        SamplerKind::USampler1D => "usampler1D",
44        SamplerKind::USampler2D => "usampler2D",
45        SamplerKind::USampler3D => "usampler3D",
46        SamplerKind::USamplerCube => "usamplerCube",
47    }
48}
49
50fn count_lines(src: &str) -> isize {
51    src.bytes().filter(|b| *b == b'\n').count() as isize
52}
53
54enum Vendor {
55    Nvidia,
56    Intel,
57    Amd,
58    Other,
59}
60
61impl Vendor {
62    fn from_str(str: String) -> Self {
63        if str.contains("nvidia") {
64            Self::Nvidia
65        } else if str.contains("amd") {
66            Self::Amd
67        } else if str.contains("intel") {
68            Self::Intel
69        } else {
70            Self::Other
71        }
72    }
73
74    fn regex(&self) -> regex::Regex {
75        match self {
76            Self::Nvidia => regex::Regex::new(r"\([0-9]*\)").unwrap(),
77            Self::Intel | Vendor::Amd => regex::Regex::new(r"[0-9]*:").unwrap(),
78            Self::Other => regex::Regex::new(r":[0-9]*").unwrap(),
79        }
80    }
81
82    fn line_number_range(&self, match_range: regex::Match) -> Range<usize> {
83        match self {
84            Self::Nvidia => (match_range.start() + 1)..(match_range.end() - 1),
85            Self::Intel | Vendor::Amd => match_range.start()..(match_range.end() - 1),
86            Self::Other => (match_range.start() + 1)..match_range.end(),
87        }
88    }
89
90    fn format_line(&self, new_line_number: isize) -> String {
91        match self {
92            Vendor::Nvidia => {
93                format!("({new_line_number})")
94            }
95            Vendor::Intel | Vendor::Amd => {
96                format!("{new_line_number}:")
97            }
98            Vendor::Other => format!(":{new_line_number}"),
99        }
100    }
101}
102
103/// A version of Regex::find_at that does not panic.
104fn find_at<'a>(re: &regex::Regex, src: &'a str, offset: usize) -> Option<regex::Match<'a>> {
105    if offset > src.len() {
106        None
107    } else {
108        re.find_at(src, offset)
109    }
110}
111
112fn patch_error_message(vendor: Vendor, src: &mut String, line_offset: isize) {
113    let re = vendor.regex();
114    let mut offset = 0;
115    while let Some(result) = find_at(&re, src, offset) {
116        offset = result.end().max(offset + 1);
117        let range = vendor.line_number_range(result);
118        let substr = &src[range];
119        if let Ok(line_number) = substr.parse::<isize>() {
120            let new_line_number = line_number + line_offset;
121            let new_substr = vendor.format_line(new_line_number);
122            src.replace_range(result.range(), &new_substr);
123        }
124    }
125}
126
127pub struct GlShader {
128    state: Weak<GlGraphicsServer>,
129    pub id: glow::Shader,
130}
131
132impl ToGlConstant for ShaderKind {
133    fn into_gl(self) -> u32 {
134        match self {
135            ShaderKind::Vertex => glow::VERTEX_SHADER,
136            ShaderKind::Fragment => glow::FRAGMENT_SHADER,
137        }
138    }
139}
140
141impl GpuShaderTrait for GlShader {}
142
143impl GlShader {
144    /// Generate and compile the source code for a shader.
145    /// * `server`: The OpenGL context that is used to compile the shader.
146    /// * `name`: The name of the shader that will be used to log messages about the compilation and report errors.
147    /// * `kind`: Is this a vertex shader or a fragment shader?
148    /// * `source`: The source code for the shader, excluding the uniform declarations that are automatically generated.
149    /// * `resources`: Resource definitions that are used to automatically generate uniform declarations in the source code before compiling.
150    /// * `line_offset`: The position of the first line of source code in whatever file it came from, for error reporting.
151    pub fn new(
152        server: &GlGraphicsServer,
153        name: String,
154        kind: ShaderKind,
155        mut source: String,
156        resources: &[ShaderResourceDefinition],
157        mut line_offset: isize,
158    ) -> Result<Self, FrameworkError> {
159        // Initial validation. The program will be validated once more by the compiler.
160        for resource in resources {
161            for other_resource in resources {
162                if std::ptr::eq(resource, other_resource) {
163                    continue;
164                }
165
166                if std::mem::discriminant(&resource.kind)
167                    == std::mem::discriminant(&other_resource.kind)
168                {
169                    if resource.binding == other_resource.binding {
170                        return Err(FrameworkError::Custom(format!(
171                            "Resource {} and {} using the same binding point {} \
172                            in the {name} GPU program.",
173                            resource.name, other_resource.name, resource.binding
174                        )));
175                    }
176
177                    if resource.name == other_resource.name {
178                        return Err(FrameworkError::Custom(format!(
179                            "There are two or more resources with same name {} \
180                                in the {name} GPU program.",
181                            resource.name
182                        )));
183                    }
184                }
185            }
186        }
187
188        // Generate appropriate texture binding points and uniform blocks for the specified properties.
189
190        let mut texture_bindings = String::new();
191
192        for property in resources {
193            let resource_name = &property.name;
194            match property.kind {
195                ShaderResourceKind::Texture { kind, .. } => {
196                    let glsl_name = glsl_sampler_name(kind);
197                    texture_bindings += &format!("uniform {glsl_name} {resource_name};\n");
198                }
199                ShaderResourceKind::PropertyGroup(ref fields) => {
200                    if fields.is_empty() {
201                        Log::warn(format!(
202                            "Uniform block {resource_name} is empty and will be ignored!"
203                        ));
204                        continue;
205                    }
206                    let mut block = format!("struct T{resource_name}{{\n");
207                    for field in fields {
208                        let field_name = &field.name;
209                        match field.kind {
210                            ShaderPropertyKind::Float { .. } => {
211                                block += &format!("\tfloat {field_name};\n");
212                            }
213                            ShaderPropertyKind::FloatArray { max_len, .. } => {
214                                block += &format!("\tfloat {field_name}[{max_len}];\n");
215                            }
216                            ShaderPropertyKind::Int { .. } => {
217                                block += &format!("\tint {field_name};\n");
218                            }
219                            ShaderPropertyKind::IntArray { max_len, .. } => {
220                                block += &format!("\tint {field_name}[{max_len}];\n");
221                            }
222                            ShaderPropertyKind::UInt { .. } => {
223                                block += &format!("\tuint {field_name};\n");
224                            }
225                            ShaderPropertyKind::UIntArray { max_len, .. } => {
226                                block += &format!("\tuint {field_name}[{max_len}];\n");
227                            }
228                            ShaderPropertyKind::Bool { .. } => {
229                                block += &format!("\tbool {field_name};\n");
230                            }
231                            ShaderPropertyKind::Vector2 { .. } => {
232                                block += &format!("\tvec2 {field_name};\n");
233                            }
234                            ShaderPropertyKind::Vector2Array { max_len, .. } => {
235                                block += &format!("\tvec2 {field_name}[{max_len}];\n");
236                            }
237                            ShaderPropertyKind::Vector3 { .. } => {
238                                block += &format!("\tvec3 {field_name};\n");
239                            }
240                            ShaderPropertyKind::Vector3Array { max_len, .. } => {
241                                block += &format!("\tvec3 {field_name}[{max_len}];\n");
242                            }
243                            ShaderPropertyKind::Vector4 { .. } => {
244                                block += &format!("\tvec4 {field_name};\n");
245                            }
246                            ShaderPropertyKind::Vector4Array { max_len, .. } => {
247                                block += &format!("\tvec4 {field_name}[{max_len}];\n");
248                            }
249                            ShaderPropertyKind::Matrix2 { .. } => {
250                                block += &format!("\tmat2 {field_name};\n");
251                            }
252                            ShaderPropertyKind::Matrix2Array { max_len, .. } => {
253                                block += &format!("\tmat2 {field_name}[{max_len}];\n");
254                            }
255                            ShaderPropertyKind::Matrix3 { .. } => {
256                                block += &format!("\tmat3 {field_name};\n");
257                            }
258                            ShaderPropertyKind::Matrix3Array { max_len, .. } => {
259                                block += &format!("\tmat3 {field_name}[{max_len}];\n");
260                            }
261                            ShaderPropertyKind::Matrix4 { .. } => {
262                                block += &format!("\tmat4 {field_name};\n");
263                            }
264                            ShaderPropertyKind::Matrix4Array { max_len, .. } => {
265                                block += &format!("\tmat4 {field_name}[{max_len}];\n");
266                            }
267                            ShaderPropertyKind::Color { .. } => {
268                                block += &format!("\tvec4 {field_name};\n");
269                            }
270                        }
271                    }
272                    block += "};\n";
273                    block += &format!(
274                        "layout(std140) uniform U{resource_name} {{ T{resource_name} {resource_name}; }};\n"
275                    );
276                    source.insert_str(0, &block);
277                    line_offset -= count_lines(&block);
278                }
279            }
280        }
281        source.insert_str(0, &texture_bindings);
282        line_offset -= count_lines(&texture_bindings);
283
284        unsafe {
285            let gl_kind = server.gl_kind();
286            let initial_lines_count = count_lines(&source);
287            let merged_source = prepare_source_code(&source, gl_kind);
288            line_offset -= count_lines(&merged_source) - initial_lines_count;
289
290            let shader = server.gl.create_shader(kind.into_gl())?;
291            server.gl.shader_source(shader, &merged_source);
292            server.gl.compile_shader(shader);
293
294            let status = server.gl.get_shader_compile_status(shader);
295            let mut compilation_message = server.gl.get_shader_info_log(shader);
296
297            if !status {
298                let vendor_str = server.gl.get_parameter_string(glow::VENDOR).to_lowercase();
299                let vendor = Vendor::from_str(vendor_str);
300                patch_error_message(vendor, &mut compilation_message, line_offset);
301                Log::writeln(
302                    MessageKind::Error,
303                    format!("Failed to compile {name} shader:\n{compilation_message}"),
304                );
305                Err(FrameworkError::ShaderCompilationFailed {
306                    shader_name: name,
307                    error_message: compilation_message,
308                })
309            } else {
310                let msg = if compilation_message.is_empty()
311                    || compilation_message.chars().all(|c| c.is_whitespace())
312                {
313                    format!("Shader {name} compiled successfully!")
314                } else {
315                    format!(
316                        "Shader {name} compiled successfully!\nAdditional info: {compilation_message}"
317                    )
318                };
319
320                Log::writeln(MessageKind::Information, msg);
321
322                Ok(Self {
323                    id: shader,
324                    state: server.weak(),
325                })
326            }
327        }
328    }
329}
330
331impl Drop for GlShader {
332    fn drop(&mut self) {
333        if let Some(state) = self.state.upgrade() {
334            unsafe {
335                state.gl.delete_shader(self.id);
336            }
337        }
338    }
339}
340
341fn prepare_source_code(code: &str, gl_kind: GlKind) -> String {
342    let mut full_source_code = "#version 330 core\n// include 'shared.glsl'\n".to_owned();
343
344    if gl_kind == GlKind::OpenGLES {
345        full_source_code += r#"
346            precision highp float;
347            precision highp int;
348            precision highp usampler2D;
349            precision highp usampler3D;
350            precision highp usamplerCube;
351            precision highp sampler2D;
352            precision highp sampler3D;
353            precision highp samplerCube;
354        "#;
355    }
356
357    full_source_code += include_str!("shaders/shared.glsl");
358    full_source_code += "\n// end of include\n";
359    full_source_code += code;
360
361    if gl_kind == GlKind::OpenGLES {
362        full_source_code.replace("#version 330 core", "#version 300 es")
363    } else {
364        full_source_code
365    }
366}
367
368pub struct GlProgram {
369    state: Weak<GlGraphicsServer>,
370    pub id: glow::Program,
371    // Force compiler to not implement Send and Sync, because OpenGL is not thread-safe.
372    thread_mark: PhantomData<*const u8>,
373}
374
375fn bind_resources(
376    server: &GlGraphicsServer,
377    program: &GlProgram,
378    resources: &[ShaderResourceDefinition],
379) {
380    unsafe {
381        server.set_program(Some(program.id));
382        for resource_definition in resources {
383            match resource_definition.kind {
384                ShaderResourceKind::Texture { .. } => {
385                    if let Some(location) = server
386                        .gl
387                        .get_uniform_location(program.id, &resource_definition.name)
388                    {
389                        server
390                            .gl
391                            .uniform_1_i32(Some(&location), resource_definition.binding as i32);
392                    }
393                }
394                ShaderResourceKind::PropertyGroup { .. } => {
395                    if let Some(shader_block_index) = server.gl.get_uniform_block_index(
396                        program.id,
397                        &format!("U{}", resource_definition.name),
398                    ) {
399                        server.gl.uniform_block_binding(
400                            program.id,
401                            shader_block_index,
402                            resource_definition.binding as u32,
403                        )
404                    } else {
405                        Log::warn(format!(
406                            "Couldn't find uniform block U{}",
407                            resource_definition.name
408                        ));
409                    }
410                }
411            }
412        }
413    }
414}
415
416impl GlProgram {
417    pub fn from_source_and_resources(
418        server: &GlGraphicsServer,
419        program_name: &str,
420        vertex_source: String,
421        vertex_source_line_offset: isize,
422        fragment_source: String,
423        fragment_source_line_offset: isize,
424        resources: &[ShaderResourceDefinition],
425    ) -> Result<GlProgram, FrameworkError> {
426        let program = Self::from_source(
427            server,
428            program_name,
429            vertex_source,
430            vertex_source_line_offset,
431            fragment_source,
432            fragment_source_line_offset,
433            resources,
434        )?;
435
436        bind_resources(server, &program, resources);
437
438        Ok(program)
439    }
440
441    pub fn from_shaders_and_resources(
442        server: &GlGraphicsServer,
443        program_name: &str,
444        vertex_shader: &GpuShader,
445        fragment_shader: &GpuShader,
446        resources: &[ShaderResourceDefinition],
447    ) -> Result<GlProgram, FrameworkError> {
448        let vertex_shader = vertex_shader
449            .as_any()
450            .downcast_ref::<GlShader>()
451            .ok_or_else(|| {
452                FrameworkError::Custom("Unable to downcast the shader to GlShader!".to_string())
453            })?;
454        let fragment_shader = fragment_shader
455            .as_any()
456            .downcast_ref::<GlShader>()
457            .ok_or_else(|| {
458                FrameworkError::Custom("Unable to downcast the shader to GlShader!".to_string())
459            })?;
460        let program = Self::from_shaders(server, program_name, vertex_shader, fragment_shader)?;
461
462        bind_resources(server, &program, resources);
463
464        Ok(program)
465    }
466
467    fn from_source(
468        server: &GlGraphicsServer,
469        name: &str,
470        vertex_source: String,
471        vertex_source_line_offset: isize,
472        fragment_source: String,
473        fragment_source_line_offset: isize,
474        resources: &[ShaderResourceDefinition],
475    ) -> Result<GlProgram, FrameworkError> {
476        let vertex_shader = GlShader::new(
477            server,
478            format!("{name}_VertexShader"),
479            ShaderKind::Vertex,
480            vertex_source,
481            resources,
482            vertex_source_line_offset,
483        )?;
484        let fragment_shader = GlShader::new(
485            server,
486            format!("{name}_FragmentShader"),
487            ShaderKind::Fragment,
488            fragment_source,
489            resources,
490            fragment_source_line_offset,
491        )?;
492        Self::from_shaders(server, name, &vertex_shader, &fragment_shader)
493    }
494
495    fn from_shaders(
496        server: &GlGraphicsServer,
497        name: &str,
498        vertex_shader: &GlShader,
499        fragment_shader: &GlShader,
500    ) -> Result<GlProgram, FrameworkError> {
501        unsafe {
502            let program = server.gl.create_program()?;
503            server.gl.attach_shader(program, vertex_shader.id);
504            server.gl.attach_shader(program, fragment_shader.id);
505            server.gl.link_program(program);
506            let status = server.gl.get_program_link_status(program);
507            let link_message = server.gl.get_program_info_log(program);
508
509            if !status {
510                Log::writeln(
511                    MessageKind::Error,
512                    format!("Failed to link {name} shader: {link_message}"),
513                );
514                Err(FrameworkError::ShaderLinkingFailed {
515                    shader_name: name.to_owned(),
516                    error_message: link_message,
517                })
518            } else {
519                let msg = if link_message.is_empty()
520                    || link_message.chars().all(|c| c.is_whitespace())
521                {
522                    format!("Shader {name} linked successfully!")
523                } else {
524                    format!("Shader {name} linked successfully!\nAdditional info: {link_message}")
525                };
526
527                Log::writeln(MessageKind::Information, msg);
528
529                Ok(Self {
530                    state: server.weak(),
531                    id: program,
532                    thread_mark: PhantomData,
533                })
534            }
535        }
536    }
537}
538
539impl GpuProgramTrait for GlProgram {}
540
541impl Drop for GlProgram {
542    fn drop(&mut self) {
543        if let Some(state) = self.state.upgrade() {
544            state.delete_program(self.id);
545        }
546    }
547}
548
549#[cfg(test)]
550mod test {
551    use crate::program::{patch_error_message, Vendor};
552
553    #[test]
554    fn test_line_correction() {
555        let mut err_msg = r#"0(62) : error C0000: syntax error, unexpected identifier, expecting '{' at token "vertexPosition"
556        0(66) : error C1503: undefined variable "vertexPosition""#.to_string();
557
558        patch_error_message(Vendor::Nvidia, &mut err_msg, 10);
559
560        let expected_result = r#"0(72) : error C0000: syntax error, unexpected identifier, expecting '{' at token "vertexPosition"
561        0(76) : error C1503: undefined variable "vertexPosition""#;
562
563        assert_eq!(err_msg, expected_result);
564    }
565}