diff --git a/fyrox-graphics/src/gl/program.rs b/fyrox-graphics/src/gl/program.rs index ae8de6d51..d319e00f5 100644 --- a/fyrox-graphics/src/gl/program.rs +++ b/fyrox-graphics/src/gl/program.rs @@ -21,15 +21,17 @@ use crate::{ core::log::{Log, MessageKind}, error::FrameworkError, - gl::server::{GlGraphicsServer, GlKind}, + gl::{ + server::{GlGraphicsServer, GlKind}, + ToGlConstant, + }, gpu_program::{ - GpuProgramTrait, SamplerKind, ShaderPropertyKind, ShaderResourceDefinition, - ShaderResourceKind, + GpuProgramTrait, GpuShaderTrait, SamplerKind, ShaderKind, ShaderPropertyKind, + ShaderResourceDefinition, ShaderResourceKind, }, }; use glow::HasContext; -use std::ops::Range; -use std::{marker::PhantomData, rc::Weak}; +use std::{marker::PhantomData, ops::Range, rc::Weak}; impl SamplerKind { pub fn glsl_name(&self) -> &str { @@ -114,49 +116,206 @@ fn patch_error_message(vendor: Vendor, src: &mut String, line_offset: isize) { } } -unsafe fn create_shader( - server: &GlGraphicsServer, - name: String, - actual_type: u32, - source: &str, - mut line_offset: isize, - gl_kind: GlKind, -) -> Result { - let initial_lines_count = count_lines(source); - let merged_source = prepare_source_code(source, gl_kind); - line_offset -= count_lines(&merged_source) - initial_lines_count; +pub struct GlShader { + state: Weak, + pub id: glow::Shader, +} - let shader = server.gl.create_shader(actual_type)?; - server.gl.shader_source(shader, &merged_source); - server.gl.compile_shader(shader); +impl ToGlConstant for ShaderKind { + fn into_gl(self) -> u32 { + match self { + ShaderKind::Vertex => glow::VERTEX_SHADER, + ShaderKind::Fragment => glow::FRAGMENT_SHADER, + } + } +} - let status = server.gl.get_shader_compile_status(shader); - let mut compilation_message = server.gl.get_shader_info_log(shader); +impl GpuShaderTrait for GlShader {} - if !status { - let vendor_str = unsafe { server.gl.get_parameter_string(glow::VENDOR).to_lowercase() }; - let vendor = Vendor::from_str(vendor_str); - patch_error_message(vendor, &mut compilation_message, line_offset); - Log::writeln( - MessageKind::Error, - format!("Failed to compile {name} shader:\n{compilation_message}"), - ); - Err(FrameworkError::ShaderCompilationFailed { - shader_name: name, - error_message: compilation_message, - }) - } else { - let msg = if compilation_message.is_empty() - || compilation_message.chars().all(|c| c.is_whitespace()) - { - format!("Shader {name} compiled successfully!") - } else { - format!("Shader {name} compiled successfully!\nAdditional info: {compilation_message}") - }; +impl GlShader { + pub fn new( + server: &GlGraphicsServer, + name: String, + kind: ShaderKind, + mut source: String, + resources: &[ShaderResourceDefinition], + mut line_offset: isize, + ) -> Result { + // Initial validation. The program will be validated once more by the compiler. + for resource in resources { + for other_resource in resources { + if std::ptr::eq(resource, other_resource) { + continue; + } - Log::writeln(MessageKind::Information, msg); + if std::mem::discriminant(&resource.kind) + == std::mem::discriminant(&other_resource.kind) + { + if resource.binding == other_resource.binding { + return Err(FrameworkError::Custom(format!( + "Resource {} and {} using the same binding point {} \ + in the {name} GPU program.", + resource.name, other_resource.name, resource.binding + ))); + } - Ok(shader) + if resource.name == other_resource.name { + return Err(FrameworkError::Custom(format!( + "There are two or more resources with same name {} \ + in the {name} GPU program.", + resource.name + ))); + } + } + } + } + + // Generate appropriate texture binding points and uniform blocks for the specified properties. + + let mut texture_bindings = String::new(); + + for property in resources { + let resource_name = &property.name; + match property.kind { + ShaderResourceKind::Texture { kind, .. } => { + let glsl_name = kind.glsl_name(); + texture_bindings += &format!("uniform {glsl_name} {resource_name};\n"); + } + ShaderResourceKind::PropertyGroup(ref fields) => { + if fields.is_empty() { + Log::warn(format!( + "Uniform block {resource_name} is empty and will be ignored!" + )); + continue; + } + let mut block = format!("struct T{resource_name}{{\n"); + for field in fields { + let field_name = &field.name; + match field.kind { + ShaderPropertyKind::Float { .. } => { + block += &format!("\tfloat {field_name};\n"); + } + ShaderPropertyKind::FloatArray { max_len, .. } => { + block += &format!("\tfloat {field_name}[{max_len}];\n"); + } + ShaderPropertyKind::Int { .. } => { + block += &format!("\tint {field_name};\n"); + } + ShaderPropertyKind::IntArray { max_len, .. } => { + block += &format!("\tint {field_name}[{max_len}];\n"); + } + ShaderPropertyKind::UInt { .. } => { + block += &format!("\tuint {field_name};\n"); + } + ShaderPropertyKind::UIntArray { max_len, .. } => { + block += &format!("\tuint {field_name}[{max_len}];\n"); + } + ShaderPropertyKind::Bool { .. } => { + block += &format!("\tbool {field_name};\n"); + } + ShaderPropertyKind::Vector2 { .. } => { + block += &format!("\tvec2 {field_name};\n"); + } + ShaderPropertyKind::Vector2Array { max_len, .. } => { + block += &format!("\tvec2 {field_name}[{max_len}];\n"); + } + ShaderPropertyKind::Vector3 { .. } => { + block += &format!("\tvec3 {field_name};\n"); + } + ShaderPropertyKind::Vector3Array { max_len, .. } => { + block += &format!("\tvec3 {field_name}[{max_len}];\n"); + } + ShaderPropertyKind::Vector4 { .. } => { + block += &format!("\tvec4 {field_name};\n"); + } + ShaderPropertyKind::Vector4Array { max_len, .. } => { + block += &format!("\tvec4 {field_name}[{max_len}];\n"); + } + ShaderPropertyKind::Matrix2 { .. } => { + block += &format!("\tmat2 {field_name};\n"); + } + ShaderPropertyKind::Matrix2Array { max_len, .. } => { + block += &format!("\tmat2 {field_name}[{max_len}];\n"); + } + ShaderPropertyKind::Matrix3 { .. } => { + block += &format!("\tmat3 {field_name};\n"); + } + ShaderPropertyKind::Matrix3Array { max_len, .. } => { + block += &format!("\tmat3 {field_name}[{max_len}];\n"); + } + ShaderPropertyKind::Matrix4 { .. } => { + block += &format!("\tmat4 {field_name};\n"); + } + ShaderPropertyKind::Matrix4Array { max_len, .. } => { + block += &format!("\tmat4 {field_name}[{max_len}];\n"); + } + ShaderPropertyKind::Color { .. } => { + block += &format!("\tvec4 {field_name};\n"); + } + } + } + block += "};\n"; + block += &format!("layout(std140) uniform U{resource_name} {{ T{resource_name} {resource_name}; }};\n"); + source.insert_str(0, &block); + line_offset -= count_lines(&block); + } + } + } + source.insert_str(0, &texture_bindings); + line_offset -= count_lines(&texture_bindings); + + unsafe { + let gl_kind = server.gl_kind(); + let initial_lines_count = count_lines(&source); + let merged_source = prepare_source_code(&source, gl_kind); + line_offset -= count_lines(&merged_source) - initial_lines_count; + + let shader = server.gl.create_shader(kind.into_gl())?; + server.gl.shader_source(shader, &merged_source); + server.gl.compile_shader(shader); + + let status = server.gl.get_shader_compile_status(shader); + let mut compilation_message = server.gl.get_shader_info_log(shader); + + if !status { + let vendor_str = server.gl.get_parameter_string(glow::VENDOR).to_lowercase(); + let vendor = Vendor::from_str(vendor_str); + patch_error_message(vendor, &mut compilation_message, line_offset); + Log::writeln( + MessageKind::Error, + format!("Failed to compile {name} shader:\n{compilation_message}"), + ); + Err(FrameworkError::ShaderCompilationFailed { + shader_name: name, + error_message: compilation_message, + }) + } else { + let msg = if compilation_message.is_empty() + || compilation_message.chars().all(|c| c.is_whitespace()) + { + format!("Shader {name} compiled successfully!") + } else { + format!("Shader {name} compiled successfully!\nAdditional info: {compilation_message}") + }; + + Log::writeln(MessageKind::Information, msg); + + Ok(Self { + id: shader, + state: server.weak(), + }) + } + } + } +} + +impl Drop for GlShader { + fn drop(&mut self) { + if let Some(state) = self.state.upgrade() { + unsafe { + state.gl.delete_shader(self.id); + } + } } } @@ -198,149 +357,20 @@ impl GlProgram { pub fn from_source_and_resources( server: &GlGraphicsServer, program_name: &str, - vertex_source: &str, - mut vertex_source_line_offset: isize, - fragment_source: &str, - mut fragment_source_line_offset: isize, + vertex_source: String, + vertex_source_line_offset: isize, + fragment_source: String, + fragment_source_line_offset: isize, resources: &[ShaderResourceDefinition], ) -> Result { - let mut vertex_source = vertex_source.to_string(); - let mut fragment_source = fragment_source.to_string(); - - // Initial validation. The program will be validated once more by the compiler. - for resource in resources { - for other_resource in resources { - if std::ptr::eq(resource, other_resource) { - continue; - } - - if std::mem::discriminant(&resource.kind) - == std::mem::discriminant(&other_resource.kind) - { - if resource.binding == other_resource.binding { - return Err(FrameworkError::Custom(format!( - "Resource {} and {} using the same binding point {} \ - in the {program_name} GPU program.", - resource.name, other_resource.name, resource.binding - ))); - } - - if resource.name == other_resource.name { - return Err(FrameworkError::Custom(format!( - "There are two or more resources with same name {} \ - in the {program_name} GPU program.", - resource.name - ))); - } - } - } - } - - // Generate appropriate texture binding points and uniform blocks for the specified properties. - for (offset, initial_source) in [ - (&mut vertex_source_line_offset, &mut vertex_source), - (&mut fragment_source_line_offset, &mut fragment_source), - ] { - let mut texture_bindings = String::new(); - - for property in resources { - let resource_name = &property.name; - match property.kind { - ShaderResourceKind::Texture { kind, .. } => { - let glsl_name = kind.glsl_name(); - texture_bindings += &format!("uniform {glsl_name} {resource_name};\n"); - } - ShaderResourceKind::PropertyGroup(ref fields) => { - if fields.is_empty() { - Log::warn(format!( - "Uniform block {resource_name} is empty and will be ignored!" - )); - continue; - } - let mut block = format!("struct T{resource_name}{{\n"); - for field in fields { - let field_name = &field.name; - match field.kind { - ShaderPropertyKind::Float { .. } => { - block += &format!("\tfloat {field_name};\n"); - } - ShaderPropertyKind::FloatArray { max_len, .. } => { - block += &format!("\tfloat {field_name}[{max_len}];\n"); - } - ShaderPropertyKind::Int { .. } => { - block += &format!("\tint {field_name};\n"); - } - ShaderPropertyKind::IntArray { max_len, .. } => { - block += &format!("\tint {field_name}[{max_len}];\n"); - } - ShaderPropertyKind::UInt { .. } => { - block += &format!("\tuint {field_name};\n"); - } - ShaderPropertyKind::UIntArray { max_len, .. } => { - block += &format!("\tuint {field_name}[{max_len}];\n"); - } - ShaderPropertyKind::Bool { .. } => { - block += &format!("\tbool {field_name};\n"); - } - ShaderPropertyKind::Vector2 { .. } => { - block += &format!("\tvec2 {field_name};\n"); - } - ShaderPropertyKind::Vector2Array { max_len, .. } => { - block += &format!("\tvec2 {field_name}[{max_len}];\n"); - } - ShaderPropertyKind::Vector3 { .. } => { - block += &format!("\tvec3 {field_name};\n"); - } - ShaderPropertyKind::Vector3Array { max_len, .. } => { - block += &format!("\tvec3 {field_name}[{max_len}];\n"); - } - ShaderPropertyKind::Vector4 { .. } => { - block += &format!("\tvec4 {field_name};\n"); - } - ShaderPropertyKind::Vector4Array { max_len, .. } => { - block += &format!("\tvec4 {field_name}[{max_len}];\n"); - } - ShaderPropertyKind::Matrix2 { .. } => { - block += &format!("\tmat2 {field_name};\n"); - } - ShaderPropertyKind::Matrix2Array { max_len, .. } => { - block += &format!("\tmat2 {field_name}[{max_len}];\n"); - } - ShaderPropertyKind::Matrix3 { .. } => { - block += &format!("\tmat3 {field_name};\n"); - } - ShaderPropertyKind::Matrix3Array { max_len, .. } => { - block += &format!("\tmat3 {field_name}[{max_len}];\n"); - } - ShaderPropertyKind::Matrix4 { .. } => { - block += &format!("\tmat4 {field_name};\n"); - } - ShaderPropertyKind::Matrix4Array { max_len, .. } => { - block += &format!("\tmat4 {field_name}[{max_len}];\n"); - } - ShaderPropertyKind::Color { .. } => { - block += &format!("\tvec4 {field_name};\n"); - } - } - } - block += "};\n"; - block += &format!("layout(std140) uniform U{resource_name} {{ T{resource_name} {resource_name}; }};\n"); - initial_source.insert_str(0, &block); - *offset -= count_lines(&block); - } - } - } - initial_source.insert_str(0, &texture_bindings); - *offset -= count_lines(&texture_bindings); - } - let program = Self::from_source( server, program_name, - &vertex_source, + vertex_source, vertex_source_line_offset, - &fragment_source, + fragment_source, fragment_source_line_offset, + resources, )?; unsafe { @@ -384,33 +414,32 @@ impl GlProgram { fn from_source( server: &GlGraphicsServer, name: &str, - vertex_source: &str, + vertex_source: String, vertex_source_line_offset: isize, - fragment_source: &str, + fragment_source: String, fragment_source_line_offset: isize, + resources: &[ShaderResourceDefinition], ) -> Result { unsafe { - let vertex_shader = create_shader( + let vertex_shader = GlShader::new( server, format!("{name}_VertexShader"), - glow::VERTEX_SHADER, + ShaderKind::Vertex, vertex_source, + resources, vertex_source_line_offset, - server.gl_kind(), )?; - let fragment_shader = create_shader( + let fragment_shader = GlShader::new( server, format!("{name}_FragmentShader"), - glow::FRAGMENT_SHADER, + ShaderKind::Fragment, fragment_source, + resources, fragment_source_line_offset, - server.gl_kind(), )?; let program = server.gl.create_program()?; - server.gl.attach_shader(program, vertex_shader); - server.gl.delete_shader(vertex_shader); - server.gl.attach_shader(program, fragment_shader); - server.gl.delete_shader(fragment_shader); + server.gl.attach_shader(program, vertex_shader.id); + server.gl.attach_shader(program, fragment_shader.id); server.gl.link_program(program); let status = server.gl.get_program_link_status(program); let link_message = server.gl.get_program_info_log(program); diff --git a/fyrox-graphics/src/gl/server.rs b/fyrox-graphics/src/gl/server.rs index 4ff345fe0..17f8ddd2b 100644 --- a/fyrox-graphics/src/gl/server.rs +++ b/fyrox-graphics/src/gl/server.rs @@ -25,11 +25,17 @@ use crate::{ framebuffer::{Attachment, GpuFrameBuffer}, geometry_buffer::{GeometryBufferDescriptor, GpuGeometryBuffer}, gl::{ - self, framebuffer::GlFrameBuffer, geometry_buffer::GlGeometryBuffer, program::GlProgram, - query::GlQuery, read_buffer::GlAsyncReadBuffer, sampler::GlSampler, texture::GlTexture, + self, + framebuffer::GlFrameBuffer, + geometry_buffer::GlGeometryBuffer, + program::{GlProgram, GlShader}, + query::GlQuery, + read_buffer::GlAsyncReadBuffer, + sampler::GlSampler, + texture::GlTexture, ToGlConstant, }, - gpu_program::{GpuProgram, ShaderResourceDefinition}, + gpu_program::{GpuProgram, GpuShader, ShaderKind, ShaderResourceDefinition}, gpu_texture::{GpuTexture, GpuTextureDescriptor}, query::GpuQuery, read_buffer::GpuAsyncReadBuffer, @@ -1031,12 +1037,30 @@ impl GraphicsServer for GlGraphicsServer { Ok(GpuQuery(Rc::new(GlQuery::new(self)?))) } + fn create_shader( + &self, + name: String, + kind: ShaderKind, + source: String, + resources: &[ShaderResourceDefinition], + line_offset: isize, + ) -> Result { + Ok(GpuShader(Rc::new(GlShader::new( + self, + name, + kind, + source, + resources, + line_offset, + )?))) + } + fn create_program( &self, name: &str, - vertex_source: &str, + vertex_source: String, vertex_source_line_offset: isize, - fragment_source: &str, + fragment_source: String, fragment_source_line_offset: isize, resources: &[ShaderResourceDefinition], ) -> Result { diff --git a/fyrox-graphics/src/gpu_program.rs b/fyrox-graphics/src/gpu_program.rs index 6a66c87b1..e1b5b8ec0 100644 --- a/fyrox-graphics/src/gpu_program.rs +++ b/fyrox-graphics/src/gpu_program.rs @@ -33,8 +33,17 @@ use serde::{Deserialize, Serialize}; use strum_macros::{AsRefStr, EnumString, VariantNames}; define_as_any_trait!(GpuProgramAsAny => GpuProgramTrait); - pub trait GpuProgramTrait: GpuProgramAsAny {} +define_shared_wrapper!(GpuProgram); + +pub enum ShaderKind { + Vertex, + Fragment, +} + +define_as_any_trait!(GpuShaderAsAny => GpuShaderTrait); +pub trait GpuShaderTrait: GpuShaderAsAny {} +define_shared_wrapper!(GpuShader); /// A fallback value for the sampler. /// @@ -310,5 +319,3 @@ impl ShaderResourceDefinition { self.name.starts_with("fyrox_") } } - -define_shared_wrapper!(GpuProgram); diff --git a/fyrox-graphics/src/server.rs b/fyrox-graphics/src/server.rs index 42f85c095..dff339bc0 100644 --- a/fyrox-graphics/src/server.rs +++ b/fyrox-graphics/src/server.rs @@ -28,7 +28,7 @@ use crate::{ error::FrameworkError, framebuffer::{Attachment, GpuFrameBuffer}, geometry_buffer::{GeometryBufferDescriptor, GpuGeometryBuffer}, - gpu_program::{GpuProgram, ShaderResourceDefinition}, + gpu_program::{GpuProgram, GpuShader, ShaderKind, ShaderResourceDefinition}, gpu_texture::{GpuTexture, GpuTextureDescriptor, GpuTextureKind, PixelKind}, query::GpuQuery, read_buffer::GpuAsyncReadBuffer, @@ -94,15 +94,27 @@ pub trait GraphicsServer: GraphicsServerAsAny { /// is used to create occlusion queries. fn create_query(&self) -> Result; + /// Creates a new named GPU shader. The name could be used for debugging purposes. The + /// implementation of graphics server will generate proper resource bindings in the shader code + /// for you. + fn create_shader( + &self, + name: String, + kind: ShaderKind, + source: String, + resources: &[ShaderResourceDefinition], + line_offset: isize, + ) -> Result; + /// Creates a new named GPU program using a pair of vertex and fragment shaders. The name could /// be used for debugging purposes. The implementation of graphics server will generate proper /// resource bindings in the shader code for you. fn create_program( &self, name: &str, - vertex_source: &str, + vertex_source: String, vertex_source_line_offset: isize, - fragment_source: &str, + fragment_source: String, fragment_source_line_offset: isize, resources: &[ShaderResourceDefinition], ) -> Result; diff --git a/fyrox-impl/src/renderer/cache/shader.rs b/fyrox-impl/src/renderer/cache/shader.rs index 2f2978ff0..4b7e7a7b9 100644 --- a/fyrox-impl/src/renderer/cache/shader.rs +++ b/fyrox-impl/src/renderer/cache/shader.rs @@ -217,9 +217,9 @@ impl RenderPassContainer { let program_name = format!("{}_{}", shader.definition.name, render_pass.name); match server.create_program( &program_name, - &render_pass.vertex_shader, + render_pass.vertex_shader.clone(), render_pass.vertex_shader_line, - &render_pass.fragment_shader, + render_pass.fragment_shader.clone(), render_pass.fragment_shader_line, &shader.definition.resources, ) {