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    core::log::{Log, MessageKind},
23    error::FrameworkError,
24    gl::server::{GlGraphicsServer, GlKind},
25    gpu_program::{
26        GpuProgramTrait, SamplerKind, ShaderPropertyKind, ShaderResourceDefinition,
27        ShaderResourceKind,
28    },
29};
30use glow::HasContext;
31use std::{marker::PhantomData, rc::Weak};
32
33impl SamplerKind {
34    pub fn glsl_name(&self) -> &str {
35        match self {
36            SamplerKind::Sampler1D => "sampler1D",
37            SamplerKind::Sampler2D => "sampler2D",
38            SamplerKind::Sampler3D => "sampler3D",
39            SamplerKind::SamplerCube => "samplerCube",
40            SamplerKind::USampler1D => "usampler1D",
41            SamplerKind::USampler2D => "usampler2D",
42            SamplerKind::USampler3D => "usampler3D",
43            SamplerKind::USamplerCube => "usamplerCube",
44        }
45    }
46}
47
48unsafe fn create_shader(
49    server: &GlGraphicsServer,
50    name: String,
51    actual_type: u32,
52    source: &str,
53    gl_kind: GlKind,
54) -> Result<glow::Shader, FrameworkError> {
55    let merged_source = prepare_source_code(source, gl_kind);
56
57    let shader = server.gl.create_shader(actual_type)?;
58    server.gl.shader_source(shader, &merged_source);
59    server.gl.compile_shader(shader);
60
61    let status = server.gl.get_shader_compile_status(shader);
62    let compilation_message = server.gl.get_shader_info_log(shader);
63
64    if !status {
65        Log::writeln(
66            MessageKind::Error,
67            format!("Failed to compile {name} shader: {compilation_message}"),
68        );
69        Err(FrameworkError::ShaderCompilationFailed {
70            shader_name: name,
71            error_message: compilation_message,
72        })
73    } else {
74        let msg = if compilation_message.is_empty()
75            || compilation_message.chars().all(|c| c.is_whitespace())
76        {
77            format!("Shader {name} compiled successfully!")
78        } else {
79            format!("Shader {name} compiled successfully!\nAdditional info: {compilation_message}")
80        };
81
82        Log::writeln(MessageKind::Information, msg);
83
84        Ok(shader)
85    }
86}
87
88fn prepare_source_code(code: &str, gl_kind: GlKind) -> String {
89    let mut full_source_code = "#version 330 core\n// include 'shared.glsl'\n".to_owned();
90
91    if gl_kind == GlKind::OpenGLES {
92        full_source_code += r#"
93            precision highp float;
94            precision highp int;
95            precision highp usampler2D;
96            precision highp usampler3D;
97            precision highp usamplerCube;
98            precision highp sampler2D;
99            precision highp sampler3D;
100            precision highp samplerCube;
101        "#;
102    }
103
104    full_source_code += include_str!("shaders/shared.glsl");
105    full_source_code += "\n// end of include\n";
106    full_source_code += code;
107
108    if gl_kind == GlKind::OpenGLES {
109        full_source_code.replace("#version 330 core", "#version 300 es")
110    } else {
111        full_source_code
112    }
113}
114
115pub struct GlProgram {
116    state: Weak<GlGraphicsServer>,
117    pub id: glow::Program,
118    // Force compiler to not implement Send and Sync, because OpenGL is not thread-safe.
119    thread_mark: PhantomData<*const u8>,
120}
121
122impl GlProgram {
123    pub fn from_source_and_resources(
124        server: &GlGraphicsServer,
125        program_name: &str,
126        vertex_source: &str,
127        fragment_source: &str,
128        resources: &[ShaderResourceDefinition],
129    ) -> Result<GlProgram, FrameworkError> {
130        let mut vertex_source = vertex_source.to_string();
131        let mut fragment_source = fragment_source.to_string();
132
133        // Initial validation. The program will be validated once more by the compiler.
134        for resource in resources {
135            for other_resource in resources {
136                if std::ptr::eq(resource, other_resource) {
137                    continue;
138                }
139
140                if std::mem::discriminant(&resource.kind)
141                    == std::mem::discriminant(&other_resource.kind)
142                {
143                    if resource.binding == other_resource.binding {
144                        return Err(FrameworkError::Custom(format!(
145                            "Resource {} and {} using the same binding point {} \
146                            in the {program_name} GPU program.",
147                            resource.name, other_resource.name, resource.binding
148                        )));
149                    }
150
151                    if resource.name == other_resource.name {
152                        return Err(FrameworkError::Custom(format!(
153                            "There are two or more resources with same name {} \
154                                in the {program_name} GPU program.",
155                            resource.name
156                        )));
157                    }
158                }
159            }
160        }
161
162        // Generate appropriate texture binding points and uniform blocks for the specified properties.
163        for initial_source in [&mut vertex_source, &mut fragment_source] {
164            let mut texture_bindings = String::new();
165
166            for property in resources {
167                let resource_name = &property.name;
168                match property.kind {
169                    ShaderResourceKind::Texture { kind, .. } => {
170                        let glsl_name = kind.glsl_name();
171                        texture_bindings += &format!("uniform {glsl_name} {resource_name};\n");
172                    }
173                    ShaderResourceKind::PropertyGroup(ref fields) => {
174                        if fields.is_empty() {
175                            Log::warn(format!(
176                                "Uniform block {resource_name} is empty and will be ignored!"
177                            ));
178                            continue;
179                        }
180                        let mut block = format!("struct T{resource_name}{{\n");
181                        for field in fields {
182                            let field_name = &field.name;
183                            match field.kind {
184                                ShaderPropertyKind::Float { .. } => {
185                                    block += &format!("\tfloat {field_name};\n");
186                                }
187                                ShaderPropertyKind::FloatArray { max_len, .. } => {
188                                    block += &format!("\tfloat {field_name}[{max_len}];\n");
189                                }
190                                ShaderPropertyKind::Int { .. } => {
191                                    block += &format!("\tint {field_name};\n");
192                                }
193                                ShaderPropertyKind::IntArray { max_len, .. } => {
194                                    block += &format!("\tint {field_name}[{max_len}];\n");
195                                }
196                                ShaderPropertyKind::UInt { .. } => {
197                                    block += &format!("\tuint {field_name};\n");
198                                }
199                                ShaderPropertyKind::UIntArray { max_len, .. } => {
200                                    block += &format!("\tuint {field_name}[{max_len}];\n");
201                                }
202                                ShaderPropertyKind::Bool { .. } => {
203                                    block += &format!("\tbool {field_name};\n");
204                                }
205                                ShaderPropertyKind::Vector2 { .. } => {
206                                    block += &format!("\tvec2 {field_name};\n");
207                                }
208                                ShaderPropertyKind::Vector2Array { max_len, .. } => {
209                                    block += &format!("\tvec2 {field_name}[{max_len}];\n");
210                                }
211                                ShaderPropertyKind::Vector3 { .. } => {
212                                    block += &format!("\tvec3 {field_name};\n");
213                                }
214                                ShaderPropertyKind::Vector3Array { max_len, .. } => {
215                                    block += &format!("\tvec3 {field_name}[{max_len}];\n");
216                                }
217                                ShaderPropertyKind::Vector4 { .. } => {
218                                    block += &format!("\tvec4 {field_name};\n");
219                                }
220                                ShaderPropertyKind::Vector4Array { max_len, .. } => {
221                                    block += &format!("\tvec4 {field_name}[{max_len}];\n");
222                                }
223                                ShaderPropertyKind::Matrix2 { .. } => {
224                                    block += &format!("\tmat2 {field_name};\n");
225                                }
226                                ShaderPropertyKind::Matrix2Array { max_len, .. } => {
227                                    block += &format!("\tmat2 {field_name}[{max_len}];\n");
228                                }
229                                ShaderPropertyKind::Matrix3 { .. } => {
230                                    block += &format!("\tmat3 {field_name};\n");
231                                }
232                                ShaderPropertyKind::Matrix3Array { max_len, .. } => {
233                                    block += &format!("\tmat3 {field_name}[{max_len}];\n");
234                                }
235                                ShaderPropertyKind::Matrix4 { .. } => {
236                                    block += &format!("\tmat4 {field_name};\n");
237                                }
238                                ShaderPropertyKind::Matrix4Array { max_len, .. } => {
239                                    block += &format!("\tmat4 {field_name}[{max_len}];\n");
240                                }
241                                ShaderPropertyKind::Color { .. } => {
242                                    block += &format!("\tvec4 {field_name};\n");
243                                }
244                            }
245                        }
246                        block += "};\n";
247                        block += &format!("layout(std140) uniform U{resource_name} {{ T{resource_name} {resource_name}; }};\n");
248                        initial_source.insert_str(0, &block);
249                    }
250                }
251            }
252            initial_source.insert_str(0, &texture_bindings);
253        }
254
255        let program = Self::from_source(server, program_name, &vertex_source, &fragment_source)?;
256
257        unsafe {
258            server.set_program(Some(program.id));
259            for resource_definition in resources {
260                match resource_definition.kind {
261                    ShaderResourceKind::Texture { .. } => {
262                        if let Some(location) = server
263                            .gl
264                            .get_uniform_location(program.id, &resource_definition.name)
265                        {
266                            server
267                                .gl
268                                .uniform_1_i32(Some(&location), resource_definition.binding as i32);
269                        }
270                    }
271                    ShaderResourceKind::PropertyGroup { .. } => {
272                        if let Some(shader_block_index) = server.gl.get_uniform_block_index(
273                            program.id,
274                            &format!("U{}", resource_definition.name),
275                        ) {
276                            server.gl.uniform_block_binding(
277                                program.id,
278                                shader_block_index,
279                                resource_definition.binding as u32,
280                            )
281                        } else {
282                            Log::warn(format!(
283                                "Couldn't find uniform block U{}",
284                                resource_definition.name
285                            ));
286                        }
287                    }
288                }
289            }
290        }
291
292        Ok(program)
293    }
294
295    fn from_source(
296        server: &GlGraphicsServer,
297        name: &str,
298        vertex_source: &str,
299        fragment_source: &str,
300    ) -> Result<GlProgram, FrameworkError> {
301        unsafe {
302            let vertex_shader = create_shader(
303                server,
304                format!("{name}_VertexShader"),
305                glow::VERTEX_SHADER,
306                vertex_source,
307                server.gl_kind(),
308            )?;
309            let fragment_shader = create_shader(
310                server,
311                format!("{name}_FragmentShader"),
312                glow::FRAGMENT_SHADER,
313                fragment_source,
314                server.gl_kind(),
315            )?;
316            let program = server.gl.create_program()?;
317            server.gl.attach_shader(program, vertex_shader);
318            server.gl.delete_shader(vertex_shader);
319            server.gl.attach_shader(program, fragment_shader);
320            server.gl.delete_shader(fragment_shader);
321            server.gl.link_program(program);
322            let status = server.gl.get_program_link_status(program);
323            let link_message = server.gl.get_program_info_log(program);
324
325            if !status {
326                Log::writeln(
327                    MessageKind::Error,
328                    format!("Failed to link {name} shader: {link_message}"),
329                );
330                Err(FrameworkError::ShaderLinkingFailed {
331                    shader_name: name.to_owned(),
332                    error_message: link_message,
333                })
334            } else {
335                let msg = if link_message.is_empty()
336                    || link_message.chars().all(|c| c.is_whitespace())
337                {
338                    format!("Shader {name} linked successfully!")
339                } else {
340                    format!("Shader {name} linked successfully!\nAdditional info: {link_message}")
341                };
342
343                Log::writeln(MessageKind::Information, msg);
344
345                Ok(Self {
346                    state: server.weak(),
347                    id: program,
348                    thread_mark: PhantomData,
349                })
350            }
351        }
352    }
353}
354
355impl GpuProgramTrait for GlProgram {}
356
357impl Drop for GlProgram {
358    fn drop(&mut self) {
359        if let Some(state) = self.state.upgrade() {
360            unsafe {
361                state.gl.delete_program(self.id);
362            }
363        }
364    }
365}