diff --git a/Cargo.lock b/Cargo.lock index 1a68a01eb..0ff325175 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1275,6 +1275,8 @@ dependencies = [ "once_cell", "paste", "selectors", + "serde", + "serde_json", "smallvec", "stylo", "stylo_config", diff --git a/crates/jsbindings/Cargo.toml b/crates/jsbindings/Cargo.toml index 838ed8629..4995bf298 100644 --- a/crates/jsbindings/Cargo.toml +++ b/crates/jsbindings/Cargo.toml @@ -23,6 +23,8 @@ euclid = { workspace = true } lazy_static = "1.4.0" log = "0.4.21" once_cell = "1.17.1" +serde = { workspace = true } +serde_json = { workspace = true } surfman = "0.9.2" taffy = { workspace = true, features = ["default"] } gl = { version = "0.14.0" } diff --git a/crates/jsbindings/bindings.webgl.hpp b/crates/jsbindings/bindings.webgl.hpp index 2eec34b42..3ea87e6a9 100644 --- a/crates/jsbindings/bindings.webgl.hpp +++ b/crates/jsbindings/bindings.webgl.hpp @@ -2,6 +2,10 @@ #include #include +#include +#include +#include +#include #include "./holocron_webgl.autogen.hpp" namespace crates::webgl @@ -24,4 +28,235 @@ namespace crates::webgl return std::string(patched); } }; + + /** + * Represents a GLSL vertex attribute. + */ + struct GLSLAttribute + { + std::string name; ///< Attribute name (e.g., "position", "normal") + uint32_t type; ///< GL type constant (e.g., 0x8B51 for GL_FLOAT_VEC3) + int32_t size; ///< Size (number of elements, 1 for non-arrays) + int32_t location; ///< Attribute location (0-based index) + bool active; ///< Whether the attribute is active (referenced in shader) + }; + + /** + * Represents a GLSL uniform variable. + */ + struct GLSLUniform + { + std::string name; ///< Uniform name (e.g., "modelViewMatrix", "lightColor") + uint32_t type; ///< GL type constant (e.g., 0x8B5C for GL_FLOAT_MAT4) + int32_t size; ///< Size (number of elements, 1 for non-arrays) + bool active; ///< Whether the uniform is active (referenced in shader) + }; + + /** + * GLSLShaderAnalyzer parses GLSL shader source code to extract variable information. + * + * This class provides a convenient C++ interface to parse GLSL shaders and + * extract all attribute and uniform declarations, including their names, types, and locations. + * + * Features: + * - Supports both GLSL 100 ES (attribute) and GLSL 300 ES (in) qualifiers + * - Handles explicit layout(location = N) qualifiers + * - Auto-assigns locations based on declaration order when not explicitly specified + * - Marks inactive (unreferenced) attributes and uniforms with active=false + * - Compatible with WebGL 1.0 and WebGL 2.0 shaders + * + * Example usage: + * @code + * std::string vertexShader = R"( + * #version 300 es + * in vec3 position; + * in vec3 normal; + * uniform mat4 mvpMatrix; + * void main() { + * gl_Position = mvpMatrix * vec4(position, 1.0); + * } + * )"; + * + * // Recommended: Parse both attributes and uniforms in a single call + * std::vector attributes; + * std::vector uniforms; + * GLSLShaderAnalyzer::Parse(vertexShader, attributes, uniforms); + * + * // Or parse individually (deprecated, less efficient) + * auto attributes = GLSLShaderAnalyzer::ParseAttributes(vertexShader); + * auto uniforms = GLSLShaderAnalyzer::ParseUniforms(vertexShader); + * @endcode + */ + class GLSLShaderAnalyzer + { + public: + /** + * Parse GLSL shader source and extract both attributes and uniforms in a single pass. + * This is more efficient than calling ParseAttributes() and ParseUniforms() separately. + * + * @param source The GLSL shader source code as a string. + * @param attributes Output vector to receive attribute metadata. + * @param uniforms Output vector to receive uniform metadata. + * @param include_builtin_attributes Whether to include gl_InstanceID/gl_VertexID as active attributes if used in shader. + * @returns true if parsing succeeded, false otherwise. + */ + static inline bool Parse(const std::string &source, + std::vector &attributes, + std::vector &uniforms, + bool include_builtin_attributes = false) + { + auto json_str = holocron::webgl::parseGLSLShader(source.c_str(), include_builtin_attributes); + + rapidjson::Document doc; + doc.Parse(std::string(json_str).c_str()); + + if (!doc.IsObject() || !doc.HasMember("attributes") || !doc.HasMember("uniforms")) + { + return false; + } + + // Parse attributes + const auto &attrs = doc["attributes"]; + if (attrs.IsArray()) + { + attributes.clear(); + attributes.reserve(attrs.Size()); + for (rapidjson::SizeType i = 0; i < attrs.Size(); i++) + { + const auto &attr = attrs[i]; + if (attr.IsObject() && attr.HasMember("name") && attr.HasMember("type") && + attr.HasMember("size") && attr.HasMember("location") && attr.HasMember("active")) + { + attributes.push_back(GLSLAttribute{ + attr["name"].GetString(), + attr["type"].GetUint(), + attr["size"].GetInt(), + attr["location"].GetInt(), + attr["active"].GetBool()}); + } + } + } + + // Parse uniforms + const auto &unifs = doc["uniforms"]; + if (unifs.IsArray()) + { + uniforms.clear(); + uniforms.reserve(unifs.Size()); + for (rapidjson::SizeType i = 0; i < unifs.Size(); i++) + { + const auto &uniform = unifs[i]; + if (uniform.IsObject() && uniform.HasMember("name") && uniform.HasMember("type") && + uniform.HasMember("size") && uniform.HasMember("active")) + { + uniforms.push_back(GLSLUniform{ + uniform["name"].GetString(), + uniform["type"].GetUint(), + uniform["size"].GetInt(), + uniform["active"].GetBool()}); + } + } + } + + return true; + } + + /** + * Parse GLSL vertex shader source and extract all attribute declarations. + * @deprecated Use Parse() instead to get both attributes and uniforms in a single call. + * + * @param source The GLSL vertex shader source code as a string. + * @param include_builtin_attributes Whether to include gl_InstanceID/gl_VertexID as active attributes if used in shader. + * @returns A vector of GLSLAttribute structs containing attribute metadata. + */ + static inline std::vector ParseAttributes(const std::string &source, bool include_builtin_attributes = false) + { + auto json_str = holocron::webgl::parseGLSLAttributes(source.c_str(), include_builtin_attributes); + std::vector attributes; + + rapidjson::Document doc; + doc.Parse(std::string(json_str).c_str()); + + if (!doc.IsArray()) + { + return attributes; + } + + attributes.reserve(doc.Size()); + for (rapidjson::SizeType i = 0; i < doc.Size(); i++) + { + const auto &attr = doc[i]; + if (attr.IsObject() && attr.HasMember("name") && attr.HasMember("type") && + attr.HasMember("size") && attr.HasMember("location") && attr.HasMember("active")) + { + attributes.push_back(GLSLAttribute{ + attr["name"].GetString(), + attr["type"].GetUint(), + attr["size"].GetInt(), + attr["location"].GetInt(), + attr["active"].GetBool()}); + } + } + + return attributes; + } + + /** + * Parse GLSL shader source and extract all uniform declarations. + * @deprecated Use Parse() instead to get both attributes and uniforms in a single call. + * + * @param source The GLSL shader source code as a string. + * @returns A vector of GLSLUniform structs containing uniform metadata. + */ + static inline std::vector ParseUniforms(const std::string &source) + { + auto json_str = holocron::webgl::parseGLSLUniforms(source.c_str()); + std::vector uniforms; + + rapidjson::Document doc; + doc.Parse(std::string(json_str).c_str()); + + if (!doc.IsArray()) + { + return uniforms; + } + + uniforms.reserve(doc.Size()); + for (rapidjson::SizeType i = 0; i < doc.Size(); i++) + { + const auto &uniform = doc[i]; + if (uniform.IsObject() && uniform.HasMember("name") && uniform.HasMember("type") && + uniform.HasMember("size") && uniform.HasMember("active")) + { + uniforms.push_back(GLSLUniform{ + uniform["name"].GetString(), + uniform["type"].GetUint(), + uniform["size"].GetInt(), + uniform["active"].GetBool()}); + } + } + + return uniforms; + } + + /** + * Get the location of a specific attribute by name. + * + * @param source The GLSL vertex shader source code as a string. + * @param name The name of the attribute to look up. + * @returns An optional containing the attribute location if found, or std::nullopt if not found. + */ + static inline std::optional GetAttribLocation(const std::string &source, const std::string &name) + { + auto attributes = ParseAttributes(source); + for (const auto &attr : attributes) + { + if (attr.name == name) + { + return attr.location; + } + } + return std::nullopt; + } + }; } diff --git a/crates/jsbindings/lib.rs b/crates/jsbindings/lib.rs index bc2d4340d..03260f8de 100644 --- a/crates/jsbindings/lib.rs +++ b/crates/jsbindings/lib.rs @@ -1,7 +1,6 @@ #![allow(unused_variables)] #![allow(clippy::uninlined_format_args)] #![allow(deprecated)] -#![feature(concat_idents)] extern crate ctor; extern crate jsar_jsbinding_macro; diff --git a/crates/jsbindings/webgl.rs b/crates/jsbindings/webgl.rs index b19796942..3e5bbc442 100644 --- a/crates/jsbindings/webgl.rs +++ b/crates/jsbindings/webgl.rs @@ -1,5 +1,6 @@ use glsl_lang::ast; -use glsl_lang::visitor::{HostMut, Visit, VisitorMut}; +use glsl_lang::visitor::{Host, HostMut, Visit, Visitor, VisitorMut}; +use serde::{Deserialize, Serialize}; use std::path::Path; use crate::glsl_transpiler; @@ -118,18 +119,790 @@ fn patch_glsl_source_from_str(s: &str) -> String { s } +/// Visitor to collect all variable references in the shader +struct ReferenceCollector { + referenced_names: std::collections::HashSet, + /// Track if gl_InstanceID or gl_VertexID are used + uses_gl_instance_id: bool, + uses_gl_vertex_id: bool, + /// Track local variable assignments: local_var -> source_var + /// For example, when we see "local_foo = foo[0]", we store "local_foo" -> "foo[0]" + variable_aliases: std::collections::HashMap>, +} + +impl ReferenceCollector { + fn new() -> Self { + Self { + referenced_names: std::collections::HashSet::new(), + uses_gl_instance_id: false, + uses_gl_vertex_id: false, + variable_aliases: std::collections::HashMap::new(), + } + } + + /// Track a reference, expanding through variable aliases + fn track_reference(&mut self, var_name: String) { + self.track_reference_internal(var_name, &mut std::collections::HashSet::new()); + } + + /// Internal helper that tracks visited variables to prevent infinite recursion + fn track_reference_internal(&mut self, var_name: String, visited: &mut std::collections::HashSet) { + // Prevent infinite recursion from circular aliases + if visited.contains(&var_name) { + return; + } + visited.insert(var_name.clone()); + + self.referenced_names.insert(var_name.clone()); + + // Check if this is a field access on an aliased variable (e.g., local_foo.f1 -> foo[0].f1) + if let Some(dot_pos) = var_name.find('.') { + let base_var = &var_name[..dot_pos]; + let field_path = &var_name[dot_pos..]; // includes the dot + + // If the base variable has aliases, track the expanded names + if let Some(sources) = self.variable_aliases.get(base_var).cloned() { + for source in sources { + let expanded = format!("{}{}", source, field_path); + self.track_reference_internal(expanded, visited); + } + } + } else { + // Simple variable reference - check for aliases + if let Some(sources) = self.variable_aliases.get(&var_name).cloned() { + for source in sources { + self.track_reference_internal(source, visited); + } + } + } + } +} + +impl Visitor for ReferenceCollector { + fn visit_expr(&mut self, expr: &ast::Expr) -> Visit { + match &expr.content { + // Direct variable reference + ast::ExprData::Variable(identifier) => { + let var_name = identifier.content.0.to_string(); + + // Track built-in GL variables + if var_name == "gl_InstanceID" { + self.uses_gl_instance_id = true; + } else if var_name == "gl_VertexID" { + self.uses_gl_vertex_id = true; + } + + self.track_reference(var_name); + } + // Field access: uniform.field or uniform[0].field + ast::ExprData::Dot(base_expr, field_ident) => { + // Build the full path for struct member access + let full_path = self.build_member_path(base_expr, &field_ident.content.0); + self.track_reference(full_path); + } + // Array access: uniform[0] + ast::ExprData::Bracket(base_expr, index_expr) => { + // Try to extract the index if it's a constant + let index_str = if let ast::ExprData::IntConst(val) = &index_expr.content { + format!("[{}]", val) + } else { + // For non-constant indices, we can't determine exact element + // Mark the base as referenced + "[0]".to_string() // Default to [0] for tracking + }; + + if let ast::ExprData::Variable(ident) = &base_expr.content { + let full_name = format!("{}{}", ident.content.0, index_str); + self.track_reference(full_name); + } + } + // Assignment: track variable aliases (local_var = uniform_var) + ast::ExprData::Assignment(lhs, _, rhs) => { + // Extract the left-hand side variable name + let lhs_name = self.extract_var_name(lhs); + // Extract all variables referenced on the right-hand side + let rhs_vars = self.extract_all_var_names(rhs); + + if let Some(lhs_var) = lhs_name { + if !rhs_vars.is_empty() { + self.variable_aliases.insert(lhs_var, rhs_vars); + } + } + } + _ => {} + } + Visit::Children + } +} + +impl ReferenceCollector { + /// Build the full member path for nested field access + fn build_member_path(&self, expr: &ast::Expr, field: &str) -> String { + match &expr.content { + // Simple variable: var.field + ast::ExprData::Variable(ident) => { + format!("{}.{}", ident.content.0, field) + } + // Nested access: var[0].field + ast::ExprData::Bracket(base_expr, index_expr) => { + let base_path = self.get_base_path(base_expr); + let index_str = if let ast::ExprData::IntConst(val) = &index_expr.content { + format!("[{}]", val) + } else { + "[0]".to_string() + }; + format!("{}{}.{}", base_path, index_str, field) + } + // Nested field: var.field1.field2 + ast::ExprData::Dot(nested_base, nested_field) => { + let base_path = self.build_member_path(nested_base, &nested_field.content.0); + format!("{}.{}", base_path, field) + } + _ => field.to_string(), + } + } + + /// Get the base path from an expression + fn get_base_path(&self, expr: &ast::Expr) -> String { + match &expr.content { + ast::ExprData::Variable(ident) => ident.content.0.to_string(), + ast::ExprData::Dot(base, field) => { + self.build_member_path(base, &field.content.0) + } + _ => String::new(), + } + } + + /// Extract the variable name from an expression (for LHS of assignments) + fn extract_var_name(&self, expr: &ast::Expr) -> Option { + match &expr.content { + ast::ExprData::Variable(ident) => Some(ident.content.0.to_string()), + _ => None, + } + } + + /// Extract all variable names referenced in an expression (for RHS of assignments) + fn extract_all_var_names(&self, expr: &ast::Expr) -> Vec { + let mut vars = Vec::new(); + self.collect_var_names(expr, &mut vars); + vars + } + + /// Recursively collect all variable names from an expression + fn collect_var_names(&self, expr: &ast::Expr, vars: &mut Vec) { + match &expr.content { + ast::ExprData::Variable(ident) => { + vars.push(ident.content.0.to_string()); + } + ast::ExprData::Bracket(base_expr, index_expr) => { + // For array access like foo[0], build the full name + if let ast::ExprData::Variable(ident) = &base_expr.content { + let index_str = if let ast::ExprData::IntConst(val) = &index_expr.content { + format!("[{}]", val) + } else { + "[0]".to_string() + }; + vars.push(format!("{}{}", ident.content.0, index_str)); + } else { + self.collect_var_names(base_expr, vars); + } + self.collect_var_names(index_expr, vars); + } + ast::ExprData::Dot(base_expr, field) => { + // For field access like foo.bar, build the full path + let full_path = self.build_member_path(base_expr, &field.content.0); + vars.push(full_path); + } + ast::ExprData::Unary(_, operand) => { + self.collect_var_names(operand, vars); + } + ast::ExprData::Binary(_, lhs, rhs) => { + self.collect_var_names(lhs, vars); + self.collect_var_names(rhs, vars); + } + ast::ExprData::Ternary(cond, if_true, if_false) => { + self.collect_var_names(cond, vars); + self.collect_var_names(if_true, vars); + self.collect_var_names(if_false, vars); + } + // Note: Assignment is intentionally NOT handled here to avoid infinite recursion. + // Assignments are handled specially in visit_expr() to track variable aliases. + // If we recursively process assignments here, it creates a loop since visit_expr + // calls extract_all_var_names which calls this function. + ast::ExprData::FunCall(_, args) => { + for arg in args { + self.collect_var_names(arg, vars); + } + } + _ => {} + } + } +} + +/// GL type constants matching WebGL spec +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[repr(u32)] +pub enum GLType { + Float = 0x1406, + FloatVec2 = 0x8B50, + FloatVec3 = 0x8B51, + FloatVec4 = 0x8B52, + Int = 0x1404, + IntVec2 = 0x8B53, + IntVec3 = 0x8B54, + IntVec4 = 0x8B55, + UnsignedInt = 0x1405, + Bool = 0x8B56, + BoolVec2 = 0x8B57, + BoolVec3 = 0x8B58, + BoolVec4 = 0x8B59, + FloatMat2 = 0x8B5A, + FloatMat3 = 0x8B5B, + FloatMat4 = 0x8B5C, + FloatMat2x3 = 0x8B65, + FloatMat2x4 = 0x8B66, + FloatMat3x2 = 0x8B67, + FloatMat3x4 = 0x8B68, + FloatMat4x2 = 0x8B69, + FloatMat4x3 = 0x8B6A, + Sampler2D = 0x8B5E, + SamplerCube = 0x8B60, + Unknown = 0, +} + +/// Represents metadata about a vertex attribute in GLSL +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct GLSLAttribute { + /// The name of the attribute (e.g., "position", "normal") + pub name: String, + /// The GL type constant (e.g., GL_FLOAT_VEC3, GL_FLOAT_VEC4) + #[serde(rename = "type")] + pub gl_type: u32, + /// The size of the attribute (number of elements, usually 1 for single values) + pub size: i32, + /// The assigned location of the attribute (0-based index) + pub location: i32, + /// Whether the attribute is active (referenced in the shader) + pub active: bool, +} + +/// Represents metadata about a uniform variable in GLSL +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct GLSLUniform { + /// The name of the uniform (e.g., "modelViewMatrix", "lightColor") + pub name: String, + /// The GL type constant (e.g., GL_FLOAT_MAT4, GL_FLOAT_VEC3, GL_SAMPLER_2D) + #[serde(rename = "type")] + pub gl_type: u32, + /// The size of the uniform (number of elements, 1 for non-arrays) + pub size: i32, + /// Whether the uniform is active (referenced in the shader) + pub active: bool, +} + +/// Combined result of shader parsing containing both attributes and uniforms +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct GLSLShaderVariables { + /// List of vertex attributes + pub attributes: Vec, + /// List of uniform variables + pub uniforms: Vec, +} + +/// Struct field definition +#[derive(Debug, Clone)] +struct StructField { + name: String, + gl_type: GLType, + is_struct: bool, + struct_name: Option, +} + +/// Analyzer for extracting shader variables (attributes, uniforms) from GLSL source code +pub struct GLSLShaderAnalyzer { + attributes: Vec, + uniforms: Vec, + next_location: i32, + referenced_names: std::collections::HashSet, + /// Map of struct names to their field definitions + struct_definitions: std::collections::HashMap>, + /// Whether to include gl_InstanceID/gl_VertexID as active attributes if used + include_builtin_attributes: bool, +} + +impl GLSLShaderAnalyzer { + /// Create a new analyzer + pub fn new() -> Self { + Self { + attributes: Vec::new(), + uniforms: Vec::new(), + next_location: 0, + referenced_names: std::collections::HashSet::new(), + struct_definitions: std::collections::HashMap::new(), + include_builtin_attributes: false, + } + } + + /// Create a new analyzer with configuration for built-in attributes + pub fn with_builtin_attributes(include_builtin: bool) -> Self { + Self { + attributes: Vec::new(), + uniforms: Vec::new(), + next_location: 0, + referenced_names: std::collections::HashSet::new(), + struct_definitions: std::collections::HashMap::new(), + include_builtin_attributes: include_builtin, + } + } + + /// Parse GLSL source and extract all attribute and uniform declarations + pub fn parse(&mut self, source: &str) -> Result<(), String> { + use glsl_lang::{ + ast::TranslationUnit, lexer::full::fs::PreprocessorExt, parse::IntoParseBuilderExt, + }; + + let mut processor = glsl_lang_pp::processor::fs::StdProcessor::new(); + let tu: TranslationUnit = processor + .open_source(source, Path::new(".")) + .builder() + .parse() + .map(|(mut tu, _, iter)| { + iter.into_directives().inject(&mut tu); + tu + }) + .map_err(|e| format!("Failed to parse GLSL source: {:?}", e))?; + + // First pass: collect all variable references + let mut ref_collector = ReferenceCollector::new(); + tu.visit(&mut ref_collector); + self.referenced_names = ref_collector.referenced_names; + + // Second pass: visit the AST to find attributes and uniforms + use glsl_lang::visitor::Host; + tu.visit(self); + + // Mark inactive variables instead of filtering them out + for attr in &mut self.attributes { + attr.active = self.referenced_names.contains(&attr.name); + } + for uniform in &mut self.uniforms { + uniform.active = self.referenced_names.contains(&uniform.name); + } + + // Add built-in attributes if requested and they're used in the shader + if self.include_builtin_attributes { + if ref_collector.uses_gl_instance_id { + self.attributes.push(GLSLAttribute { + name: "gl_InstanceID".to_string(), + gl_type: GLType::Int as u32, + size: 1, + location: -1, // Built-in attributes don't have explicit locations + active: true, + }); + } + if ref_collector.uses_gl_vertex_id { + self.attributes.push(GLSLAttribute { + name: "gl_VertexID".to_string(), + gl_type: GLType::Int as u32, + size: 1, + location: -1, // Built-in attributes don't have explicit locations + active: true, + }); + } + } + + Ok(()) + } + + /// Get the parsed attributes + pub fn get_attributes(&self) -> &[GLSLAttribute] { + &self.attributes + } + + /// Get the parsed uniforms + pub fn get_uniforms(&self) -> &[GLSLUniform] { + &self.uniforms + } + + /// Find attribute location by name + pub fn get_attrib_location(&self, name: &str) -> Option { + self + .attributes + .iter() + .find(|attr| attr.name == name) + .map(|attr| attr.location) + } + + /// Extract GL type from TypeSpecifierNonArray + fn type_to_gl_type(&self, ty: &ast::TypeSpecifierNonArray) -> GLType { + match &ty.content { + ast::TypeSpecifierNonArrayData::Float => GLType::Float, + ast::TypeSpecifierNonArrayData::Double => GLType::Float, // Map double to float + ast::TypeSpecifierNonArrayData::Int => GLType::Int, + ast::TypeSpecifierNonArrayData::UInt => GLType::UnsignedInt, + ast::TypeSpecifierNonArrayData::Bool => GLType::Bool, + ast::TypeSpecifierNonArrayData::Vec2 => GLType::FloatVec2, + ast::TypeSpecifierNonArrayData::Vec3 => GLType::FloatVec3, + ast::TypeSpecifierNonArrayData::Vec4 => GLType::FloatVec4, + ast::TypeSpecifierNonArrayData::DVec2 => GLType::FloatVec2, + ast::TypeSpecifierNonArrayData::DVec3 => GLType::FloatVec3, + ast::TypeSpecifierNonArrayData::DVec4 => GLType::FloatVec4, + ast::TypeSpecifierNonArrayData::BVec2 => GLType::BoolVec2, + ast::TypeSpecifierNonArrayData::BVec3 => GLType::BoolVec3, + ast::TypeSpecifierNonArrayData::BVec4 => GLType::BoolVec4, + ast::TypeSpecifierNonArrayData::IVec2 => GLType::IntVec2, + ast::TypeSpecifierNonArrayData::IVec3 => GLType::IntVec3, + ast::TypeSpecifierNonArrayData::IVec4 => GLType::IntVec4, + ast::TypeSpecifierNonArrayData::UVec2 => GLType::IntVec2, // Map uint to int + ast::TypeSpecifierNonArrayData::UVec3 => GLType::IntVec3, + ast::TypeSpecifierNonArrayData::UVec4 => GLType::IntVec4, + ast::TypeSpecifierNonArrayData::Mat2 => GLType::FloatMat2, + ast::TypeSpecifierNonArrayData::Mat3 => GLType::FloatMat3, + ast::TypeSpecifierNonArrayData::Mat4 => GLType::FloatMat4, + ast::TypeSpecifierNonArrayData::Mat23 => GLType::FloatMat2x3, + ast::TypeSpecifierNonArrayData::Mat24 => GLType::FloatMat2x4, + ast::TypeSpecifierNonArrayData::Mat32 => GLType::FloatMat3x2, + ast::TypeSpecifierNonArrayData::Mat34 => GLType::FloatMat3x4, + ast::TypeSpecifierNonArrayData::Mat42 => GLType::FloatMat4x2, + ast::TypeSpecifierNonArrayData::Mat43 => GLType::FloatMat4x3, + ast::TypeSpecifierNonArrayData::DMat2 => GLType::FloatMat2, + ast::TypeSpecifierNonArrayData::DMat3 => GLType::FloatMat3, + ast::TypeSpecifierNonArrayData::DMat4 => GLType::FloatMat4, + ast::TypeSpecifierNonArrayData::DMat23 => GLType::FloatMat2x3, + ast::TypeSpecifierNonArrayData::DMat24 => GLType::FloatMat2x4, + ast::TypeSpecifierNonArrayData::DMat32 => GLType::FloatMat3x2, + ast::TypeSpecifierNonArrayData::DMat34 => GLType::FloatMat3x4, + ast::TypeSpecifierNonArrayData::DMat42 => GLType::FloatMat4x2, + ast::TypeSpecifierNonArrayData::DMat43 => GLType::FloatMat4x3, + ast::TypeSpecifierNonArrayData::Sampler2D => GLType::Sampler2D, + ast::TypeSpecifierNonArrayData::SamplerCube => GLType::SamplerCube, + _ => GLType::Unknown, + } + } + + /// Extract explicit location from layout qualifier if present + fn extract_layout_location(&self, qualifiers: &[ast::LayoutQualifierSpec]) -> Option { + for qualifier in qualifiers { + match &qualifier.content { + ast::LayoutQualifierSpecData::Identifier(ident, Some(expr)) => { + if ident.content.0.as_str() == "location" { + // Try to extract the constant expression value + if let ast::ExprData::IntConst(val) = &expr.content { + return Some(*val); + } + } + } + _ => {} + } + } + None + } + + /// Check if a declaration has an attribute/in qualifier + fn has_attribute_qualifier(&self, qualifiers: &[ast::TypeQualifierSpec]) -> (bool, Option) { + let mut is_attribute = false; + let mut layout_location = None; + + for qualifier in qualifiers { + match &qualifier.content { + ast::TypeQualifierSpecData::Storage(storage) => match &storage.content { + ast::StorageQualifierData::Attribute | ast::StorageQualifierData::In => { + is_attribute = true; + } + _ => {} + }, + ast::TypeQualifierSpecData::Layout(layout) => { + layout_location = self.extract_layout_location(&layout.content.ids); + } + _ => {} + } + } + + (is_attribute, layout_location) + } + + /// Check if a declaration has a uniform qualifier + fn has_uniform_qualifier(&self, qualifiers: &[ast::TypeQualifierSpec]) -> bool { + for qualifier in qualifiers { + if let ast::TypeQualifierSpecData::Storage(storage) = &qualifier.content { + if let ast::StorageQualifierData::Uniform = &storage.content { + return true; + } + } + } + false + } + + /// Expand a struct uniform into its individual members + /// Returns a list of (name, gl_type, size) tuples + /// + /// # Arguments + /// * `base_name` - The base name of the uniform + /// * `struct_name` - The name of the struct type + /// * `array_size` - The size of the array + /// * `is_array_decl` - Whether this was declared as an array (even if size is 1) + fn expand_struct_uniform( + &self, + base_name: &str, + struct_name: &str, + array_size: i32, + is_array_decl: bool, + ) -> Vec<(String, u32, i32)> { + let mut expanded = Vec::new(); + + if let Some(fields) = self.struct_definitions.get(struct_name) { + if is_array_decl { + // Array of structs: expand as uniformName[i].field + // Note: even for single-element arrays (declared as [1]), we use [0] notation + for i in 0..array_size { + for field in fields { + let member_name = format!("{}[{}].{}", base_name, i, field.name); + if field.is_struct { + if let Some(ref nested_struct) = field.struct_name { + // Recursively expand nested structs (nested structs are not arrays) + let nested = self.expand_struct_uniform(&member_name, nested_struct, 1, false); + expanded.extend(nested); + } + } else { + expanded.push((member_name, field.gl_type as u32, 1)); + } + } + } + } else { + // Non-array struct: expand as uniformName.field + for field in fields { + let member_name = format!("{}.{}", base_name, field.name); + if field.is_struct { + if let Some(ref nested_struct) = field.struct_name { + // Recursively expand nested structs + let nested = self.expand_struct_uniform(&member_name, nested_struct, 1, false); + expanded.extend(nested); + } + } else { + expanded.push((member_name, field.gl_type as u32, 1)); + } + } + } + } + + expanded + } + + /// Check if declaration has an array specifier (even if size is 1) + fn is_array(&self, array_specifier: &Option) -> bool { + array_specifier.is_some() + } + + /// Extract array size from array specifier if present + fn get_array_size(&self, array_specifier: &Option) -> i32 { + if let Some(spec) = array_specifier { + if let Some(first_dim) = spec.content.dimensions.first() { + if let ast::ArraySpecifierDimensionData::ExplicitlySized(ref expr) = first_dim.content { + if let ast::ExprData::IntConst(val) = expr.content { + return val; + } + } + } + } + 1 // Default to 1 if no array or can't determine size + } + + /// Check if a type specifier is a struct type + fn is_struct_type(&self, ty: &ast::TypeSpecifierNonArray) -> Option { + match &ty.content { + ast::TypeSpecifierNonArrayData::TypeName(type_name) => { + Some(type_name.content.0.to_string()) + } + _ => None, + } + } +} + +impl Visitor for GLSLShaderAnalyzer { + /// Visit struct specifier to collect struct definitions + fn visit_struct_specifier(&mut self, spec: &ast::StructSpecifier) -> Visit { + if let Some(ref name) = spec.name { + let struct_name = name.content.0.to_string(); + let mut fields = Vec::new(); + + // Collect all fields from the struct + for field_decl in &spec.fields { + // field_decl.content is StructFieldSpecifier + // .ty is FullySpecifiedType + // .ty.ty is DIRECTLY TypeSpecifierNonArray (not TypeSpecifier!) + // This is different from SingleDeclaration where ty.ty.ty is needed + let type_spec_non_array = &field_decl.content.ty.ty; + let field_name_base = &field_decl.content.identifiers; + + for field in field_name_base { + let field_name = field.content.ident.content.0.to_string(); + + // Check if field is itself a struct + if let Some(nested_struct_name) = self.is_struct_type(type_spec_non_array) { + fields.push(StructField { + name: field_name, + gl_type: GLType::Unknown, + is_struct: true, + struct_name: Some(nested_struct_name), + }); + } else { + let gl_type = self.type_to_gl_type(type_spec_non_array); + fields.push(StructField { + name: field_name, + gl_type, + is_struct: false, + struct_name: None, + }); + } + } + } + + self.struct_definitions.insert(struct_name, fields); + } + + Visit::Children + } + + fn visit_single_declaration(&mut self, declaration: &ast::SingleDeclaration) -> Visit { + // Check if this declaration has qualifiers + if let Some(ref qualifier) = declaration.ty.qualifier { + let (is_attribute, layout_location) = + self.has_attribute_qualifier(&qualifier.content.qualifiers); + let is_uniform = self.has_uniform_qualifier(&qualifier.content.qualifiers); + + if is_attribute { + if let Some(ref name) = declaration.name { + // Extract GL type from the type specifier + let gl_type = self.type_to_gl_type(&declaration.ty.ty.ty) as u32; + + let location = layout_location.unwrap_or_else(|| { + let loc = self.next_location; + self.next_location += 1; + loc + }); + + // Get array size + let size = self.get_array_size(&declaration.array_specifier); + + self.attributes.push(GLSLAttribute { + name: name.content.0.to_string(), + gl_type, + size, + location, + active: false, // Will be set later after reference collection + }); + } + } else if is_uniform { + if let Some(ref name) = declaration.name { + let uniform_name = name.content.0.to_string(); + + // Check if this is a struct type + if let Some(struct_name) = self.is_struct_type(&declaration.ty.ty.ty) { + // Get array size and check if it's declared as an array + let is_array_decl = self.is_array(&declaration.array_specifier); + let array_size = self.get_array_size(&declaration.array_specifier); + + // Expand the struct into individual member uniforms + let expanded = self.expand_struct_uniform(&uniform_name, &struct_name, array_size, is_array_decl); + + for (member_name, member_gl_type, member_size) in expanded { + self.uniforms.push(GLSLUniform { + name: member_name, + gl_type: member_gl_type, + size: member_size, + active: false, // Will be set later after reference collection + }); + } + } else { + // Regular uniform (not a struct) + let gl_type = self.type_to_gl_type(&declaration.ty.ty.ty) as u32; + let size = self.get_array_size(&declaration.array_specifier); + + self.uniforms.push(GLSLUniform { + name: uniform_name, + gl_type, + size, + active: false, // Will be set later after reference collection + }); + } + } + } + } + + Visit::Children + } +} + #[cxx::bridge(namespace = "holocron::webgl")] mod ffi { extern "Rust" { + /// Patch GLSL source code from string #[cxx_name = "patchGLSLSourceFromStr"] fn patch_glsl_source_from_str(input: &str) -> String; + + /// Parse GLSL shader source and extract both attributes and uniforms as JSON + /// @param source The GLSL shader source code + /// @param include_builtin_attributes Whether to include gl_InstanceID/gl_VertexID as active attributes if used + #[cxx_name = "parseGLSLShader"] + fn parse_glsl_shader(source: &str, include_builtin_attributes: bool) -> String; + + /// Parse GLSL shader source and extract attributes as JSON (deprecated, use parseGLSLShader) + /// @param source The GLSL shader source code + /// @param include_builtin_attributes Whether to include gl_InstanceID/gl_VertexID as active attributes if used + #[cxx_name = "parseGLSLAttributes"] + fn parse_glsl_attributes(source: &str, include_builtin_attributes: bool) -> String; + + /// Parse GLSL shader source and extract uniforms as JSON (deprecated, use parseGLSLShader) + #[cxx_name = "parseGLSLUniforms"] + fn parse_glsl_uniforms(source: &str) -> String; + } +} + +/// Parse GLSL source and return both attributes and uniforms as JSON string +fn parse_glsl_shader(source: &str, include_builtin_attributes: bool) -> String { + let mut analyzer = GLSLShaderAnalyzer::with_builtin_attributes(include_builtin_attributes); + + match analyzer.parse(source) { + Ok(_) => { + let variables = GLSLShaderVariables { + attributes: analyzer.get_attributes().to_vec(), + uniforms: analyzer.get_uniforms().to_vec(), + }; + serde_json::to_string(&variables).unwrap_or_else(|_| r#"{"attributes":[],"uniforms":[]}"#.to_string()) + } + Err(_) => r#"{"attributes":[],"uniforms":[]}"#.to_string(), + } +} + +/// Parse GLSL source and return all attributes as JSON string +fn parse_glsl_attributes(source: &str, include_builtin_attributes: bool) -> String { + let mut analyzer = GLSLShaderAnalyzer::with_builtin_attributes(include_builtin_attributes); + + match analyzer.parse(source) { + Ok(_) => { + let attributes = analyzer.get_attributes(); + serde_json::to_string(attributes).unwrap_or_else(|_| "[]".to_string()) + } + Err(_) => "[]".to_string(), + } +} + +/// Parse GLSL source and return all uniforms as JSON string +fn parse_glsl_uniforms(source: &str) -> String { + let mut analyzer = GLSLShaderAnalyzer::new(); + + match analyzer.parse(source) { + Ok(_) => { + let uniforms = analyzer.get_uniforms(); + serde_json::to_string(uniforms).unwrap_or_else(|_| "[]".to_string()) + } + Err(_) => "[]".to_string(), } } #[cfg(test)] mod tests { use super::*; - use std::ffi::CString; #[test] fn test_patch_glsl_source() { @@ -235,4 +1008,856 @@ vec3 test() { "# ) } + + #[test] + fn test_parse_glsl_attributes_basic() { + let source_str = r#" +#version 300 es +precision highp float; + +in vec3 position; +in vec3 normal; +in vec2 uv; + +uniform mat4 modelViewMatrix; + +out vec3 vNormal; +out vec2 vUv; + +void main() { + gl_Position = modelViewMatrix * vec4(position, 1.0); + vNormal = normal; + vUv = uv; +} +"#; + let mut parser = GLSLShaderAnalyzer::new(); + parser.parse(source_str).expect("Failed to parse GLSL"); + + let attributes = parser.get_attributes(); + assert_eq!(attributes.len(), 3); + + // Check first attribute + assert_eq!(attributes[0].name, "position"); + assert_eq!(attributes[0].gl_type, GLType::FloatVec3 as u32); + assert_eq!(attributes[0].size, 1); + assert_eq!(attributes[0].location, 0); + assert_eq!(attributes[0].active, true); + + // Check second attribute + assert_eq!(attributes[1].name, "normal"); + assert_eq!(attributes[1].gl_type, GLType::FloatVec3 as u32); + assert_eq!(attributes[1].size, 1); + assert_eq!(attributes[1].location, 1); + assert_eq!(attributes[1].active, true); + + // Check third attribute + assert_eq!(attributes[2].name, "uv"); + assert_eq!(attributes[2].gl_type, GLType::FloatVec2 as u32); + assert_eq!(attributes[2].size, 1); + assert_eq!(attributes[2].location, 2); + assert_eq!(attributes[2].active, true); + } + + #[test] + fn test_parse_glsl_attributes_with_layout() { + let source_str = r#" +#version 300 es +precision highp float; + +layout(location = 0) in vec3 aPos; +layout(location = 1) in vec3 aNormal; +layout(location = 2) in vec2 aTexCoord; + +out vec3 vNormal; +out vec2 vTexCoord; + +void main() { + gl_Position = vec4(aPos, 1.0); + vNormal = aNormal; + vTexCoord = aTexCoord; +} +"#; + let mut parser = GLSLShaderAnalyzer::new(); + parser.parse(source_str).expect("Failed to parse GLSL"); + + let attributes = parser.get_attributes(); + assert_eq!(attributes.len(), 3); + + assert_eq!(attributes[0].name, "aPos"); + assert_eq!(attributes[0].gl_type, GLType::FloatVec3 as u32); + assert_eq!(attributes[0].location, 0); + + assert_eq!(attributes[1].name, "aNormal"); + assert_eq!(attributes[1].gl_type, GLType::FloatVec3 as u32); + assert_eq!(attributes[1].location, 1); + + assert_eq!(attributes[2].name, "aTexCoord"); + assert_eq!(attributes[2].gl_type, GLType::FloatVec2 as u32); + assert_eq!(attributes[2].location, 2); + } + + #[test] + fn test_parse_glsl_attributes_glsl100() { + let source_str = r#" +precision highp float; + +attribute vec3 position; +attribute vec3 normal; +attribute vec2 texCoord; + +varying vec3 vNormal; +varying vec2 vTexCoord; + +void main() { + gl_Position = vec4(position, 1.0); + vNormal = normal; + vTexCoord = texCoord; +} +"#; + let mut parser = GLSLShaderAnalyzer::new(); + parser.parse(source_str).expect("Failed to parse GLSL"); + + let attributes = parser.get_attributes(); + assert_eq!(attributes.len(), 3); + + assert_eq!(attributes[0].name, "position"); + assert_eq!(attributes[0].gl_type, GLType::FloatVec3 as u32); + + assert_eq!(attributes[1].name, "normal"); + assert_eq!(attributes[1].gl_type, GLType::FloatVec3 as u32); + + assert_eq!(attributes[2].name, "texCoord"); + assert_eq!(attributes[2].gl_type, GLType::FloatVec2 as u32); + } + + #[test] + fn test_get_attrib_location() { + let source_str = r#" +#version 300 es +in vec3 position; +in vec3 normal; +in vec2 uv; + +out vec3 vNormal; +out vec2 vUv; + +void main() { + gl_Position = vec4(position, 1.0); + vNormal = normal; + vUv = uv; +} +"#; + let mut parser = GLSLShaderAnalyzer::new(); + parser.parse(source_str).expect("Failed to parse GLSL"); + + assert_eq!(parser.get_attrib_location("position"), Some(0)); + assert_eq!(parser.get_attrib_location("normal"), Some(1)); + assert_eq!(parser.get_attrib_location("uv"), Some(2)); + assert_eq!(parser.get_attrib_location("nonexistent"), None); + } + + #[test] + fn test_parse_glsl_attributes_ffi() { + let source_str = r#" +#version 300 es +in vec3 position; +in vec3 normal; + +out vec3 vNormal; + +void main() { + gl_Position = vec4(position, 1.0); + vNormal = normal; +} +"#; + let result = parse_glsl_attributes(source_str, false); + + // Parse the JSON result + let attributes: Vec = serde_json::from_str(&result).expect("Failed to parse JSON"); + assert_eq!(attributes.len(), 2); + assert_eq!(attributes[0].name, "position"); + assert_eq!(attributes[1].name, "normal"); + } + + #[test] + fn test_parse_glsl_attributes_marks_inactive() { + let source_str = r#" +#version 300 es +in vec3 position; +in vec3 normal; +in vec2 unused_attr; + +out vec3 vNormal; + +void main() { + gl_Position = vec4(position, 1.0); + vNormal = normal; +} +"#; + let mut parser = GLSLShaderAnalyzer::new(); + parser.parse(source_str).expect("Failed to parse GLSL"); + + let attributes = parser.get_attributes(); + // All attributes should be returned, but unused_attr should be marked inactive + assert_eq!(attributes.len(), 3); + assert_eq!(attributes[0].name, "position"); + assert_eq!(attributes[0].active, true); + assert_eq!(attributes[1].name, "normal"); + assert_eq!(attributes[1].active, true); + assert_eq!(attributes[2].name, "unused_attr"); + assert_eq!(attributes[2].active, false); // This one is inactive + } + + #[test] + fn test_parse_glsl_uniforms_basic() { + let source_str = r#" +#version 300 es +precision highp float; + +in vec3 position; +uniform mat4 modelViewMatrix; +uniform mat4 projectionMatrix; +uniform vec3 lightPosition; + +out vec3 vPosition; + +void main() { + vec4 worldPos = modelViewMatrix * vec4(position, 1.0); + gl_Position = projectionMatrix * worldPos; + vPosition = lightPosition; +} +"#; + let mut analyzer = GLSLShaderAnalyzer::new(); + analyzer.parse(source_str).expect("Failed to parse GLSL"); + + let uniforms = analyzer.get_uniforms(); + assert_eq!(uniforms.len(), 3); + + assert_eq!(uniforms[0].name, "modelViewMatrix"); + assert_eq!(uniforms[0].gl_type, GLType::FloatMat4 as u32); + + assert_eq!(uniforms[1].name, "projectionMatrix"); + assert_eq!(uniforms[1].gl_type, GLType::FloatMat4 as u32); + + assert_eq!(uniforms[2].name, "lightPosition"); + assert_eq!(uniforms[2].gl_type, GLType::FloatVec3 as u32); + } + + #[test] + fn test_parse_glsl_uniforms_marks_inactive() { + let source_str = r#" +#version 300 es +precision highp float; + +in vec3 position; +uniform mat4 mvpMatrix; +uniform vec3 unusedColor; +uniform float unusedScale; + +void main() { + gl_Position = mvpMatrix * vec4(position, 1.0); +} +"#; + let mut analyzer = GLSLShaderAnalyzer::new(); + analyzer.parse(source_str).expect("Failed to parse GLSL"); + + let uniforms = analyzer.get_uniforms(); + // All uniforms should be returned, but only mvpMatrix should be active + assert_eq!(uniforms.len(), 3); + assert_eq!(uniforms[0].name, "mvpMatrix"); + assert_eq!(uniforms[0].gl_type, GLType::FloatMat4 as u32); + assert_eq!(uniforms[0].active, true); + assert_eq!(uniforms[1].name, "unusedColor"); + assert_eq!(uniforms[1].active, false); + assert_eq!(uniforms[2].name, "unusedScale"); + assert_eq!(uniforms[2].active, false); + } + + #[test] + fn test_parse_glsl_uniforms_ffi() { + let source_str = r#" +#version 300 es +in vec3 position; +uniform mat4 transform; +uniform vec4 color; + +out vec4 vColor; + +void main() { + gl_Position = transform * vec4(position, 1.0); + vColor = color; +} +"#; + let result = parse_glsl_uniforms(source_str); + + // Parse the JSON result + let uniforms: Vec = serde_json::from_str(&result).expect("Failed to parse JSON"); + assert_eq!(uniforms.len(), 2); + assert_eq!(uniforms[0].name, "transform"); + assert_eq!(uniforms[0].gl_type, GLType::FloatMat4 as u32); + assert_eq!(uniforms[1].name, "color"); + assert_eq!(uniforms[1].gl_type, GLType::FloatVec4 as u32); + } + + #[test] + fn test_parse_glsl_shader_combined() { + let source_str = r#" +#version 300 es +in vec3 position; +in vec3 normal; +in vec2 unused_attr; + +uniform mat4 modelMatrix; +uniform mat4 viewMatrix; +uniform vec3 unusedColor; + +out vec3 vNormal; + +void main() { + gl_Position = modelMatrix * viewMatrix * vec4(position, 1.0); + vNormal = normal; +} +"#; + let result = parse_glsl_shader(source_str, false); + + // Parse the JSON result + let variables: GLSLShaderVariables = serde_json::from_str(&result).expect("Failed to parse JSON"); + + // Check attributes + assert_eq!(variables.attributes.len(), 3); + assert_eq!(variables.attributes[0].name, "position"); + assert_eq!(variables.attributes[0].active, true); + assert_eq!(variables.attributes[1].name, "normal"); + assert_eq!(variables.attributes[1].active, true); + assert_eq!(variables.attributes[2].name, "unused_attr"); + assert_eq!(variables.attributes[2].active, false); + + // Check uniforms + assert_eq!(variables.uniforms.len(), 3); + assert_eq!(variables.uniforms[0].name, "modelMatrix"); + assert_eq!(variables.uniforms[0].active, true); + assert_eq!(variables.uniforms[1].name, "viewMatrix"); + assert_eq!(variables.uniforms[1].active, true); + assert_eq!(variables.uniforms[2].name, "unusedColor"); + assert_eq!(variables.uniforms[2].active, false); + } + + #[test] + fn test_parse_glsl_structured_uniforms() { + let source_str = r#" +#version 300 es +struct DirectionalLight { + vec3 direction; + vec3 color; + float intensity; +}; + +in vec3 position; +uniform DirectionalLight directionalLights[2]; +uniform vec3 ambient; + +out vec4 fragColor; + +void main() { + gl_Position = vec4(position, 1.0); + vec3 lighting = ambient; + lighting += directionalLights[0].color * directionalLights[0].intensity; + lighting += directionalLights[1].direction * 0.5; + fragColor = vec4(lighting, 1.0); +} +"#; + let mut analyzer = GLSLShaderAnalyzer::new(); + analyzer.parse(source_str).expect("Failed to parse"); + + let uniforms = analyzer.get_uniforms(); + + // Uniforms should be in declaration order: directionalLights[0].*, directionalLights[1].*, ambient + // Should have 7 total: 6 from the 2-element array of structs + 1 ambient + assert_eq!(uniforms.len(), 7); + + // Check expanded struct uniforms (directionalLights declared first) + assert_eq!(uniforms[0].name, "directionalLights[0].direction"); + assert_eq!(uniforms[0].gl_type, GLType::FloatVec3 as u32); + assert_eq!(uniforms[0].active, false); // Not used in the shader + + assert_eq!(uniforms[1].name, "directionalLights[0].color"); + assert_eq!(uniforms[1].gl_type, GLType::FloatVec3 as u32); + assert_eq!(uniforms[1].active, true); // Used in shader + + assert_eq!(uniforms[2].name, "directionalLights[0].intensity"); + assert_eq!(uniforms[2].gl_type, GLType::Float as u32); + assert_eq!(uniforms[2].active, true); // Used in shader + + assert_eq!(uniforms[3].name, "directionalLights[1].direction"); + assert_eq!(uniforms[3].gl_type, GLType::FloatVec3 as u32); + assert_eq!(uniforms[3].active, true); // Used in shader + + assert_eq!(uniforms[4].name, "directionalLights[1].color"); + assert_eq!(uniforms[4].gl_type, GLType::FloatVec3 as u32); + assert_eq!(uniforms[4].active, false); // Not used in the shader + + assert_eq!(uniforms[5].name, "directionalLights[1].intensity"); + assert_eq!(uniforms[5].gl_type, GLType::Float as u32); + assert_eq!(uniforms[5].active, false); // Not used in the shader + + // Check ambient uniform (declared after directionalLights) + assert_eq!(uniforms[6].name, "ambient"); + assert_eq!(uniforms[6].gl_type, GLType::FloatVec3 as u32); + assert_eq!(uniforms[6].active, true); + } + + #[test] + fn test_parse_glsl_nested_struct_uniforms() { + let source_str = r#" +#version 300 es +struct Material { + vec3 ambient; + vec3 diffuse; +}; + +struct Light { + vec3 position; + Material material; +}; + +in vec3 vertPosition; +uniform Light light; + +out vec4 fragColor; + +void main() { + gl_Position = vec4(vertPosition, 1.0); + fragColor = vec4(light.material.diffuse, 1.0); +} +"#; + let mut analyzer = GLSLShaderAnalyzer::new(); + analyzer.parse(source_str).expect("Failed to parse"); + + let uniforms = analyzer.get_uniforms(); + + // Should have: light.position, light.material.ambient, light.material.diffuse + assert_eq!(uniforms.len(), 3); + + assert_eq!(uniforms[0].name, "light.position"); + assert_eq!(uniforms[0].gl_type, GLType::FloatVec3 as u32); + assert_eq!(uniforms[0].active, false); // Not used + + assert_eq!(uniforms[1].name, "light.material.ambient"); + assert_eq!(uniforms[1].gl_type, GLType::FloatVec3 as u32); + assert_eq!(uniforms[1].active, false); // Not used + + assert_eq!(uniforms[2].name, "light.material.diffuse"); + assert_eq!(uniforms[2].gl_type, GLType::FloatVec3 as u32); + assert_eq!(uniforms[2].active, true); // Used! + } + + #[test] + fn test_parse_glsl_with_builtin_attributes() { + let source_str = r#" +#version 300 es +in vec3 position; +in vec3 offset; + +uniform mat4 mvpMatrix; + +void main() { + // Use gl_InstanceID to index into transforms + vec3 instanceOffset = offset * float(gl_InstanceID); + vec4 worldPos = vec4(position + instanceOffset, 1.0); + gl_Position = mvpMatrix * worldPos; + + // Use gl_VertexID for something + float fade = float(gl_VertexID) * 0.01; +} +"#; + + // Test WITHOUT including builtin attributes + let mut analyzer_no_builtin = GLSLShaderAnalyzer::new(); + analyzer_no_builtin.parse(source_str).expect("Failed to parse"); + let attributes_no_builtin = analyzer_no_builtin.get_attributes(); + + // Should only have position and offset (no gl_InstanceID or gl_VertexID) + assert_eq!(attributes_no_builtin.len(), 2); + assert_eq!(attributes_no_builtin[0].name, "position"); + assert_eq!(attributes_no_builtin[1].name, "offset"); + + // Test WITH including builtin attributes + let mut analyzer_with_builtin = GLSLShaderAnalyzer::with_builtin_attributes(true); + analyzer_with_builtin.parse(source_str).expect("Failed to parse"); + let attributes_with_builtin = analyzer_with_builtin.get_attributes(); + + // Should have position, offset, gl_InstanceID, and gl_VertexID + assert_eq!(attributes_with_builtin.len(), 4); + assert_eq!(attributes_with_builtin[0].name, "position"); + assert_eq!(attributes_with_builtin[0].active, true); + assert_eq!(attributes_with_builtin[1].name, "offset"); + assert_eq!(attributes_with_builtin[1].active, true); + assert_eq!(attributes_with_builtin[2].name, "gl_InstanceID"); + assert_eq!(attributes_with_builtin[2].gl_type, GLType::Int as u32); + assert_eq!(attributes_with_builtin[2].active, true); + assert_eq!(attributes_with_builtin[2].location, -1); // Builtin has no explicit location + assert_eq!(attributes_with_builtin[3].name, "gl_VertexID"); + assert_eq!(attributes_with_builtin[3].gl_type, GLType::Int as u32); + assert_eq!(attributes_with_builtin[3].active, true); + assert_eq!(attributes_with_builtin[3].location, -1); // Builtin has no explicit location + } + + #[test] + fn test_parse_glsl_builtin_attributes_ffi() { + let source_str = r#" +#version 300 es +in vec3 position; + +void main() { + gl_Position = vec4(position * float(gl_InstanceID), 1.0); +} +"#; + + // Test FFI with builtin attributes enabled + let result = parse_glsl_shader(source_str, true); + let variables: GLSLShaderVariables = serde_json::from_str(&result).expect("Failed to parse JSON"); + + // Should have position and gl_InstanceID + assert_eq!(variables.attributes.len(), 2); + assert_eq!(variables.attributes[0].name, "position"); + assert_eq!(variables.attributes[1].name, "gl_InstanceID"); + assert_eq!(variables.attributes[1].active, true); + } + + #[test] + fn test_parse_glsl_with_preprocessor_defines() { + // Test that preprocessor directives are handled before analysis + let source_str = r#" +#version 300 es +#define USE_NORMAL 1 +#define USE_UV 1 + +in vec3 position; +#if USE_NORMAL +in vec3 normal; +#endif +#if USE_UV +in vec2 uv; +#endif + +uniform mat4 mvp; + +void main() { + gl_Position = mvp * vec4(position, 1.0); +#if USE_NORMAL + vec3 n = normal; +#endif +#if USE_UV + vec2 texCoord = uv; +#endif +} +"#; + let mut analyzer = GLSLShaderAnalyzer::new(); + analyzer.parse(source_str).expect("Failed to parse GLSL with preprocessor"); + + let attributes = analyzer.get_attributes(); + // Should include all three attributes (position, normal, uv) since preprocessor enables them + assert_eq!(attributes.len(), 3); + assert_eq!(attributes[0].name, "position"); + assert_eq!(attributes[0].active, true); + assert_eq!(attributes[1].name, "normal"); + assert_eq!(attributes[1].active, true); + assert_eq!(attributes[2].name, "uv"); + assert_eq!(attributes[2].active, true); + + let uniforms = analyzer.get_uniforms(); + assert_eq!(uniforms.len(), 1); + assert_eq!(uniforms[0].name, "mvp"); + assert_eq!(uniforms[0].active, true); + } + + #[test] + fn test_parse_glsl_conditional_compilation() { + // Test that #ifdef and #if directives properly control attribute/uniform declarations + let source_str = r#" +#version 300 es +#define ENABLE_LIGHTING +#define ENABLE_TEXTURES + +in vec3 position; + +// These should be included (defined and enabled) +#ifdef ENABLE_LIGHTING +in vec3 normal; +uniform vec3 lightDir; +#endif + +#ifdef ENABLE_TEXTURES +in vec2 texCoord; +uniform vec4 texColor; +#endif + +// This should NOT be included (not defined) +#ifdef ENABLE_SHADOWS +in vec4 shadowCoord; +uniform mat4 shadowMatrix; +#endif + +// Conditional uniform inside #if with expression +#if defined(ENABLE_LIGHTING) && defined(ENABLE_TEXTURES) +uniform float shininess; +#endif + +out vec4 fragColor; + +void main() { + gl_Position = vec4(position, 1.0); + fragColor = vec4(1.0); +#ifdef ENABLE_LIGHTING + vec3 n = normal; + vec3 light = lightDir; + fragColor.rgb *= dot(n, light); +#endif +#ifdef ENABLE_TEXTURES + vec2 tc = texCoord; + fragColor *= texColor; +#endif +#if defined(ENABLE_LIGHTING) && defined(ENABLE_TEXTURES) + float s = shininess; + fragColor.a *= s; +#endif +} +"#; + let mut analyzer = GLSLShaderAnalyzer::new(); + analyzer.parse(source_str).expect("Failed to parse shader with conditional compilation"); + + let attributes = analyzer.get_attributes(); + // Should have: position, normal, texCoord (shadowCoord should NOT be included) + assert_eq!(attributes.len(), 3, "Should include only conditionally enabled attributes"); + assert_eq!(attributes[0].name, "position"); + assert_eq!(attributes[0].active, true); + assert_eq!(attributes[1].name, "normal"); + assert_eq!(attributes[1].active, true); + assert_eq!(attributes[2].name, "texCoord"); + assert_eq!(attributes[2].active, true); + + let uniforms = analyzer.get_uniforms(); + // Should have: lightDir, diffuseMap, shininess (shadowMatrix should NOT be included) + assert_eq!(uniforms.len(), 3, "Should include only conditionally enabled uniforms"); + + let light_dir = uniforms.iter().find(|u| u.name == "lightDir"); + assert!(light_dir.is_some(), "lightDir should be included (ENABLE_LIGHTING defined)"); + assert_eq!(light_dir.unwrap().active, true); + + let tex_color = uniforms.iter().find(|u| u.name == "texColor"); + assert!(tex_color.is_some(), "texColor should be included (ENABLE_TEXTURES defined)"); + assert_eq!(tex_color.unwrap().active, true); + + let shininess = uniforms.iter().find(|u| u.name == "shininess"); + assert!(shininess.is_some(), "shininess should be included (both flags defined)"); + assert_eq!(shininess.unwrap().active, true); + + // Verify that ENABLE_SHADOWS items are NOT included + let shadow_coord = attributes.iter().find(|a| a.name == "shadowCoord"); + assert!(shadow_coord.is_none(), "shadowCoord should NOT be included (ENABLE_SHADOWS not defined)"); + + let shadow_matrix = uniforms.iter().find(|u| u.name == "shadowMatrix"); + assert!(shadow_matrix.is_none(), "shadowMatrix should NOT be included (ENABLE_SHADOWS not defined)"); + } + + #[test] + fn test_parse_glsl_array_sizes() { + // Test that array sizes are correctly extracted + let source_str = r#" +#version 300 es +precision highp float; + +in vec3 position; +uniform vec4 colors[4]; +uniform mat4 transforms[3]; +uniform float values[10]; + +void main() { + vec4 color = colors[0] + colors[1]; + gl_Position = transforms[0] * vec4(position, 1.0); + float val = values[0]; +} +"#; + let mut analyzer = GLSLShaderAnalyzer::new(); + analyzer.parse(source_str).expect("Failed to parse GLSL with arrays"); + + let uniforms = analyzer.get_uniforms(); + assert_eq!(uniforms.len(), 3); + + // Check colors array + assert_eq!(uniforms[0].name, "colors"); + assert_eq!(uniforms[0].gl_type, GLType::FloatVec4 as u32); + assert_eq!(uniforms[0].size, 4); + assert_eq!(uniforms[0].active, true); + + // Check transforms array + assert_eq!(uniforms[1].name, "transforms"); + assert_eq!(uniforms[1].gl_type, GLType::FloatMat4 as u32); + assert_eq!(uniforms[1].size, 3); + assert_eq!(uniforms[1].active, true); + + // Check values array + assert_eq!(uniforms[2].name, "values"); + assert_eq!(uniforms[2].gl_type, GLType::Float as u32); + assert_eq!(uniforms[2].size, 10); + assert_eq!(uniforms[2].active, true); + } + + #[test] + fn test_parse_glsl_with_macro_array_size() { + // Test array size defined by preprocessor macro + // Note: This tests whether the current implementation handles this case + let source_str = r#" +#version 300 es +precision highp float; + +#define NUM_LIGHTS 5 + +in vec3 position; +uniform vec3 lightPositions[NUM_LIGHTS]; + +void main() { + vec3 lighting = vec3(0.0); + for (int i = 0; i < NUM_LIGHTS; i++) { + lighting += lightPositions[i]; + } + gl_Position = vec4(position + lighting * 0.01, 1.0); +} +"#; + let mut analyzer = GLSLShaderAnalyzer::new(); + let result = analyzer.parse(source_str); + + // The preprocessor should expand NUM_LIGHTS to 5 before parsing + assert!(result.is_ok(), "Should successfully parse shader with macro-defined array size"); + + let uniforms = analyzer.get_uniforms(); + assert_eq!(uniforms.len(), 1); + assert_eq!(uniforms[0].name, "lightPositions"); + assert_eq!(uniforms[0].gl_type, GLType::FloatVec3 as u32); + // After preprocessing, the array size should be 5 + assert_eq!(uniforms[0].size, 5, "Preprocessor should expand macro array size"); + assert_eq!(uniforms[0].active, true); + } + + #[test] + fn test_single_element_array_uses_bracket_notation() { + // Test that single-element arrays still use [0] notation + let source_str = r#" +#version 300 es +precision highp float; + +struct HemisphereLight { + vec3 skyColor; + vec3 groundColor; +}; + +in vec3 position; +uniform HemisphereLight hemisphereLights[1]; + +void main() { + vec3 color = hemisphereLights[0].skyColor + hemisphereLights[0].groundColor; + gl_Position = vec4(position + color * 0.01, 1.0); +} +"#; + let mut analyzer = GLSLShaderAnalyzer::new(); + analyzer.parse(source_str).expect("Failed to parse single-element array shader"); + + let uniforms = analyzer.get_uniforms(); + // Should expand to hemisphereLights[0].skyColor and hemisphereLights[0].groundColor + assert_eq!(uniforms.len(), 2); + + // Both should use [0] notation, not just "hemisphereLights.skyColor" + assert_eq!(uniforms[0].name, "hemisphereLights[0].skyColor"); + assert_eq!(uniforms[0].active, true); + + assert_eq!(uniforms[1].name, "hemisphereLights[0].groundColor"); + assert_eq!(uniforms[1].active, true); + } + + #[test] + fn test_variable_expansion_tracking() { + // Test that when a local variable is assigned from a uniform, + // references to the local variable mark the uniform as active + let source_str = r#" +#version 300 es +precision highp float; + +struct Foo { + vec3 f1; + vec3 f2; +}; + +in vec3 position; +uniform Foo foo[2]; + +out vec3 vColor; + +void main() { + Foo local_foo; + local_foo = foo[0]; // Assignment: local_foo is an alias of foo[0] + + // Reference to local_foo.f1 should mark foo[0].f1 as active + vColor = local_foo.f1; + + gl_Position = vec4(position, 1.0); +} +"#; + let mut analyzer = GLSLShaderAnalyzer::new(); + analyzer.parse(source_str).expect("Failed to parse variable expansion shader"); + + let uniforms = analyzer.get_uniforms(); + // Should have foo[0].f1, foo[0].f2, foo[1].f1, foo[1].f2 + assert_eq!(uniforms.len(), 4); + + // foo[0].f1 should be active (referenced via local_foo.f1) + let foo0_f1 = uniforms.iter().find(|u| u.name == "foo[0].f1"); + assert!(foo0_f1.is_some()); + assert_eq!(foo0_f1.unwrap().active, true, "foo[0].f1 should be active through local_foo.f1"); + + // foo[0].f2 should be inactive (not referenced) + let foo0_f2 = uniforms.iter().find(|u| u.name == "foo[0].f2"); + assert!(foo0_f2.is_some()); + assert_eq!(foo0_f2.unwrap().active, false, "foo[0].f2 should be inactive"); + + // foo[1].* should all be inactive + let foo1_f1 = uniforms.iter().find(|u| u.name == "foo[1].f1"); + assert!(foo1_f1.is_some()); + assert_eq!(foo1_f1.unwrap().active, false, "foo[1].f1 should be inactive"); + + let foo1_f2 = uniforms.iter().find(|u| u.name == "foo[1].f2"); + assert!(foo1_f2.is_some()); + assert_eq!(foo1_f2.unwrap().active, false, "foo[1].f2 should be inactive"); + } + + #[test] + fn test_parse_glsl_no_stack_overflow_on_reassignment() { + // This test verifies the fix for the stack overflow bug where + // reassignments like "transformedPos = normalMatrix * transformedPos" + // caused infinite recursion in collect_var_names + let source_str = r#" +in vec3 position; +uniform mat3 normalMatrix; + +void main() { + vec3 transformedPos = position; + transformedPos = normalMatrix * transformedPos; + gl_Position = vec4(transformedPos, 1.0); +} +"#; + let mut analyzer = GLSLShaderAnalyzer::new(); + // This should not cause a stack overflow + analyzer.parse(source_str).expect("Failed to parse shader with reassignment"); + + // Verify that the uniform is correctly marked as active + let uniforms = analyzer.get_uniforms(); + assert_eq!(uniforms.len(), 1); + assert_eq!(uniforms[0].name, "normalMatrix"); + assert_eq!(uniforms[0].active, true, "normalMatrix should be active"); + + // Verify that the attribute is correctly found + let attributes = analyzer.get_attributes(); + assert_eq!(attributes.len(), 1); + assert_eq!(attributes[0].name, "position"); + assert_eq!(attributes[0].active, true, "position should be active"); + } } diff --git a/docs/glsl-attribute-parser.md b/docs/glsl-attribute-parser.md new file mode 100644 index 000000000..175ffb8f0 --- /dev/null +++ b/docs/glsl-attribute-parser.md @@ -0,0 +1,475 @@ +# GLSL Shader Analyzer + +This document describes the GLSL shader analyzer implementation in JSAR Runtime, which allows parsing GLSL shader source code to extract variable metadata (attributes and uniforms). + +## Overview + +The GLSL shader analyzer uses the [glsl-lang](https://github.com/alixinne/glsl-lang) Rust crate to parse GLSL shaders and extract information about vertex attributes (their names, types, and locations) and uniforms (their names and types). This information is exposed to C++ via FFI for use in WebGL shader/program management. + +## Features + +- ✅ **Attribute parsing**: Supports GLSL 100 ES (`attribute` keyword) and GLSL 300 ES (`in` keyword) +- ✅ **Uniform parsing**: Extracts uniform variable declarations from shaders +- ✅ Handles explicit `layout(location = N)` qualifiers for attributes +- ✅ Auto-assigns attribute locations based on declaration order when not explicitly specified +- ✅ **Active/inactive marking** - marks variables with `active` field based on whether they're referenced in shader +- ✅ **Built-in attribute support** - optionally includes `gl_InstanceID` and `gl_VertexID` as active attributes when used +- ✅ **Structured uniform support** - flattens struct uniforms and arrays per WebGL spec +- ✅ Compatible with WebGL 1.0 and WebGL 2.0 shaders +- ✅ Full FFI interface for C++ integration with JSON-based data exchange + +## Important: Active Variable Marking + +The analyzer implements WebGL's "active variable" behavior: **all declared variables are returned, but marked as active or inactive**. This matches the behavior of WebGL's `getActiveAttrib()` and `getActiveUniform()` functions. + +A variable (attribute or uniform) is marked `active = true` if it is: +- Declared with the appropriate qualifier (`attribute`, `in`, or `uniform`) +- Referenced somewhere in the shader code (e.g., used in calculations, assigned to outputs, etc.) + +Variables that are declared but never used will have `active = false`, as they would typically be optimized away by the shader compiler. + +## Built-in Attributes (gl_InstanceID, gl_VertexID) + +When shaders use OpenGL/GLES built-in variables like `gl_InstanceID` or `gl_VertexID`, you can optionally have the analyzer include them in the attributes list by setting the `include_builtin_attributes` parameter to `true`: + +```cpp +// C++ example with built-in attributes +std::vector attributes; +std::vector uniforms; +GLSLShaderAnalyzer::Parse(shaderSource, attributes, uniforms, true); // true = include built-ins + +// Built-in attributes will have location = -1 and type = GL_INT +``` + +This is useful when you need to track all attributes that affect shader execution, including implicit built-in ones. + +## Rust API + +### Data Structures + +```rust +/// Represents metadata about a vertex attribute in GLSL +pub struct GLSLAttribute { + pub name: String, // Attribute name (e.g., "position", "normal") + pub type_name: String, // GLSL type (e.g., "vec3", "vec4", "mat4") + pub location: i32, // Assigned location (0-based index) +} + +/// Represents metadata about a uniform variable in GLSL +pub struct GLSLUniform { + pub name: String, // Uniform name (e.g., "modelViewMatrix", "lightColor") + pub type_name: String, // GLSL type (e.g., "mat4", "vec3", "sampler2D") +} + +/// Analyzer for extracting shader variables from GLSL source code +pub struct GLSLShaderAnalyzer { + // ... +} +``` + +### Methods + +```rust +impl GLSLShaderAnalyzer { + /// Create a new analyzer + pub fn new() -> Self; + + /// Parse GLSL source and extract all attribute and uniform declarations + pub fn parse(&mut self, source: &str) -> Result<(), String>; + + /// Get the parsed attributes + pub fn get_attributes(&self) -> &[GLSLAttribute]; + + /// Get the parsed uniforms + pub fn get_uniforms(&self) -> &[GLSLUniform]; + + /// Find attribute location by name + pub fn get_attrib_location(&self, name: &str) -> Option; +} +``` + +### FFI Functions + +```rust +/// Parse GLSL source and return all attributes as JSON string +fn parse_glsl_attributes(source: &str) -> String; + +/// Parse GLSL source and return all uniforms as JSON string +fn parse_glsl_uniforms(source: &str) -> String; + +/// Get attribute location by name +fn get_glsl_attrib_location(source: &str, name: &str) -> Result; +``` + +## C++ API + +### Include + +```cpp +#include +``` + +### Data Structures + +```cpp +namespace crates::webgl { + struct GLSLAttribute { + std::string name; // Attribute name + std::string type_name; // Attribute type + int32_t location; // Attribute location + }; + + struct GLSLUniform { + std::string name; // Uniform name + std::string type_name; // Uniform type + }; +} +``` + +### GLSLShaderAnalyzer Class + +```cpp +namespace crates::webgl { + class GLSLShaderAnalyzer { + public: + /// Parse GLSL shader source and extract all attribute declarations + static std::vector ParseAttributes(const std::string &source); + + /// Parse GLSL shader source and extract all uniform declarations + static std::vector ParseUniforms(const std::string &source); + + /// Get the location of a specific attribute by name + static std::optional GetAttribLocation(const std::string &source, + const std::string &name); + }; +} +``` + +## Usage Examples + +### C++ Examples + +#### Example 1: Parse All Attributes + +```cpp +#include +#include + +std::string vertexShader = R"( + #version 300 es + precision highp float; + + in vec3 position; + in vec3 normal; + in vec2 uv; + + uniform mat4 modelViewMatrix; + uniform mat4 projectionMatrix; + + out vec3 vNormal; + out vec2 vUv; + + void main() { + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + vNormal = normal; + vUv = uv; + } +)"; + +// Parse all attributes +auto attributes = crates::webgl::GLSLShaderAnalyzer::ParseAttributes(vertexShader); + +std::cout << "Found " << attributes.size() << " active attributes:" << std::endl; +for (const auto& attr : attributes) { + std::cout << " - " << attr.name + << " (" << attr.type_name << ")" + << " at location " << attr.location << std::endl; +} + +// Output: +// Found 3 attributes: +// - position (vec3) at location 0 +// - normal (vec3) at location 1 +// - uv (vec2) at location 2 +``` + +#### Example 2: Get Specific Attribute Location + +```cpp +#include +#include + +std::string vertexShader = R"( + #version 300 es + layout(location = 0) in vec3 aPos; + layout(location = 1) in vec3 aNormal; + + void main() { + gl_Position = vec4(aPos, 1.0); + } +)"; + +// Get location of a specific attribute +auto location = crates::webgl::GLSLShaderAnalyzer::GetAttribLocation(vertexShader, "aPos"); + +if (location.has_value()) { + std::cout << "aPos is at location " << location.value() << std::endl; +} else { + std::cout << "aPos not found" << std::endl; +} + +// Output: aPos is at location 0 +``` + +#### Example 3: Parse Uniforms + +```cpp +#include +#include + +std::string vertexShader = R"( + #version 300 es + in vec3 position; + in vec3 normal; + + uniform mat4 modelMatrix; + uniform mat4 viewMatrix; + uniform mat4 projectionMatrix; + uniform vec3 lightPosition; + + out vec3 vNormal; + + void main() { + mat4 mvpMatrix = projectionMatrix * viewMatrix * modelMatrix; + gl_Position = mvpMatrix * vec4(position, 1.0); + vNormal = normal; + vec3 light = lightPosition; // Use lightPosition + } +)"; + +// Parse all uniforms +auto uniforms = crates::webgl::GLSLShaderAnalyzer::ParseUniforms(vertexShader); + +std::cout << "Found " << uniforms.size() << " active uniforms:" << std::endl; +for (const auto& uniform : uniforms) { + std::cout << " - " << uniform.name << " (" << uniform.type_name << ")" << std::endl; +} + +// Output: +// Found 4 active uniforms: +// - modelMatrix (mat4) +// - viewMatrix (mat4) +// - projectionMatrix (mat4) +// - lightPosition (vec3) +``` + +#### Example 4: Integration with WebGL Program + +```cpp +#include +#include + +void setupWebGLProgram(client_graphics::WebGLProgram* program, + const std::string& vertexShaderSource) { + try { + // Parse attributes from shader source + auto attributes = crates::webgl::GLSLShaderAnalyzer::ParseAttributes(vertexShaderSource); + + // Set attribute locations in the program + for (const auto& attr : attributes) { + program->setAttribLocation(attr.name, attr.location); + } + + std::cout << "Successfully set up " << attributes.size() << " attributes" << std::endl; + } + catch (const std::exception& e) { + std::cerr << "Failed to parse shader attributes: " << e.what() << std::endl; + } +} +``` + +#### Example 4: Inactive Attribute Filtering + +```cpp +#include + +std::string vertexShader = R"( + #version 300 es + in vec3 position; + in vec3 normal; + in vec2 unused_texcoord; // This attribute is declared but never used + + uniform mat4 mvpMatrix; + out vec3 vNormal; + + void main() { + gl_Position = mvpMatrix * vec4(position, 1.0); + vNormal = normal; + // Note: unused_texcoord is not referenced anywhere + } +)"; + +// Parse attributes - only active (referenced) attributes are returned +auto attributes = crates::webgl::GLSLShaderAnalyzer::ParseAttributes(vertexShader); + +// Only 2 attributes will be returned: position and normal +// unused_texcoord is filtered out because it's never referenced +std::cout << "Active attributes: " << attributes.size() << std::endl; // Prints: 2 + +for (const auto& attr : attributes) { + std::cout << " - " << attr.name << std::endl; +} +// Output: +// - position +// - normal + +// Trying to get location of inactive attribute returns nullopt +auto loc = crates::webgl::GLSLShaderAnalyzer::GetAttribLocation(vertexShader, "unused_texcoord"); +if (!loc.has_value()) { + std::cout << "unused_texcoord is not an active attribute" << std::endl; +} +``` + +### Rust Examples + +#### Example 1: Direct Parser Usage + +```rust +use crate::webgl::GLSLShaderAnalyzer; + +let shader_source = r#" + #version 300 es + in vec3 position; + in vec3 normal; + + out vec3 vNormal; + + void main() { + gl_Position = vec4(position, 1.0); + vNormal = normal; + } +"#; + +let mut parser = GLSLShaderAnalyzer::new(); +parser.parse(shader_source).expect("Failed to parse shader"); + +for attr in parser.get_attributes() { + println!("{} ({}) at location {}", attr.name, attr.type_name, attr.location); +} +``` + +#### Example 2: FFI Function Usage + +```rust +use crate::webgl::{parse_glsl_attributes, get_glsl_attrib_location}; + +let shader_source = "..."; + +// Get all attributes +let result = parse_glsl_attributes(shader_source)?; +for attr in &result.attributes { + println!("{}: {}", attr.name, attr.location); +} + +// Get specific attribute +let location = get_glsl_attrib_location(shader_source, "position")?; +println!("position is at {}", location); +``` + +## Supported GLSL Types + +The parser recognizes the following GLSL types: + +### Scalar Types +- `float`, `double` +- `int`, `uint` +- `bool` + +### Vector Types +- `vec2`, `vec3`, `vec4` (float vectors) +- `dvec2`, `dvec3`, `dvec4` (double vectors) +- `ivec2`, `ivec3`, `ivec4` (integer vectors) +- `uvec2`, `uvec3`, `uvec4` (unsigned integer vectors) +- `bvec2`, `bvec3`, `bvec4` (boolean vectors) + +### Matrix Types +- `mat2`, `mat3`, `mat4` (square matrices) +- `mat2x3`, `mat2x4`, `mat3x2`, `mat3x4`, `mat4x2`, `mat4x3` (non-square matrices) +- `dmat2`, `dmat3`, `dmat4` (double matrices) +- `dmat2x3`, `dmat2x4`, `dmat3x2`, `dmat3x4`, `dmat4x2`, `dmat4x3` (double non-square matrices) + +## Location Assignment Rules + +1. **Explicit Layout**: If an attribute has `layout(location = N)`, that location is used +2. **Auto-assignment**: If no explicit location, attributes are assigned locations in the order they appear in the source code, starting from 0 +3. **Mixed Mode**: You can mix explicit and auto-assigned locations; auto-assigned locations continue from the highest explicit location + 1 + +### Examples + +```glsl +// All auto-assigned +in vec3 position; // location 0 +in vec3 normal; // location 1 +in vec2 uv; // location 2 + +// All explicit +layout(location = 2) in vec3 position; // location 2 +layout(location = 0) in vec3 normal; // location 0 +layout(location = 1) in vec2 uv; // location 1 + +// Mixed (auto-assigned locations continue after highest explicit) +layout(location = 5) in vec3 position; // location 5 +in vec3 normal; // location 0 +in vec2 uv; // location 1 +``` + +## Testing + +The implementation includes comprehensive tests covering: + +- Basic attribute parsing (GLSL 300 ES with `in` qualifier) +- Explicit layout location parsing +- GLSL 100 ES compatibility (`attribute` qualifier) +- Attribute lookup by name +- FFI function correctness +- Error handling + +Run tests with: +```bash +cargo test --package jsar_jsbindings --lib webgl::tests +``` + +## Implementation Details + +The parser uses the Visitor pattern from glsl-lang to walk the GLSL AST: + +1. Parse GLSL source using `glsl_lang_pp::processor::fs::StdProcessor` +2. Implement `Visitor` trait to visit `SingleDeclaration` nodes +3. Check for attribute/in qualifiers in type qualifiers +4. Extract attribute name and type from the declaration +5. Check for explicit layout location or auto-assign +6. Store attributes in order for later retrieval + +## Related Files + +- **Rust Implementation**: `crates/jsbindings/webgl.rs` +- **C++ Wrapper**: `crates/jsbindings/bindings.webgl.hpp` +- **Generated FFI Header**: `crates/jsbindings/holocron_webgl.autogen.hpp` +- **Build Script**: `crates/jsbindings/build.rs` +- **Tests**: `crates/jsbindings/webgl.rs` (test module) + +## WebGL Specification References + +- [WebGL 1.0 Specification - getAttribLocation](https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6) +- [GLSL ES 1.00 Specification](https://www.khronos.org/files/opengles_shading_language.pdf) +- [GLSL ES 3.00 Specification](https://www.khronos.org/registry/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf) + +## Future Enhancements + +Potential improvements for future versions: + +- [ ] Cache parsed results to avoid re-parsing the same shader +- [ ] Support for more complex attribute types (structs, arrays) +- [ ] Integration with shader compilation error reporting +- [ ] Performance optimizations for large shaders +- [ ] Support for geometry shader attributes (if needed) diff --git a/src/client/graphics/webgl_active_info.hpp b/src/client/graphics/webgl_active_info.hpp index 27c063db7..4132187c1 100644 --- a/src/client/graphics/webgl_active_info.hpp +++ b/src/client/graphics/webgl_active_info.hpp @@ -20,6 +20,12 @@ namespace endor , type(info.type) { } + WebGLActiveInfo(const std::string &name, int size, int type) + : name(name) + , size(size) + , type(type) + { + } public: std::string name; diff --git a/src/client/graphics/webgl_attrib_location.hpp b/src/client/graphics/webgl_attrib_location.hpp index b67272189..a4aad6cd6 100644 --- a/src/client/graphics/webgl_attrib_location.hpp +++ b/src/client/graphics/webgl_attrib_location.hpp @@ -2,6 +2,7 @@ #include #include +#include namespace endor { @@ -29,6 +30,18 @@ namespace endor { } + friend std::ostream &operator<<(std::ostream &os, const WebGLAttribLocation &loc) + { + os << "WebGLAttribLocation(" + << "name='" << loc.name << "'"; + if (loc.index.has_value()) + os << ", index=" << loc.index.value(); + else + os << ", index=nullopt"; + os << ")"; + return os; + } + public: // The id of the program this uniform belongs to. int programId; diff --git a/src/client/graphics/webgl_context.cpp b/src/client/graphics/webgl_context.cpp index f950907c5..5210105e3 100644 --- a/src/client/graphics/webgl_context.cpp +++ b/src/client/graphics/webgl_context.cpp @@ -154,7 +154,12 @@ namespace endor if (program == nullptr || !program->isValid()) [[unlikely]] return; + // Link this program on the client-side first. + program->link(); + auto req = LinkProgramCommandBufferRequest(program->id); + req.attribLocations = program->getAttribLocations(); + if (!sendCommandBufferRequest(req, true)) throw LinkProgramException(*program, "Failed to send the command buffer."); @@ -168,80 +173,74 @@ namespace endor } /** - * Update the program's active attributes and uniforms. - */ + * Update the program's active attributes and uniforms. + */ { int index = 0; for (auto &activeInfo : resp.activeAttribs) program->setActiveAttrib(index++, activeInfo); - index = 0; - for (auto &activeInfo : resp.activeUniforms) - program->setActiveUniform(index++, activeInfo); + // index = 0; + // for (auto &activeInfo : resp.activeUniforms) + // program->setActiveUniform(index++, activeInfo); } /** - * Update the program's attribute locations. - */ - for (auto &attribLocation : resp.attribLocations) - program->setAttribLocation(attribLocation.name, attribLocation.location); - - /** - * See https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getUniformLocation#name - * - * When uniforms declared as an array, the valid name might be like the followings: - * - * - foo - * - foo[0] - * - foo[1] - */ - for (auto &uniformLocation : resp.uniformLocations) - { - auto name = uniformLocation.name; - auto location = uniformLocation.location; - auto size = uniformLocation.size; - - /** - * FIXME: The OpenGL returns "foo[0]" from `glGetActiveUniform()`, thus we need to handle it here: + * See https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getUniformLocation#name * - * 1. check if the name ends with "[0]" - * 2. grab the name without "[0]" - * 3. set the uniform location for the name without "[0]" - * 4. set the uniform location for the name with "[0]" and the index - * 5. repeat 4 for the rest of the indices + * When uniforms declared as an array, the valid name might be like the followings: * - * After the above steps, we will have the names looks like: foo, foo[0], foo[1], foo[2], ... - */ - string arraySuffix = "[0]"; - int endedAt = name.length() - arraySuffix.length(); - bool endsWithArray = name.size() > arraySuffix.size() && name.rfind(arraySuffix) != string::npos; - - /** - * Check if size is 1 and not ends with [0], WebGL developers might use 1-size array such as: `[0]`. + * - foo + * - foo[0] + * - foo[1] */ - if (size == 1 && !endsWithArray) - { - program->setUniformLocation(name, location); - } - else if (endsWithArray) - { - auto arrayName = name.substr(0, endedAt); - program->setUniformLocation(arrayName, location); - program->setUniformLocation(name, location); - for (int i = 1; i < size; i++) - program->setUniformLocation(arrayName + "[" + to_string(i) + "]", location + i); - } - else - { - // TODO: warning size is invalid? - continue; - } - } + // for (auto &uniformLocation : resp.uniformLocations) + // { + // auto name = uniformLocation.name; + // auto location = uniformLocation.location; + // auto size = uniformLocation.size; + + // /** + // * FIXME: The OpenGL returns "foo[0]" from `glGetActiveUniform()`, thus we need to handle it here: + // * + // * 1. check if the name ends with "[0]" + // * 2. grab the name without "[0]" + // * 3. set the uniform location for the name without "[0]" + // * 4. set the uniform location for the name with "[0]" and the index + // * 5. repeat 4 for the rest of the indices + // * + // * After the above steps, we will have the names looks like: foo, foo[0], foo[1], foo[2], ... + // */ + // string arraySuffix = "[0]"; + // int endedAt = name.length() - arraySuffix.length(); + // bool endsWithArray = name.size() > arraySuffix.size() && name.rfind(arraySuffix) != string::npos; + + // /** + // * Check if size is 1 and not ends with [0], WebGL developers might use 1-size array such as: `[0]`. + // */ + // if (size == 1 && !endsWithArray) + // { + // program->setUniformLocation(name, location); + // } + // else if (endsWithArray) + // { + // auto arrayName = name.substr(0, endedAt); + // program->setUniformLocation(arrayName, location); + // program->setUniformLocation(name, location); + // for (int i = 1; i < size; i++) + // program->setUniformLocation(arrayName + "[" + to_string(i) + "]", location + i); + // } + // else + // { + // // TODO: warning size is invalid? + // continue; + // } + // } if (isWebGL2_ == true) { /** - * Save the uniform block indices to the program object - */ + * Save the uniform block indices to the program object + */ for (auto &uniformBlock : resp.uniformBlocks) program->setUniformBlockIndex(uniformBlock.name, uniformBlock.index); } @@ -284,9 +283,9 @@ namespace endor int WebGLContext::getProgramParameter(shared_ptr program, int pname) { /** - * The following parameters are carried when linkProgram() is responded, thus we could return them from the client-side - * `WebGLProgram` object directly. - */ + * The following parameters are carried when linkProgram() is responded, thus we could return them from the client-side + * `WebGLProgram` object directly. + */ if (pname == WEBGL_LINK_STATUS) return static_cast(program->getLinkStatus(false)); if (pname == WEBGL_VALIDATE_STATUS) @@ -297,8 +296,8 @@ namespace endor return static_cast(program->countActiveUniforms()); /** - * Send a command buffer request and wait for the response if not hit the above conditions. - */ + * Send a command buffer request and wait for the response if not hit the above conditions. + */ auto req = GetProgramParamCommandBufferRequest(program->id, pname); sendCommandBufferRequest(req, true); @@ -365,32 +364,28 @@ namespace endor void WebGLContext::attachShader(shared_ptr program, shared_ptr shader) { + assert(program != nullptr && "Program is not null"); + assert(shader != nullptr && "Shader is not null"); + + program->attachShader(shader); auto req = AttachShaderCommandBufferRequest(program->id, shader->id); sendCommandBufferRequest(req); } void WebGLContext::detachShader(shared_ptr program, shared_ptr shader) { + assert(program != nullptr && "Program is not null"); + assert(shader != nullptr && "Shader is not null"); + + program->detachShader(shader); auto req = DetachShaderCommandBufferRequest(program->id, shader->id); sendCommandBufferRequest(req); } string WebGLContext::getShaderSource(shared_ptr shader) { - auto req = GetShaderSourceCommandBufferRequest(shader->id); - sendCommandBufferRequest(req, true); - - auto resp = recvResponse(COMMAND_BUFFER_GET_SHADER_SOURCE_RES, req); - if (resp != nullptr) [[likely]] - { - string source(resp->source); - delete resp; - return source; - } - else - { - throw runtime_error("Failed to get shader source: timeout."); - } + assert(shader != nullptr && "Shader is not null"); + return shader->source; } int WebGLContext::getShaderParameter(shared_ptr shader, int pname) @@ -946,7 +941,7 @@ namespace endor optional WebGLContext::getActiveAttrib(shared_ptr program, unsigned int index) { assert(program != nullptr && "Program is not null"); - program->waitForCompleted(); + program->waitForCompleted("getActiveAttrib"); if (program->hasActiveAttrib(index)) return program->getActiveAttrib(index); @@ -957,19 +952,20 @@ namespace endor optional WebGLContext::getActiveUniform(shared_ptr program, unsigned int index) { assert(program != nullptr && "Program is not null"); - program->waitForCompleted(); + program->waitForCompleted("getActiveUniform"); if (program->hasActiveUniform(index)) return program->getActiveUniform(index); else + { + cerr << "Warning: getActiveUniform: no active uniform at index " << index << " in program " + << program->id << endl; return nullopt; + } } optional WebGLContext::getAttribLocation(shared_ptr program, const string &name) { - assert(program != nullptr && "Program is not null"); - program->waitForCompleted(); - // Returns `nullopt` if the program is incomplete or not linked. if (!program->hasAttribLocation(name)) return nullopt; @@ -981,17 +977,22 @@ namespace endor optional WebGLContext::getUniformLocation(shared_ptr program, const string &name) { - if (program->isIncomplete()) - { - return WebGLUniformLocation(program->id, name); - } + // if (program->isIncomplete()) + // { + // return WebGLUniformLocation(program->id, name); + // } + // else + // { + // if (program->hasUniformLocation(name)) + // return program->getUniformLocation(name); + // else + // return nullopt; + // } + + if (program->hasUniformLocation(name)) + return program->getUniformLocation(name); else - { - if (program->hasUniformLocation(name)) - return program->getUniformLocation(name); - else - return nullopt; - } + return nullopt; } void WebGLContext::uniform1f(WebGLUniformLocation location, float v0) @@ -1148,10 +1149,10 @@ namespace endor void WebGLContext::uniformMatrix3fv(WebGLUniformLocation location, bool transpose, glm::mat3 m) { // clang-format off - vector values = { - m[0][0], m[0][1], m[0][2], - m[1][0], m[1][1], m[1][2], - m[2][0], m[2][1], m[2][2]}; + vector values = { + m[0][0], m[0][1], m[0][2], + m[1][0], m[1][1], m[1][2], + m[2][0], m[2][1], m[2][2]}; // clang-format on uniformMatrix3fv(location, transpose, values); } @@ -1175,11 +1176,11 @@ namespace endor void WebGLContext::uniformMatrix4fv(WebGLUniformLocation location, bool transpose, glm::mat4 m) { // clang-format off - vector values = { - m[0][0], m[0][1], m[0][2], m[0][3], - m[1][0], m[1][1], m[1][2], m[1][3], - m[2][0], m[2][1], m[2][2], m[2][3], - m[3][0], m[3][1], m[3][2], m[3][3]}; + vector values = { + m[0][0], m[0][1], m[0][2], m[0][3], + m[1][0], m[1][1], m[1][2], m[1][3], + m[2][0], m[2][1], m[2][2], m[2][3], + m[3][0], m[3][1], m[3][2], m[3][3]}; // clang-format on uniformMatrix4fv(location, transpose, values); } @@ -2198,7 +2199,7 @@ namespace endor int WebGL2Context::getUniformBlockIndex(shared_ptr program, const string &uniformBlockName) { assert(program != nullptr && "Program must not be null."); - program->waitForCompleted(); + program->waitForCompleted("getUniformBlockIndex"); if (program == nullptr || !program->isValid() || !program->hasUniformBlockIndex(uniformBlockName)) return -1; diff --git a/src/client/graphics/webgl_context.hpp b/src/client/graphics/webgl_context.hpp index 8573532a6..6489333b5 100644 --- a/src/client/graphics/webgl_context.hpp +++ b/src/client/graphics/webgl_context.hpp @@ -269,9 +269,9 @@ namespace endor }; /** - * The `WebGLContext` class implements the WebGLRenderingContext interface in C/C++ at the client-side, this is used to - * implement the WebGL API, and support native C/C++ renderer. - */ + * The `WebGLContext` class implements the WebGLRenderingContext interface in C/C++ at the client-side, this is used to + * implement the WebGL API, and support native C/C++ renderer. + */ class WebGLContext : public scripting_base::JSObjectHolder { friend class WebGLProgramScope; @@ -283,67 +283,16 @@ namespace endor ~WebGLContext(); public: // graphics methods - /** - * It creates and initializes a WebGLProgram object. - * - * @returns The created WebGLProgram object. - */ std::shared_ptr createProgram(); - /** - * It deletes a given WebGLProgram object, and this method has no effect if the program has already been deleted. - * - * @param program The WebGLProgram object to delete. - */ void deleteProgram(std::shared_ptr program); - /** - * It links a given `WebGLProgram`, completing the process of preparing the GPU code for the program's fragment and - * vertex shaders. - * - * @param program The WebGLProgram object to link. - */ void linkProgram(std::shared_ptr program); - /** - * It validates a program. It checks if a program can execute given the current WebGL state. The information - * generated by the validation process will be stored in the program's information log. The validation - * information may consist of an empty string, or it may be a string containing information about how the - * current program might execute given the current WebGL state. This information could be useful during - * program development. - * - * @param program The WebGLProgram object to validate. - */ void validateProgram(std::shared_ptr program); - /** - * It sets the specified WebGLProgram as part of the current rendering state. - * - * @param program The WebGLProgram object to use. - */ void useProgram(std::shared_ptr program); - /** - * It binds a generic vertex index to an attribute variable. - * - * @param program The WebGLProgram object to bind the attribute to. - * @param index The index of the generic vertex to bind. - * @param name A string specifying the name of the variable to bind to the generic vertex index. This name cannot start - * with "webgl_" or "_webgl_", as these are reserved for use by WebGL. - */ + void bindAttribLocation(std::shared_ptr program, uint32_t index, const std::string &name); - /** - * It returns information about the given program. - * - * @param program The WebGLProgram object to get information from. - * @param pname A GLenum specifying the information to query. - * @returns the requested program information (as specified with pname). - */ int getProgramParameter(std::shared_ptr program, int pname); - /** - * It returns the information log for the specified `WebGLProgram` object. It contains errors that occurred during failed - * linking or validation of `WebGLProgram` objects. - * - * @param program The WebGLProgram object to get the information log from. - * @returns A string that contains diagnostic messages, warning messages, and other information about the last linking or - * validation operation. - */ std::string getProgramInfoLog(std::shared_ptr program); + std::shared_ptr createShader(WebGLShaderType type); void deleteShader(std::shared_ptr shader); void shaderSource(std::shared_ptr shader, const std::string &source); @@ -353,12 +302,14 @@ namespace endor std::string getShaderSource(std::shared_ptr shader); int getShaderParameter(std::shared_ptr shader, int pname); std::string getShaderInfoLog(std::shared_ptr shader); + std::shared_ptr createBuffer(); void deleteBuffer(std::shared_ptr buffer); void bindBuffer(WebGLBufferBindingTarget target, std::shared_ptr buffer); void bufferData(WebGLBufferBindingTarget target, size_t size, WebGLBufferUsage usage); void bufferData(WebGLBufferBindingTarget target, size_t srcSize, void *srcData, WebGLBufferUsage usage); void bufferSubData(WebGLBufferBindingTarget target, int offset, size_t size, void *data); + std::shared_ptr createFramebuffer(); void deleteFramebuffer(std::shared_ptr framebuffer); void bindFramebuffer(WebGLFramebufferBindingTarget target, std::shared_ptr framebuffer); @@ -374,10 +325,12 @@ namespace endor std::shared_ptr texture, int level); uint32_t checkFramebufferStatus(WebGLFramebufferBindingTarget target); + std::shared_ptr createRenderbuffer(); void deleteRenderbuffer(std::shared_ptr renderbuffer); void bindRenderbuffer(WebGLRenderbufferBindingTarget target, std::shared_ptr renderbuffer); void renderbufferStorage(WebGLRenderbufferBindingTarget target, int internalformat, int width, int height); + std::shared_ptr createTexture(); void deleteTexture(std::shared_ptr texture); void bindTexture(WebGLTextureTarget target, std::shared_ptr texture); @@ -425,6 +378,7 @@ namespace endor void texParameteriv(WebGLTextureTarget target, WebGLTextureParameterName pname, const std::vector params); void activeTexture(WebGLTextureUnit texture); void generateMipmap(WebGLTextureTarget target); + void enableVertexAttribArray(const WebGLAttribLocation &); void enableVertexAttribArray(int index); void disableVertexAttribArray(const WebGLAttribLocation &); @@ -468,6 +422,7 @@ namespace endor void uniformMatrix4fv(WebGLUniformLocation location, bool transpose, glm::mat4 m); void uniformMatrix4fv(WebGLUniformLocation location, bool transpose, std::vector values); void uniformMatrix4fv(WebGLUniformLocation location, bool transpose, MatrixComputationGraph &graphToValues); + void drawArrays(WebGLDrawMode mode, int first, int count); void drawElements(WebGLDrawMode mode, int count, int type, int offset); void hint(WebGLHintTargetBehavior target, WebGLHintBehaviorMode mode); @@ -499,47 +454,13 @@ namespace endor void frontFace(int mode); void enable(int cap); void disable(int cap); - /** - * @param pname The parameter name that returns a boolean value. - * @returns The boolean value for the parameter name. - */ bool getParameter(WebGLBooleanParameterName pname); - /** - * @param pname The parameter name that returns a float value. - * @returns The float value for the parameter name. - */ float getParameter(WebGLFloatParameterName pname); - /** - * @param pname The parameter name that returns a float array value. - * @returns The float array value for the parameter name. - */ std::vector getParameter(WebGLFloatArrayParameterName pname); - /** - * @param pname The parameter name that returns an integer value. - * @returns The integer value for the parameter name. - */ int getParameter(WebGLIntegerParameterName pname); - /** - * @param pname The parameter name that returns a 64-bit integer value. - * @returns The 64-bit integer value for the parameter name. - */ int64_t getParameter(WebGLInteger64ParameterName pname); - /** - * @param pname The parameter name that returns a boolean value. - * @param index The index of the parameter. - * @returns The boolean value for the parameter name. - */ bool getParameter(WebGLBooleanIndexedParameterName pname, int index); - /** - * @param pname The parameter name that returns a float value. - * @param index The index of the parameter. - * @returns The float value for the parameter name. - */ float getParameter(WebGLFloatArrayParameterName pname, int index); - /** - * @param pname The parameter name that returns a string. - * @returns The string value for the parameter name, such as `renderer`, `vendor` or others. - */ std::string getParameter(WebGLStringParameterName pname); WebGLShaderPrecisionFormat getShaderPrecisionFormat(int shadertype, int precisiontype); int getError(); @@ -548,53 +469,34 @@ namespace endor bool makeXRCompatible(); public: - /** - * @returns the width of the current drawing buffer, commonly the bound framebuffer. - */ inline int drawingBufferWidth() { return viewport_.width(); } - - /** - * @returns the height of the current drawing buffer, commonly the bound framebuffer. - */ inline int drawingBufferHeight() { return viewport_.height(); } - - /** - * @returns if this context is a WebGL2 context. - */ inline bool isWebGL2() { return isWebGL2_; } - - /** - * @returns if the context is lost. - */ inline bool isContextLost() { return isContextLost_; } - - /** - * @returns if the context could be for WebXR rendering. - */ inline bool isXRCompatible() { return contextAttributes.xrCompatible; } /** - * It sets the WebGL error for the function. - * - * @param func the function name that causes the error. - * @param error the WebGL error. - * @param message the error message for debugging. - */ + * It sets the WebGL error for the function. + * + * @param func the function name that causes the error. + * @param error the WebGL error. + * @param message the error message for debugging. + */ inline void setError(std::string func, WebGLError error, std::string message) { lastError_ = error; @@ -605,13 +507,13 @@ namespace endor protected: /** - * It sends a command buffer request directly to the client context, it only updates the command buffer's context id before - * sending. - * - * @param commandBuffer - * @param followsFlush - if true, the command buffer will be executed in the default queue. - * @returns if the command buffer request is sent successfully. - */ + * It sends a command buffer request directly to the client context, it only updates the command buffer's context id before + * sending. + * + * @param commandBuffer + * @param followsFlush - if true, the command buffer will be executed in the default queue. + * @returns if the command buffer request is sent successfully. + */ inline bool sendCommandBufferRequestDirectly(commandbuffers::TrCommandBufferBase &commandBuffer, bool followsFlush = false) { commandBuffer.contextId = id; @@ -619,23 +521,23 @@ namespace endor } /** - * It sends a command buffer request to the server. - * - * @param commandBuffer - * @param followsFlush - if true, the command buffer will be executed in the default queue. - * @returns if the command buffer request is sent successfully. - */ + * It sends a command buffer request to the server. + * + * @param commandBuffer + * @param followsFlush - if true, the command buffer will be executed in the default queue. + * @returns if the command buffer request is sent successfully. + */ bool sendCommandBufferRequest(commandbuffers::TrCommandBufferBase &commandBuffer, bool followsFlush = false); /** - * Receives a command buffer response from the server, and returns the response as a pointer to the specified type. - * - * @param responseType The type of the response command buffer, that is used to check the response type. - * @param req The request command buffer that is used to match the response. - * @param timeout The timeout in milliseconds to wait for the response, default is 1000ms. - * @returns A pointer to the response command buffer of the specified type, or nullptr if the response is not - * received within the timeout or if the response type does not match. - */ + * Receives a command buffer response from the server, and returns the response as a pointer to the specified type. + * + * @param responseType The type of the response command buffer, that is used to check the response type. + * @param req The request command buffer that is used to match the response. + * @param timeout The timeout in milliseconds to wait for the response, default is 1000ms. + * @returns A pointer to the response command buffer of the specified type, or nullptr if the response is not + * received within the timeout or if the response type does not match. + */ template R *recvResponse(commandbuffers::CommandBufferType responseType, const commandbuffers::TrCommandBufferBase &req, @@ -656,12 +558,12 @@ namespace endor } /** - * It receives a command buffer response **asynchronously**, and calls the callback function with the response. - * - * @param responseType The type of the response command buffer, that is used to check the response type. - * @param req The request command buffer that is used to match the response. - * @param callback The callback function that is called with the response command buffer of the specified type. - */ + * It receives a command buffer response **asynchronously**, and calls the callback function with the response. + * + * @param responseType The type of the response command buffer, that is used to check the response type. + * @param req The request command buffer that is used to match the response. + * @param callback The callback function that is called with the response command buffer of the specified type. + */ template void recvResponseAsync(commandbuffers::CommandBufferType responseType, const commandbuffers::TrCommandBufferBase &req, @@ -685,16 +587,16 @@ namespace endor void sendFirstContentfulPaintMetrics(); /** - * It unpacks the pixels from the source buffer to the destination buffer. - * - * @param type The pixel type. - * @param format The pixel format. - * @param width The width of the image. - * @param height The height of the image. - * @param srcPixels The source pixels buffer. - * @param dstPixels The destination pixels buffer, if it is null, a new buffer will be created. - * @returns The destination pixels buffer. - */ + * It unpacks the pixels from the source buffer to the destination buffer. + * + * @param type The pixel type. + * @param format The pixel format. + * @param width The width of the image. + * @param height The height of the image. + * @param srcPixels The source pixels buffer. + * @param dstPixels The destination pixels buffer, if it is null, a new buffer will be created. + * @returns The destination pixels buffer. + */ unsigned char *unpackPixels( WebGLPixelType type, WebGLTextureFormat format, @@ -802,28 +704,28 @@ namespace endor private: /** - * @returns the client state of the WebGL context. - */ + * @returns the client state of the WebGL context. + */ WebGLState &clientState() { return clientState_; } /** - * an XR-compatible WebGL context could be configured as an `XRWebGLLayer` object and be connected to a specific WebXR - * session. At the same time, each WebXR session could own 1 base layer, thus the XR-compatible WebGL context to a WebXR - * session is a one-to-one relationship. - * - * @returns the current connected WebXR session, or `nullptr` if it is not connected. - */ + * an XR-compatible WebGL context could be configured as an `XRWebGLLayer` object and be connected to a specific WebXR + * session. At the same time, each WebXR session could own 1 base layer, thus the XR-compatible WebGL context to a WebXR + * session is a one-to-one relationship. + * + * @returns the current connected WebXR session, or `nullptr` if it is not connected. + */ inline std::shared_ptr connectedXRSession() { return isXRCompatible() ? connectedXRSession_.lock() : nullptr; } /** - * It connects the current WebGL context to a WebXR session. - * - * @param session The WebXR session to connect. - */ + * It connects the current WebGL context to a WebXR session. + * + * @param session The WebXR session to connect. + */ inline void connectXRSession(std::shared_ptr session) { connectedXRSession_ = session; @@ -846,8 +748,8 @@ namespace endor std::string version; std::string renderer; /** - * The default handedness of the coordinate system to use. - */ + * The default handedness of the coordinate system to use. + */ commandbuffers::MatrixHandedness defaultCoordHandedness = commandbuffers::MatrixHandedness::MATRIX_RIGHT_HANDED; protected: @@ -867,8 +769,8 @@ namespace endor int clearStencil_ = 0; glm::vec4 blendColor_ = {0.0f, 0.0f, 0.0f, 1.0f}; /** - * TODO: Read the value from the host - */ + * TODO: Read the value from the host + */ uint32_t unpackAlignment_ = 4; private: @@ -880,11 +782,11 @@ namespace endor { public: /** - * It creates a new `WebGL2Context` object. - * - * @param attrs The context attributes. - * @returns The created `WebGL2Context` object. - */ + * It creates a new `WebGL2Context` object. + * + * @param attrs The context attributes. + * @returns The created `WebGL2Context` object. + */ static inline std::shared_ptr Make(ContextAttributes &attrs) { return std::make_shared(attrs); @@ -924,16 +826,6 @@ namespace endor WebGLBufferUsage usage, std::optional srcOffset = 0, std::optional length = 0); - /** - * It updates a subset of a buffer object's data store. - * - * @param target The binding point (target). - * @param dstByteOffset An offset in bytes where the data replacement will start. - * @param srcSize The size of the source data. - * @param srcData The source data to copy from. - * @param srcOffset The element index offset where to start reading the buffer. - * @param length A `uint` defaulting to 0, where 0 means bufferSubData should calculate the length. - */ void bufferSubData( WebGLBufferBindingTarget target, int dstByteOffset, @@ -994,29 +886,13 @@ namespace endor void drawElementsInstanced(WebGLDrawMode mode, int count, int type, int offset, int instanceCount); void drawRangeElements(WebGLDrawMode mode, int start, int end, int count, int type, int offset); void endQuery(WebGLQueryTarget target); - /** - * It attaches a single layer of a texture to a framebuffer. - */ void framebufferTextureLayer( WebGLFramebufferBindingTarget target, WebGLFramebufferAttachment attachment, std::shared_ptr texture, int level, int layer); - /** - * @returns A string indicating the active uniform block name. - */ std::string getActiveUniformBlockName(std::shared_ptr program, int uniformBlockIndex); - /** - * It reads data from a buffer binding point and writes them to the destination buffer. - * - * @param target The binding point (target). - * @param srcByteOffset The byte offset from which to start reading from the buffer. - * @param dstSize The size of the destination buffer. - * @param dstData A data storage to copy the data to, if it is null, will throw an exception. - * @param dstOffset The element index offset to start writing in `dstData`. - * @param length the number of elements to copy. If this is 0 or nullopt, it will copy util the end of `dstData`. - */ void getBufferSubData( WebGLBufferBindingTarget target, int srcByteOffset, @@ -1027,31 +903,8 @@ namespace endor int getFragDataLocation(std::shared_ptr program, const std::string &name); int getParameterV2(WebGL2IntegerParameterName pname); std::shared_ptr getQuery(WebGLQueryTarget target, int pname); - /** - * It retrieves the index of a uniform block within a WebGLProgram. - * - * @param program The program to query. - * @param uniformBlockName The name of the uniform block. - * @returns The index of the uniform block. - */ int getUniformBlockIndex(std::shared_ptr program, const std::string &uniformBlockName); - /** - * It invalidates the contents of attachments in a framebuffer. - * - * @param target The target to which the framebuffer is attached. - * @param attachments The list of attachments to invalidate. - */ void invalidateFramebuffer(WebGLFramebufferBindingTarget target, const std::vector attachments); - /** - * It invalidates portions of the contents of attachments in a framebuffer. - * - * @param target The target to which the framebuffer is attached. - * @param attachments The list of attachments to invalidate. - * @param x The x offset of the region to invalidate. - * @param y The y offset of the region to invalidate. - * @param width The width of the region to invalidate. - * @param height The height of the region to invalidate. - */ void invalidateSubFramebuffer( WebGLFramebufferBindingTarget target, const std::vector attachments, @@ -1059,55 +912,18 @@ namespace endor int y, size_t width, size_t height); - /** - * @returns `true` if the passed object is a valid `WebGLQuery` object. - */ + bool isQuery(std::shared_ptr query); - /** - * @returns `true` if the passed object is a valid `WebGLSampler` object. - */ bool isSampler(std::shared_ptr sampler); - /** - * @returns `true` if the passed object is a valid `WebGLVertexArray` object. - */ bool isVertexArray(std::shared_ptr vertexArray); - /** - * It selects a color buffer as the source for pixels for subsequent calls to `copyTexImage2D`, `copyTexSubImage2D`, - * `copyTexSubImage3D` or `readPixels`. - * - * @param src The color buffer to select. - */ void readBuffer(int src); - /** - * It returns creates and initializes a renderbuffer object's data store and allows specifying a number of samples to - * be used. - * - * @param target The target to which the renderbuffer is attached. - * @param samples The number of samples to be used. - * @param internalformat The internal format of the renderbuffer. - * @param width The width of the renderbuffer. - * @param height The height of the renderbuffer. - */ void renderbufferStorageMultisample( WebGLRenderbufferBindingTarget target, int samples, int internalformat, int width, int height); - /** - * It specifies a three-dimensional texture image. - * - * @param target The target to which the texture is bound. - * @param level The level of detail. Level 0 is the base image level. - * @param internalformat The color components in the texture. - * @param width The width of the texture image. - * @param height The height of the texture image. - * @param depth The depth of the texture image. - * @param border The width of the border. Must be 0. - * @param format The format of the pixel data. - * @param type The data type of the pixel data. - * @param pixels The pixel data. - */ + void texImage3D( WebGLTexture3DTarget target, int level, @@ -1119,31 +935,12 @@ namespace endor WebGLTextureFormat format, WebGLPixelType type, unsigned char *pixels); - /** - * It specifies all levels of two-dimensional texture storage. - * - * @param target The target to which the texture is bound. - * @param levels The number of texture levels. - * @param internalformat The color components in the texture. - * @param width The width of the texture image. - * @param height The height of the texture image. - */ void texStorage2D( WebGLTexture2DTarget target, int levels, int internalformat, size_t width, size_t height); - /** - * It specifies all levels of three-dimensional texture storage. - * - * @param target The target to which the texture is bound. - * @param levels The number of texture levels. - * @param internalformat The color components in the texture. - * @param width The width of the texture image. - * @param height The height of the texture image. - * @param depth The depth of the texture image. - */ void texStorage3D( WebGLTexture3DTarget target, int levels, @@ -1151,21 +948,6 @@ namespace endor size_t width, size_t height, size_t depth); - /** - * It specifies a sub-rectangle of a three-dimensional texture image. - * - * @param target The target to which the texture is bound. - * @param level The level of detail. Level 0 is the base image level. - * @param xoffset The x offset of the sub-rectangle. - * @param yoffset The y offset of the sub-rectangle. - * @param zoffset The z offset of the sub-rectangle. - * @param width The width of the sub-rectangle. - * @param height The height of the sub-rectangle. - * @param depth The depth of the sub-rectangle. - * @param format The format of the pixel data. - * @param type The data type of the pixel data. - * @param pixels The pixel data. - */ void texSubImage3D( WebGLTexture3DTarget target, int level, @@ -1178,91 +960,24 @@ namespace endor WebGLTextureFormat format, WebGLPixelType type, unsigned char *pixels); - /** - * It assigns binding points for active uniform blocks. - */ + void uniformBlockBinding(std::shared_ptr program, int uniformBlockIndex, uint32_t uniformBlockBinding); - /** - * It specifies 3x2 matrix values for uniform variables. - */ void uniformMatrix3x2fv(WebGLUniformLocation location, bool transpose, std::vector values); - /** - * It specifies 4x2 matrix values for uniform variables. - */ void uniformMatrix4x2fv(WebGLUniformLocation location, bool transpose, std::vector values); - /** - * It specifies 2x3 matrix values for uniform variables. - */ void uniformMatrix2x3fv(WebGLUniformLocation location, bool transpose, std::vector values); - /** - * It specifies 4x3 matrix values for uniform variables. - */ void uniformMatrix4x3fv(WebGLUniformLocation location, bool transpose, std::vector values); - /** - * It specifies 2x4 matrix values for uniform variables. - */ void uniformMatrix2x4fv(WebGLUniformLocation location, bool transpose, std::vector values); - /** - * It specifies 3x4 matrix values for uniform variables. - */ void uniformMatrix3x4fv(WebGLUniformLocation location, bool transpose, std::vector values); - /** - * It modifies the rate at which generic vertex attributes advance when rendering multiple instances of primitives with - * `gl.drawArraysInstanced()` and `gl.drawElementsInstanced()`. - * - * @param index The index of the vertex attribute. - * @param divisor The number of instances that will pass between updates of the generic attribute. - */ void vertexAttribDivisor(const WebGLAttribLocation &, uint32_t divisor); void vertexAttribDivisor(int index, uint32_t divisor); - /** - * It specify integer values for generic vertex attributes. - * - * @param index The index of the vertex attribute. - * @param v0 The first value to set. - * @param v1 The second value to set. - * @param v2 The third value to set. - * @param v3 The fourth value to set. - */ void vertexAttribI4i(const WebGLAttribLocation &, int v0, int v1, int v2, int v3); void vertexAttribI4i(int index, int v0, int v1, int v2, int v3); - /** - * It specify unsigned integer values for generic vertex attributes. - * - * @param index The index of the vertex attribute. - * @param v0 The first value to set. - * @param v1 The second value to set. - * @param v2 The third value to set. - * @param v3 The fourth value to set. - */ void vertexAttribI4ui(const WebGLAttribLocation &, uint v0, uint v1, uint v2, uint v3); void vertexAttribI4ui(int index, uint v0, uint v1, uint v2, uint v3); - /** - * It specify integer values for generic vertex attributes from a vector. - * - * @param index The index of the vertex attribute. - * @param values The values to set. - */ void vertexAttribI4iv(const WebGLAttribLocation &, const std::vector values); void vertexAttribI4iv(int index, const std::vector values); - /** - * It specify unsigned integer values for generic vertex attributes from a vector. - * - * @param index The index of the vertex attribute. - * @param values The values to set. - */ void vertexAttribI4uiv(const WebGLAttribLocation &, const std::vector values); void vertexAttribI4uiv(int index, const std::vector values); - /** - * It specifies integer data formats and locations of vertex attributes in a vertex attributes array. - * - * @param index The index of the vertex attribute that is to be modified. - * @param size The number of components per vertex attribute. Must be 1, 2, 3, or 4. - * @param type The data type of each component in the array. Must be one of: `gl.BYTE`, `gl.UNSIGNED_BYTE`, - * `gl.SHORT`, `gl.UNSIGNED_SHORT`, `gl.INT`, or `gl.UNSIGNED_INT`. - * @param stride The offset in bytes between the beginning of consecutive vertex attributes. - * @param offset An offset in bytes of the first component in the vertex attribute array. Must be a multiple of type. - */ void vertexAttribIPointer(const WebGLAttribLocation &, int size, int type, diff --git a/src/client/graphics/webgl_program.cpp b/src/client/graphics/webgl_program.cpp index a44046565..72351a892 100644 --- a/src/client/graphics/webgl_program.cpp +++ b/src/client/graphics/webgl_program.cpp @@ -1,3 +1,4 @@ +#include #include "./webgl_program.hpp" namespace endor @@ -6,11 +7,157 @@ namespace endor { using namespace std; - void WebGLProgram::waitForCompleted(int timeout) const + void WebGLProgram::attachShader(std::shared_ptr shader) { + if (shader->type == WebGLShaderType::kVertex) + vertexShader_ = shader; + else if (shader->type == WebGLShaderType::kFragment) + fragmentShader_ = shader; + else + assert(false && "Shader type should be either vertex or fragment."); + } + + void WebGLProgram::detachShader(std::shared_ptr shader) + { + if (shader->type == WebGLShaderType::kVertex) + vertexShader_ = nullptr; + else if (shader->type == WebGLShaderType::kFragment) + fragmentShader_ = nullptr; + else + assert(false && "Shader type should be either vertex or fragment."); + } + + void WebGLProgram::link() + { + if (!vertexShader_ || !fragmentShader_) + throw runtime_error("Cannot link program without both vertex and fragment shaders."); + + vector attribsInfo; + vector uniformsInfo; + + cerr << "Vertex Shader Source:\n" + << vertexShader_->source << endl; + if (!crates::webgl::GLSLShaderAnalyzer::Parse(vertexShader_->source, attribsInfo, uniformsInfo, true)) + { + throw runtime_error("Failed to parse vertex shader source for attribute info."); + } + + { + vector attribsInfo; + vector fragment_uniformsInfo; + + cerr << "Fragment Shader Source:\n" + << fragmentShader_->source << endl; + if (!crates::webgl::GLSLShaderAnalyzer::Parse(fragmentShader_->source, attribsInfo, fragment_uniformsInfo, true)) + { + throw runtime_error("Failed to parse fragment shader source for uniform info."); + } + + for (const auto &uniform : fragment_uniformsInfo) + { + // Avoid duplicates + auto it = std::find_if(uniformsInfo.begin(), uniformsInfo.end(), [&uniform](const crates::webgl::GLSLUniform &u) + { return u.name == uniform.name; }); + if (it == uniformsInfo.end()) + { + uniformsInfo.push_back(uniform); + } + } + } + + int loc = 0; + int activeIndex = 0; + + for (const auto &attrib : attribsInfo) + { + attribLocations_[attrib.name] = WebGLAttribLocation(id, + attrib.active ? loc : -1, + attrib.name); + if (attrib.active) + { + activeAttribs_[activeIndex] = WebGLActiveInfo(attrib.name, attrib.type, attrib.size); + activeIndex += 1; + } + + // mat4 takes up 4 attribute locations + int slots = 1; + switch (attrib.type) + { + case WEBGL_FLOAT_MAT2: + case WEBGL2_FLOAT_MAT2x3: + case WEBGL2_FLOAT_MAT2x4: + slots = 2; + break; + case WEBGL_FLOAT_MAT3: + case WEBGL2_FLOAT_MAT3x2: + case WEBGL2_FLOAT_MAT3x4: + slots = 3; + break; + case WEBGL_FLOAT_MAT4: + case WEBGL2_FLOAT_MAT4x2: + case WEBGL2_FLOAT_MAT4x3: + slots = 4; + break; + default: + break; + } + + if (slots > 1) + { + for (int i = 1; i < slots; ++i) + { + string name = attrib.name + "[" + to_string(i) + "]"; + attribLocations_[name] = WebGLAttribLocation(id, loc + i, name); + } + } + + // Advance location + loc += slots; + } + + loc = 0; + activeIndex = 0; + + for (const auto &uniform : uniformsInfo) + { + uniformLocations_[uniform.name] = WebGLUniformLocation(id, + uniform.active ? loc : -1, + uniform.name); + if (uniform.active || true) + { + string name = uniform.name; + if (uniform.size > 1) + name += "[0]"; + cerr << "Uniform[" << activeIndex << "]: (" << name << "), type: " << uniform.type << ", size: " << uniform.size << ", active: " << (uniform.active ? "true" : "false") << endl; + + activeUniforms_[activeIndex] = WebGLActiveInfo(uniform.name, uniform.size, uniform.type); + activeIndex += 1; + } + + // For array uniforms, add locations for each element + if (uniform.size > 1) + { + for (int i = 1; i < uniform.size; ++i) + { + string name = uniform.name + "[" + to_string(i) + "]"; + uniformLocations_[name] = WebGLUniformLocation(id, loc + i, name); + } + } + + // Advance location + loc += 1; + } + } + + void WebGLProgram::waitForCompleted(const string &label, int timeout) const + { + chrono::steady_clock::time_point startTime = chrono::steady_clock::now(); std::unique_lock lock(setCompletedMutex_); setCompletedCv_.wait_for(lock, std::chrono::milliseconds(timeout), [this]() { return !incomplete_; }); + + auto duration = chrono::duration_cast(chrono::steady_clock::now() - startTime).count(); + cerr << "Waited for program " << id << " (" << label << ") completion for " << duration << " ms." << endl; } void WebGLProgram::setCompleted(bool linkStatus) @@ -23,20 +170,103 @@ namespace endor } } + void WebGLProgram::setLinkStatus(bool linkStatus) + { + linkStatus_ = linkStatus; + } + + void WebGLProgram::setValidateStatus(bool validateStatus) + { + validateStatus_ = validateStatus; + } + bool WebGLProgram::getLinkStatus(bool sync) const { if (sync) - waitForCompleted(); + waitForCompleted("getLinkStatus"); return linkStatus_; } bool WebGLProgram::getValidateStatus(bool sync) const { if (sync) - waitForCompleted(); + waitForCompleted("getValidateStatus"); return validateStatus_; } + size_t WebGLProgram::countActiveAttribs() const + { + waitForCompleted("countActiveAttribs"); + return activeAttribs_.size(); + } + + WebGLActiveInfo WebGLProgram::getActiveAttrib(int index) + { + return activeAttribs_[index]; + } + + void WebGLProgram::setActiveAttrib(int index, const commandbuffers::ActiveInfo &activeInfo) + { + activeAttribs_[index] = WebGLActiveInfo(activeInfo); + } + + bool WebGLProgram::hasActiveAttrib(int index) + { + return activeAttribs_.find(index) != activeAttribs_.end(); + } + + size_t WebGLProgram::countActiveUniforms() const + { + waitForCompleted("countActiveUniforms"); + return activeUniforms_.size(); + } + + WebGLActiveInfo WebGLProgram::getActiveUniform(int index) + { + return activeUniforms_[index]; + } + + void WebGLProgram::setActiveUniform(int index, const commandbuffers::ActiveInfo &activeInfo) + { + activeUniforms_[index] = activeInfo; + } + + bool WebGLProgram::hasActiveUniform(int index) + { + return activeUniforms_.find(index) != activeUniforms_.end(); + } + + void WebGLProgram::setAttribLocation(const std::string &name, int location) + { + attribLocations_[name] = WebGLAttribLocation(id, location, name); + } + + void WebGLProgram::resetAttribLocations() + { + attribLocations_.clear(); + } + + bool WebGLProgram::hasAttribLocation(const std::string &name) + { + return attribLocations_.find(name) != attribLocations_.end(); + } + + const WebGLAttribLocation &WebGLProgram::getAttribLocation(const std::string &name) + { + return attribLocations_[name]; + } + + vector WebGLProgram::getAttribLocations() const + { + vector list; + for (const auto &pair : attribLocations_) + { + auto loc = pair.second; + list.push_back(commandbuffers::AttribLocation(loc.name, loc.index.value_or(-1))); + } + return list; + } + void WebGLProgram::printInfo() const { cout << "Program " << id << " info:" << endl; diff --git a/src/client/graphics/webgl_program.hpp b/src/client/graphics/webgl_program.hpp index c62873af7..7610c60c2 100644 --- a/src/client/graphics/webgl_program.hpp +++ b/src/client/graphics/webgl_program.hpp @@ -9,6 +9,7 @@ #include #include "./webgl_object.hpp" +#include "./webgl_shader.hpp" #include "./webgl_active_info.hpp" #include "./webgl_attrib_location.hpp" #include "./webgl_uniform_location.hpp" @@ -26,187 +27,59 @@ namespace endor } public: + void attachShader(std::shared_ptr shader); + void detachShader(std::shared_ptr shader); + void link(); + // Returns if this program is incomplete, it means the program response has not been received from channel peer. bool isIncomplete() const { return incomplete_; } // Waits for the program to be completed, it will block until the program response is received from channel peer. - void waitForCompleted(int timeout = 2000) const; + void waitForCompleted(const std::string& label = "", int timeout = 2000) const; // Calls setCompleted() when the program response is received from channel peer and updates the link status. void setCompleted(bool linkStatus); - /** - * It sets the link status of the program. - * - * @param linkStatus The link status of the program. - */ - void setLinkStatus(bool linkStatus) - { - linkStatus_ = linkStatus; - } - /** - * It sets the validate status of the program. - * - * @param validateStatus The validate status of the program. - */ - void setValidateStatus(bool validateStatus) - { - validateStatus_ = validateStatus; - } - /** - * This method gets the current link status of the program. - * - * @param sync If `true`, it will wait for the program to be completed before returning the link status. - * @returns The current link status of the program. - */ + + void setLinkStatus(bool linkStatus); + void setValidateStatus(bool validateStatus); bool getLinkStatus(bool sync = false) const; - /** - * This method gets the current validate status of the program. - * - * @param sync If `true`, it will wait for the program to be completed before returning the validate status. - * @returns The current validate status of the program. - */ bool getValidateStatus(bool sync = false) const; - /** - * @returns The number of active attributes in the program. - */ - size_t countActiveAttribs() const - { - waitForCompleted(); - return activeAttribs_.size(); - } - /** - * @returns The active attribute information at the given index. - */ - WebGLActiveInfo getActiveAttrib(int index) - { - return activeAttribs_[index]; - } - /** - * It sets the active attribute information at the given index. - * - * @param index The index of the active attribute. - * @param activeInfo The active attribute information. - */ - void setActiveAttrib(int index, const commandbuffers::ActiveInfo &activeInfo) - { - activeAttribs_[index] = WebGLActiveInfo(activeInfo); - } - /** - * @returns If the active attribute exists at the given index. - */ - bool hasActiveAttrib(int index) - { - return activeAttribs_.find(index) != activeAttribs_.end(); - } - /** - * @returns The number of active uniforms in the program. - */ - size_t countActiveUniforms() const - { - waitForCompleted(); - return activeUniforms_.size(); - } - /** - * @param index The index of the active uniform. - * @returns The active uniform information at the given index. - */ - WebGLActiveInfo getActiveUniform(int index) - { - return activeUniforms_[index]; - } - /** - * It sets the active uniform information at the given index. - * - * @param index The index of the active uniform. - * @param activeInfo The active uniform information. - */ - void setActiveUniform(int index, const commandbuffers::ActiveInfo &activeInfo) - { - activeUniforms_[index] = activeInfo; - } - /** - * @param index The index of the active uniform. - * @returns If the active uniform exists at the given index. - */ - bool hasActiveUniform(int index) - { - return activeUniforms_.find(index) != activeUniforms_.end(); - } - /** - * It sets the attribute location for the given name. - * - * @param name The name of the attribute. - * @param location The location of the attribute. - */ - void setAttribLocation(const std::string &name, int location) - { - attribLocations_[name] = WebGLAttribLocation(id, location, name); - } - /** - * @param name The name of the attribute. - * @returns If the attribute location exists for the given name. - */ - bool hasAttribLocation(const std::string &name) - { - return attribLocations_.find(name) != attribLocations_.end(); - } - /** - * @param name The name of the attribute. - * @returns The attribute location for the given name. - */ - const WebGLAttribLocation &getAttribLocation(const std::string &name) - { - return attribLocations_[name]; - } - /** - * It sets the uniform location for the given name. - * - * @param name The name of the uniform. - * @param location The location of the uniform. - */ + + size_t countActiveAttribs() const; + WebGLActiveInfo getActiveAttrib(int index); + void setActiveAttrib(int index, const commandbuffers::ActiveInfo &activeInfo); + bool hasActiveAttrib(int index); + size_t countActiveUniforms() const; + WebGLActiveInfo getActiveUniform(int index); + void setActiveUniform(int index, const commandbuffers::ActiveInfo &activeInfo); + bool hasActiveUniform(int index); + void setAttribLocation(const std::string &name, int location); + void resetAttribLocations(); + bool hasAttribLocation(const std::string &name); + const WebGLAttribLocation &getAttribLocation(const std::string &name); + std::vector getAttribLocations() const; + void setUniformLocation(const std::string &name, int location) { uniformLocations_[name] = WebGLUniformLocation(id, location, name); } - /** - * @param name The name of the uniform. - * @returns If the uniform location exists for the given name. - */ bool hasUniformLocation(const std::string &name) { return uniformLocations_.find(name) != uniformLocations_.end(); } - /** - * @param name The name of the uniform. - * @returns The uniform location for the given name. - */ const WebGLUniformLocation &getUniformLocation(const std::string &name) { return uniformLocations_[name]; } - /** - * It sets the uniform block index for the given name. - * - * @param name The name of the uniform block. - * @param index The index of the uniform block. - */ void setUniformBlockIndex(const std::string &name, int index) { uniformBlockIndices_[name] = index; } - /** - * @param name The name of the uniform block. - * @returns If the uniform block index exists for the given name. - */ bool hasUniformBlockIndex(const std::string &name) { return uniformBlockIndices_.find(name) != uniformBlockIndices_.end(); } - /** - * @param name The name of the uniform block. - * @returns The uniform block index for the given name. - */ int getUniformBlockIndex(const std::string &name) { return uniformBlockIndices_[name]; @@ -214,17 +87,20 @@ namespace endor public: /** - * It prints the information of the program to stdout, including: - * - * - Link status - * - Active attributes - * - Active uniforms - * - Attribute locations - * - Uniform locations - */ + * It prints the information of the program to stdout, including: + * + * - Link status + * - Active attributes + * - Active uniforms + * - Attribute locations + * - Uniform locations + */ void printInfo() const; private: + std::shared_ptr vertexShader_; + std::shared_ptr fragmentShader_; + std::atomic incomplete_ = true; mutable std::mutex setCompletedMutex_; mutable std::condition_variable setCompletedCv_; diff --git a/src/client/script_bindings/webgl/webgl_rendering_context.cpp b/src/client/script_bindings/webgl/webgl_rendering_context.cpp index 7c9351388..5b39b39a9 100644 --- a/src/client/script_bindings/webgl/webgl_rendering_context.cpp +++ b/src/client/script_bindings/webgl/webgl_rendering_context.cpp @@ -4052,6 +4052,7 @@ namespace endor if (values.size() % 16 != 0) { + Warn(isolate, info[2]); isolate->ThrowException(Exception::TypeError( MakeMethodError(isolate, "uniformMatrix4fv", "Data length must be a multiple of 16"))); return; diff --git a/src/common/command_buffers/details/program.hpp b/src/common/command_buffers/details/program.hpp index 925cae0af..5b12ef9eb 100644 --- a/src/common/command_buffers/details/program.hpp +++ b/src/common/command_buffers/details/program.hpp @@ -5,6 +5,77 @@ namespace commandbuffers { + enum LinkProgramDataSegmentType + { + SEGMENT_ACTIVE_ATTRIB, + SEGMENT_ACTIVE_UNIFORM, + SEGMENT_ATTRIB_LOCATION, + SEGMENT_UNIFORM_LOCATION, + SEGMENT_UNIFORM_BLOCK + }; + + class ActiveInfo + { + public: + ActiveInfo() = default; + ActiveInfo(const ActiveInfo &that) = default; + ActiveInfo(string name, int size, int type) + : name(name) + , size(size) + , type(type) + { + } + + public: + std::string name; + int size; + int type; + }; + + class AttribLocation + { + public: + AttribLocation(string name, int location) + : name(name) + , location(location) + { + } + + public: + std::string name; + int location; + }; + + class UniformLocation + { + public: + UniformLocation(string name, int location, int size) + : name(name) + , location(location) + , size(size) + { + } + + public: + std::string name; + int location; + int size; + }; + + class UniformBlock + { + public: + UniformBlock(string name, int index) + : name(name) + , index(index) + { + } + + public: + std::string name; + int index; + }; + class CreateProgramCommandBufferRequest final : public TrCommandBufferSimpleRequest @@ -95,6 +166,10 @@ namespace commandbuffers : TrCommandBufferSimpleRequest(that) , clientId(that.clientId) { + if (clone) + { + attribLocations = that.attribLocations; + } } std::string toString(const char *line_prefix) const override @@ -104,8 +179,79 @@ namespace commandbuffers return ss.str(); } + public: + TrCommandBufferMessage *serialize() override + { + auto message = new TrCommandBufferMessage(type, size, this); + for (auto &attribLocation : attribLocations) + addAttribLocationSegment(attribLocation, message); + return message; + } + void deserialize(TrCommandBufferMessage &message) override + { + for (size_t i = 0; i < message.getSegmentCount(); i++) + { + auto segment = message.getSegment(i); + if (segment == nullptr) + continue; + + auto segmentChars = segment->toVec(); + char *pSourceData = segmentChars.data(); + + LinkProgramDataSegmentType segmentType; + memcpy(&segmentType, pSourceData, sizeof(LinkProgramDataSegmentType)); + pSourceData += sizeof(LinkProgramDataSegmentType); + + size_t nameSize; + memcpy(&nameSize, pSourceData, sizeof(size_t)); + pSourceData += sizeof(size_t); + + string name(pSourceData, nameSize); + pSourceData += nameSize; + + switch (segmentType) + { + case SEGMENT_ATTRIB_LOCATION: + { + int location; + memcpy(&location, pSourceData, sizeof(int)); + pSourceData += sizeof(int); + attribLocations.push_back(AttribLocation(name, location)); + break; + }; + default: + break; + } + } + } + + private: + vector getSegmentBase(LinkProgramDataSegmentType type, + string &name) + { + vector base; + base.insert(base.end(), + reinterpret_cast(&type), + reinterpret_cast(&type) + sizeof(LinkProgramDataSegmentType)); + size_t nameSize = name.size(); + base.insert(base.end(), + reinterpret_cast(&nameSize), + reinterpret_cast(&nameSize) + sizeof(size_t)); + base.insert(base.end(), name.begin(), name.end()); + return base; + } + void addAttribLocationSegment(AttribLocation &attribLocation, TrCommandBufferMessage *message) + { + auto base = getSegmentBase(SEGMENT_ATTRIB_LOCATION, attribLocation.name); + base.insert(base.end(), + reinterpret_cast(&attribLocation.location), + reinterpret_cast(&attribLocation.location) + sizeof(attribLocation.location)); + message->addSegment(new ipc::TrIpcMessageSegment(base)); + } + public: uint32_t clientId; + vector attribLocations; }; class ValidateProgramCommandBufferRequest final @@ -136,77 +282,6 @@ namespace commandbuffers uint32_t clientId; }; - class ActiveInfo - { - public: - ActiveInfo() = default; - ActiveInfo(const ActiveInfo &that) = default; - ActiveInfo(string name, int size, int type) - : name(name) - , size(size) - , type(type) - { - } - - public: - string name; - int size; - int type; - }; - - class AttribLocation - { - public: - AttribLocation(string name, int location) - : name(name) - , location(location) - { - } - - public: - string name; - int location; - }; - - class UniformLocation - { - public: - UniformLocation(string name, int location, int size) - : name(name) - , location(location) - , size(size) - { - } - - public: - string name; - int location; - int size; - }; - - class UniformBlock - { - public: - UniformBlock(string name, int index) - : name(name) - , index(index) - { - } - - public: - string name; - int index; - }; - - enum LinkProgramCommandBufferResponseSegmentType - { - SEGMENT_ACTIVE_ATTRIB, - SEGMENT_ACTIVE_UNIFORM, - SEGMENT_ATTRIB_LOCATION, - SEGMENT_UNIFORM_LOCATION, - SEGMENT_UNIFORM_BLOCK - }; - class LinkProgramCommandBufferResponse final : public TrCommandBufferSimpleResponse { @@ -262,9 +337,9 @@ namespace commandbuffers auto segmentChars = segment->toVec(); char *pSourceData = segmentChars.data(); - LinkProgramCommandBufferResponseSegmentType segmentType; - memcpy(&segmentType, pSourceData, sizeof(LinkProgramCommandBufferResponseSegmentType)); - pSourceData += sizeof(LinkProgramCommandBufferResponseSegmentType); + LinkProgramDataSegmentType segmentType; + memcpy(&segmentType, pSourceData, sizeof(LinkProgramDataSegmentType)); + pSourceData += sizeof(LinkProgramDataSegmentType); size_t nameSize; memcpy(&nameSize, pSourceData, sizeof(size_t)); @@ -319,13 +394,13 @@ namespace commandbuffers } private: - vector getSegmentBase(LinkProgramCommandBufferResponseSegmentType type, + vector getSegmentBase(LinkProgramDataSegmentType type, string &name) { vector base; base.insert(base.end(), reinterpret_cast(&type), - reinterpret_cast(&type) + sizeof(LinkProgramCommandBufferResponseSegmentType)); + reinterpret_cast(&type) + sizeof(LinkProgramDataSegmentType)); size_t nameSize = name.size(); base.insert(base.end(), reinterpret_cast(&nameSize), @@ -350,7 +425,7 @@ namespace commandbuffers return ActiveInfo(name, size, type); } - void addActiveInfoSegment(ActiveInfo &activeInfo, LinkProgramCommandBufferResponseSegmentType segmentType, TrCommandBufferMessage *message) + void addActiveInfoSegment(ActiveInfo &activeInfo, LinkProgramDataSegmentType segmentType, TrCommandBufferMessage *message) { auto base = getSegmentBase(segmentType, activeInfo.name); base.insert(base.end(), diff --git a/src/renderer/gles/context_app.cpp b/src/renderer/gles/context_app.cpp index 1981787a7..59830d544 100644 --- a/src/renderer/gles/context_app.cpp +++ b/src/renderer/gles/context_app.cpp @@ -325,29 +325,39 @@ void ContextGLApp::setClearStencil(GLint s) GLuint ContextGLApp::createProgram(uint32_t id) { - GLuint program = ObjectManagerRef().CreateProgram(id); - RecordProgramOnCreated(program); - return program; + auto program = ObjectManagerRef().CreateProgram(id); + assert(program != nullptr && "Failed to create program"); + + RecordProgramOnCreated(program->id); + return program->id; } void ContextGLApp::deleteProgram(uint32_t id, GLuint &program) { - program = ObjectManagerRef().FindProgram(id); - ObjectManagerRef().DeleteProgram(id); - - /** - * Reset the program in both "AppGlobal" and "XRFrame" when we receiving a delete program command to avoid the - * context using the deleted program. - */ - resetProgram(program); - RecordProgramOnDeleted(program); + auto program_ptr = ObjectManagerRef().FindProgram(id); + if (program_ptr != nullptr) + { + program = program_ptr->id; + ObjectManagerRef().DeleteProgram(id); + + /** + * Reset the program in both "AppGlobal" and "XRFrame" when we receiving a delete program command to avoid the + * context using the deleted program. + */ + resetProgram(program); + RecordProgramOnDeleted(program); + } } void ContextGLApp::useProgram(uint32_t id, GLuint &program) { - program = ObjectManagerRef().FindProgram(id); - glUseProgram(program); - onProgramChanged(program); + auto program_ptr = ObjectManagerRef().FindProgram(id); + if (program_ptr != nullptr) + { + program = program_ptr->id; + glUseProgram(program); + onProgramChanged(program); + } } void ContextGLApp::bindFramebuffer(GLenum target, optional id, GLuint &framebuffer) @@ -448,10 +458,10 @@ std::optional ContextGLApp::getAttribLoc(commandbuffers::SetVertexAttribC } else { - GLuint program = ObjectManagerRef().FindProgram(req->program); - if (program != 0) [[likely]] + auto program = ObjectManagerRef().FindProgram(req->program); + if (program != nullptr) [[likely]] { - loc = glGetAttribLocation(program, req->locationQueryName.c_str()); + loc = glGetAttribLocation(program->id, req->locationQueryName.c_str()); if (loc == -1) loc = nullopt; // If the location is not found, return nullopt } @@ -462,16 +472,16 @@ std::optional ContextGLApp::getAttribLoc(commandbuffers::SetVertexAttribC optional ContextGLApp::getUniformLoc(commandbuffers::SetUniformCommandBufferRequestBase *req) const { optional loc = nullopt; - if (req->locationAvailable) + if (req->locationAvailable && false) { loc = req->location; } else { - GLuint program = ObjectManagerRef().FindProgram(req->program); - if (program != 0) [[likely]] + auto program = ObjectManagerRef().FindProgram(req->program); + if (program != nullptr) [[likely]] { - loc = glGetUniformLocation(program, req->locationQueryName.c_str()); + loc = glGetUniformLocation(program->id, req->locationQueryName.c_str()); } } return loc; diff --git a/src/renderer/gles/context_app.hpp b/src/renderer/gles/context_app.hpp index 0a330467a..c28581a7a 100644 --- a/src/renderer/gles/context_app.hpp +++ b/src/renderer/gles/context_app.hpp @@ -9,6 +9,7 @@ #include "./context_storage.hpp" #include "./framebuffer.hpp" +#include "./program.hpp" #include "./gpu_command_encoder_impl.hpp" class ContextGLHost; @@ -67,7 +68,9 @@ class ContextGLApp : public ContextGLStorage // Program functions GLuint createProgram(uint32_t id); void deleteProgram(uint32_t id, GLuint &program); + void deleteProgram(uint32_t id); void useProgram(uint32_t id, GLuint &program); + void useProgram(uint32_t id); // Framebuffer functions void bindFramebuffer(GLenum target, std::optional id, GLuint &framebuffer); diff --git a/src/renderer/gles/object_manager.cpp b/src/renderer/gles/object_manager.cpp index 323c87261..4d2a68018 100644 --- a/src/renderer/gles/object_manager.cpp +++ b/src/renderer/gles/object_manager.cpp @@ -45,40 +45,33 @@ namespace gles PrintBuffers(); } - GLuint GLObjectManager::CreateProgram(uint32_t clientId) + std::shared_ptr GLObjectManager::CreateProgram(uint32_t clientId) { - GLuint program = glCreateProgram(); + auto program = make_shared(glCreateProgram()); programs[clientId] = program; return program; } - GLuint GLObjectManager::FindProgram(uint32_t clientId) + std::shared_ptr GLObjectManager::FindProgram(uint32_t clientId) const { if (clientId == 0) - return 0; - return programs[clientId]; + return nullptr; + + auto program = programs.find(clientId); + if (program == programs.end()) + return nullptr; + else + return program->second; } void GLObjectManager::DeleteProgram(uint32_t clientId) { - GLuint program = programs[clientId]; - if (program > 0) - glDeleteProgram(program); programs.erase(clientId); } size_t GLObjectManager::ClearPrograms() { - size_t removed = 0; - for (auto it = programs.begin(); it != programs.end(); ++it) - { - GLuint program = it->second; - if (program > 0) - { - glDeleteProgram(program); - removed++; - } - } + size_t removed = programs.size(); programs.clear(); return removed; } diff --git a/src/renderer/gles/object_manager.hpp b/src/renderer/gles/object_manager.hpp index 887a5991a..1d7ba88d7 100644 --- a/src/renderer/gles/object_manager.hpp +++ b/src/renderer/gles/object_manager.hpp @@ -2,6 +2,7 @@ #include #include "./common.hpp" +#include "./program.hpp" using namespace std; @@ -31,8 +32,8 @@ namespace gles void PrintMemoryUsage(); public: - GLuint CreateProgram(uint32_t clientId); - GLuint FindProgram(uint32_t clientId); + std::shared_ptr CreateProgram(uint32_t clientId); + std::shared_ptr FindProgram(uint32_t clientId) const; void DeleteProgram(uint32_t clientId); size_t ClearPrograms(); @@ -72,7 +73,7 @@ namespace gles private: std::string name; - unordered_map programs; + unordered_map> programs; unordered_map shaders; private: diff --git a/src/renderer/gles/program.hpp b/src/renderer/gles/program.hpp new file mode 100644 index 000000000..5eb9ffe46 --- /dev/null +++ b/src/renderer/gles/program.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include "./common.hpp" + +namespace gles +{ + class GLProgram + { + public: + GLProgram(GLuint id) + : id(id) + { + } + ~GLProgram() + { + if (id > 0) + { + glDeleteProgram(id); + id = 0; + } + } + + operator GLuint() const + { + return id; + } + + /** + * Links the program and checks for errors. + * + * @throws std::runtime_error if linking fails. + */ + void link() const + { + glLinkProgram(id); + + GLenum status; + glGetProgramiv(id, GL_LINK_STATUS, (GLint *)&status); + if (status == GL_FALSE) + { + GLint errorLength; + glGetProgramiv(id, GL_INFO_LOG_LENGTH, &errorLength); + GLchar *errorStr = new GLchar[errorLength]; + glGetProgramInfoLog(id, errorLength, NULL, errorStr); + + std::string msg = "Failed to link program(" + std::to_string(id) + "): " + errorStr; + delete[] errorStr; + throw std::runtime_error(msg); + } + } + void validate() const + { + glValidateProgram(id); + } + + GLint updateAttribLocation(int index, const char *name) + { + assert(index >= 0); + attrib_locations_map[index] = glGetAttribLocation(id, name); + return attrib_locations_map[index]; + } + GLint getAttribLocation(int index) const + { + auto it = attrib_locations_map.find(index); + if (it != attrib_locations_map.end()) + return it->second; + return -1; + } + + public: + GLuint id; + + private: + std::unordered_map attrib_locations_map; + }; +} diff --git a/src/renderer/render_api_opengles.cpp b/src/renderer/render_api_opengles.cpp index 606c09cbf..f96a59665 100644 --- a/src/renderer/render_api_opengles.cpp +++ b/src/renderer/render_api_opengles.cpp @@ -10,6 +10,7 @@ #include "gles/common.hpp" #include "gles/context_storage.hpp" #include "gles/framebuffer.hpp" +#include "gles/program.hpp" #include "gles/object_manager.hpp" #include "gles/gpu_device_impl.hpp" @@ -505,29 +506,32 @@ class RHI_OpenGL : public TrRenderHardwareInterface ApiCallOptions &options) { auto glContext = reqContentRenderer->getContextGL(); - GLuint program = glContext->ObjectManagerRef().FindProgram(req->clientId); - glLinkProgram(program); - reqContentRenderer->getContextGL()->MarkAsDirty(); - - /** - * Check the link status of the program. - */ - GLenum status; - glGetProgramiv(program, GL_LINK_STATUS, (GLint *)&status); - if (status == GL_FALSE) - { - GLint errorLength; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &errorLength); - GLchar *errorStr = new GLchar[errorLength]; - glGetProgramInfoLog(program, errorLength, NULL, errorStr); - DEBUG(LOG_TAG_ERROR, "Failed to link program(%d): %s", program, errorStr); - delete[] errorStr; - - LinkProgramCommandBufferResponse failureRes(req, false); - // reqContentRenderer->sendCommandBufferResponse(failureRes); + auto program = glContext->ObjectManagerRef().FindProgram(req->clientId); + if (program == nullptr) [[unlikely]] + { return; } + vector activeAttribs; + for (const auto &attrib : req->attribLocations) + { + if (attrib.location <= -1) + continue; + + glBindAttribLocation(program->id, attrib.location, attrib.name.c_str()); + cout << "BindAttribLocation(program=" << program->id << ", location=" << attrib.location + << ", name=" << attrib.name.c_str() << ")" << endl; + activeAttribs.push_back(attrib); + } + + // Do program linking and update attribute locations + program->link(); + for (const auto &attrib : activeAttribs) + program->updateAttribLocation(attrib.location, attrib.name.c_str()); + + // Mark context as dirty + reqContentRenderer->getContextGL()->MarkAsDirty(); + // Create response object LinkProgramCommandBufferResponse res(req, true); @@ -535,7 +539,7 @@ class RHI_OpenGL : public TrRenderHardwareInterface * Fetch the locations of the attributes when link successfully. */ GLint numAttributes = 0; - glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &numAttributes); + glGetProgramiv(program->id, GL_ACTIVE_ATTRIBUTES, &numAttributes); for (int i = 0; i < numAttributes; i++) { GLsizei nameLength; @@ -543,11 +547,11 @@ class RHI_OpenGL : public TrRenderHardwareInterface GLenum type; GLchar name[256]; - glGetActiveAttrib(program, i, sizeof(name) - 1, &nameLength, &size, &type, name); + glGetActiveAttrib(program->id, i, sizeof(name) - 1, &nameLength, &size, &type, name); name[nameLength] = '\0'; res.activeAttribs.push_back(ActiveInfo(name, size, type)); - GLint location = glGetAttribLocation(program, name); + GLint location = glGetAttribLocation(program->id, name); res.attribLocations.push_back(AttribLocation(name, location)); DEBUG(DEBUG_TAG, " Attribute[%d](%s) => (size=%d, type=%s)", @@ -561,7 +565,7 @@ class RHI_OpenGL : public TrRenderHardwareInterface * Fetch the locations of the uniforms and attributes when link successfully. */ GLint numUniforms = 0; - glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &numUniforms); + glGetProgramiv(program->id, GL_ACTIVE_UNIFORMS, &numUniforms); for (int i = 0; i < numUniforms; i++) { GLsizei nameLength; @@ -569,11 +573,11 @@ class RHI_OpenGL : public TrRenderHardwareInterface GLenum type; GLchar name[256]; - glGetActiveUniform(program, i, sizeof(name) - 1, &nameLength, &size, &type, name); + glGetActiveUniform(program->id, i, sizeof(name) - 1, &nameLength, &size, &type, name); name[nameLength] = '\0'; res.activeUniforms.push_back(ActiveInfo(name, size, type)); - GLint location = glGetUniformLocation(program, name); + GLint location = glGetUniformLocation(program->id, name); if (location <= -1) continue; @@ -591,22 +595,22 @@ class RHI_OpenGL : public TrRenderHardwareInterface * Fetch the uniform blocks when link successfully. */ GLint numUniformBlocks = 0; - glGetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS, &numUniformBlocks); + glGetProgramiv(program->id, GL_ACTIVE_UNIFORM_BLOCKS, &numUniformBlocks); for (int i = 0; i < numUniformBlocks; i++) { GLsizei nameLength; GLchar name[256]; - glGetActiveUniformBlockName(program, i, sizeof(name) - 1, &nameLength, name); + glGetActiveUniformBlockName(program->id, i, sizeof(name) - 1, &nameLength, name); name[nameLength] = '\0'; - GLuint index = glGetUniformBlockIndex(program, name); + GLuint index = glGetUniformBlockIndex(program->id, name); res.uniformBlocks.push_back(UniformBlock(name, index)); DEBUG(DEBUG_TAG, " UniformBlock[%s] => %d", name, index); } if (TR_UNLIKELY(CheckError(req, reqContentRenderer) != GL_NO_ERROR || options.printsCall)) - PrintDebugInfo(req, to_string(program).c_str(), nullptr, options); + PrintDebugInfo(req, to_string(program->id).c_str(), nullptr, options); reqContentRenderer->sendCommandBufferResponse(res); } TR_OPENGL_FUNC void OnUseProgram(UseProgramCommandBufferRequest *req, @@ -623,9 +627,12 @@ class RHI_OpenGL : public TrRenderHardwareInterface ApiCallOptions &options) { auto glContext = reqContentRenderer->getContextGL(); - GLuint program = glContext->ObjectManagerRef().FindProgram(req->clientId); - glValidateProgram(program); - reqContentRenderer->getContextGL()->MarkAsDirty(); + auto program = glContext->ObjectManagerRef().FindProgram(req->clientId); + if (program != nullptr) [[likely]] + { + program->validate(); + reqContentRenderer->getContextGL()->MarkAsDirty(); + } if (TR_UNLIKELY(CheckError(req, reqContentRenderer) != GL_NO_ERROR || options.printsCall)) PrintDebugInfo(req, nullptr, nullptr, options); } @@ -635,13 +642,13 @@ class RHI_OpenGL : public TrRenderHardwareInterface { auto glContext = reqContentRenderer->getContextGL(); auto program = glContext->ObjectManagerRef().FindProgram(req->program); - glBindAttribLocation(program, req->attribIndex, req->attribName.c_str()); + glBindAttribLocation(program->id, req->attribIndex, req->attribName.c_str()); if (TR_UNLIKELY(CheckError(req, reqContentRenderer) != GL_NO_ERROR || options.printsCall)) DEBUG(DEBUG_TAG, "[%d] GL::BindAttribLocation(program=%d, index=%d, name=%s)", options.isDefaultQueue(), - program, + program->id, req->attribIndex, req->attribName.c_str()); } @@ -653,7 +660,7 @@ class RHI_OpenGL : public TrRenderHardwareInterface auto program = glContext->ObjectManagerRef().FindProgram(req->clientId); GLint value; - glGetProgramiv(program, req->pname, &value); + glGetProgramiv(program->id, req->pname, &value); if (TR_UNLIKELY(CheckError(req, reqContentRenderer) != GL_NO_ERROR || options.printsCall)) PrintDebugInfo(req, to_string(value).c_str(), nullptr, options); @@ -667,9 +674,9 @@ class RHI_OpenGL : public TrRenderHardwareInterface auto glContext = reqContentRenderer->getContextGL(); auto program = glContext->ObjectManagerRef().FindProgram(req->clientId); GLint retSize; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &retSize); + glGetProgramiv(program->id, GL_INFO_LOG_LENGTH, &retSize); GLchar *infoLog = new GLchar[retSize]; - glGetProgramInfoLog(program, retSize, NULL, infoLog); + glGetProgramInfoLog(program->id, retSize, NULL, infoLog); GetProgramInfoLogCommandBufferResponse res(req, string(infoLog)); delete[] infoLog; @@ -687,9 +694,9 @@ class RHI_OpenGL : public TrRenderHardwareInterface ApiCallOptions &options) { auto glContext = reqContentRenderer->getContextGL(); - GLuint program = glContext->ObjectManagerRef().FindProgram(req->program); - GLuint shader = glContext->ObjectManagerRef().FindShader(req->shader); - glAttachShader(program, shader); + auto program = glContext->ObjectManagerRef().FindProgram(req->program); + auto shader = glContext->ObjectManagerRef().FindShader(req->shader); + glAttachShader(program->id, shader); reqContentRenderer->getContextGL()->MarkAsDirty(); if (TR_UNLIKELY(CheckError(req, reqContentRenderer) != GL_NO_ERROR || options.printsCall)) PrintDebugInfo(req, nullptr, nullptr, options); @@ -699,9 +706,9 @@ class RHI_OpenGL : public TrRenderHardwareInterface ApiCallOptions &options) { auto &glObjectManager = reqContentRenderer->getContextGL()->ObjectManagerRef(); - GLuint program = glObjectManager.FindProgram(req->program); - GLuint shader = glObjectManager.FindShader(req->shader); - glDetachShader(program, shader); + auto program = glObjectManager.FindProgram(req->program); + auto shader = glObjectManager.FindShader(req->shader); + glDetachShader(program->id, shader); reqContentRenderer->getContextGL()->MarkAsDirty(); if (TR_UNLIKELY(CheckError(req, reqContentRenderer) != GL_NO_ERROR || options.printsCall)) PrintDebugInfo(req, nullptr, nullptr, options); @@ -1724,7 +1731,7 @@ class RHI_OpenGL : public TrRenderHardwareInterface auto program = glObjectManager.FindProgram(req->program); auto uniformBlockIndex = req->uniformBlockIndex; auto uniformBlockBinding = req->uniformBlockBinding; - glUniformBlockBinding(program, uniformBlockIndex, uniformBlockBinding); + glUniformBlockBinding(program->id, uniformBlockIndex, uniformBlockBinding); if (TR_UNLIKELY(CheckError(req, reqContentRenderer) != GL_NO_ERROR || options.printsCall)) PrintDebugInfo(req, nullptr, nullptr, options);