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