GpuShader entity

- a step toward render pipeline object
This commit is contained in:
Dmitry Stepanov
2025-03-23 20:56:54 +03:00
parent fc13cbb5a6
commit 65c08a5483
5 changed files with 276 additions and 204 deletions
+220 -191
View File
@@ -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<glow::Shader, FrameworkError> {
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<GlGraphicsServer>,
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<Self, FrameworkError> {
// 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<GlProgram, FrameworkError> {
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<GlProgram, FrameworkError> {
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);
+29 -5
View File
@@ -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<GpuShader, FrameworkError> {
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<GpuProgram, FrameworkError> {
+10 -3
View File
@@ -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<dyn GpuProgramTrait>);
pub enum ShaderKind {
Vertex,
Fragment,
}
define_as_any_trait!(GpuShaderAsAny => GpuShaderTrait);
pub trait GpuShaderTrait: GpuShaderAsAny {}
define_shared_wrapper!(GpuShader<dyn GpuShaderTrait>);
/// A fallback value for the sampler.
///
@@ -310,5 +319,3 @@ impl ShaderResourceDefinition {
self.name.starts_with("fyrox_")
}
}
define_shared_wrapper!(GpuProgram<dyn GpuProgramTrait>);
+15 -3
View File
@@ -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<GpuQuery, FrameworkError>;
/// 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<GpuShader, FrameworkError>;
/// 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<GpuProgram, FrameworkError>;
+2 -2
View File
@@ -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,
) {