1use 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
103fn find_at<'a>(re: ®ex::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 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 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 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 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}