]> git.tdb.fi Git - libs/gl.git/commitdiff
Add support for array and multiple lights in generated shaders
authorMikko Rasa <tdb@tdb.fi>
Fri, 16 May 2014 17:17:32 +0000 (20:17 +0300)
committerMikko Rasa <tdb@tdb.fi>
Fri, 16 May 2014 17:17:32 +0000 (20:17 +0300)
source/programbuilder.cpp
source/programbuilder.h

index 67aaf57047ec08db186956b73120ac58c7f348a3..f22d1088bad265853e5e94d46e71e92f17a0a268 100644 (file)
@@ -1,6 +1,7 @@
 #include <algorithm>
 #include <cstring>
 #include <msp/strings/format.h>
+#include <msp/strings/utils.h>
 #include "extension.h"
 #include "program.h"
 #include "programbuilder.h"
@@ -59,24 +60,24 @@ const ProgramBuilder::VariableDefinition ProgramBuilder::standard_variables[] =
        { FRAGMENT, "ambient_product_diffuse", "vec4", "ambient_color*material.diffuse", "!g" },
        { FRAGMENT, "rgb_light_shadow", "vec3", "rgb_light*l_shadow", "s" },
        { FRAGMENT, "rgb_light_shadow", "vec3", "rgb_light", "!s" },
-       { FRAGMENT, "rgb_light", "vec3", "vec3(l_diffuse)", "!m!p" },
-       { FRAGMENT, "rgb_light", "vec3", "vec3(l_diffuse+l_specular)", "!mp" },
-       { FRAGMENT, "rgb_light", "vec3", "l_diffuse*light_product_diffuse.rgb", "m!p" },
-       { FRAGMENT, "rgb_light", "vec3", "l_diffuse*light_product_diffuse.rgb+l_specular*light_product_specular.rgb", "mp" },
-       { FRAGMENT, "light_product_diffuse", "vec4", "gl_FrontLightProduct[0].diffuse", "g" },
-       { FRAGMENT, "light_product_diffuse", "vec4", "light_sources[0].diffuse*material.diffuse", "!g" },
-       { FRAGMENT, "light_product_specular", "vec4", "gl_FrontLightProduct[0].specular", "g" },
-       { FRAGMENT, "light_product_specular", "vec4", "light_sources[0].specular*material.specular", "!g" },
+       { FRAGMENT, "rgb_light[i]", "vec3", "vec3(l_diffuse[i])", "!m!p" },
+       { FRAGMENT, "rgb_light[i]", "vec3", "vec3(l_diffuse[i]+l_specular[i])", "!mp" },
+       { FRAGMENT, "rgb_light[i]", "vec3", "l_diffuse[i]*light_product_diffuse[i].rgb", "m!p" },
+       { FRAGMENT, "rgb_light[i]", "vec3", "l_diffuse[i]*light_product_diffuse[i].rgb+l_specular[i]*light_product_specular[i].rgb", "mp" },
+       { FRAGMENT, "light_product_diffuse[i]", "vec4", "gl_FrontLightProduct[i].diffuse", "g" },
+       { FRAGMENT, "light_product_diffuse[i]", "vec4", "light_sources[i].diffuse*material.diffuse", "!g" },
+       { FRAGMENT, "light_product_specular[i]", "vec4", "gl_FrontLightProduct[i].specular", "g" },
+       { FRAGMENT, "light_product_specular[i]", "vec4", "light_sources[i].specular*material.specular", "!g" },
        { FRAGMENT, "reflect_sample", "vec4", "textureCube(environment, env_reflect_dir)", 0 },
        { FRAGMENT, "env_reflect_dir", "vec3", "env_eye_matrix*eye_reflect_dir", 0 },
        { FRAGMENT, "eye_reflect_dir", "vec3", "eye_tbn_matrix*tbn_reflect_dir", "n" },
        { FRAGMENT, "zzz_reflect_dir", "vec3", "reflect(zzz_incident_dir, n_zzz_normal)", 0 },
        { FRAGMENT, "l_shadow", "float", "mix(1.0, shadow_sample, shadow_darkness)", 0 },
        { FRAGMENT, "shadow_sample", "float", "shadow2D(shadow, shd_vertex).r", 0 },
-       { FRAGMENT, "l_diffuse", "float", "max(dot(n_zzz_normal, n_zzz_light_dir), 0.0)", 0 },
-       { FRAGMENT, "l_specular", "float", "pow(max(dot(n_zzz_half_vec, n_zzz_normal), 0.0), material.shininess)", 0 },
-       { FRAGMENT, "n_zzz_half_vec", "vec3", "normalize(zzz_light_dir-zzz_incident_dir)", 0 },
-       { FRAGMENT, "n_zzz_light_dir", "vec3", "normalize(zzz_light_dir)", 0 },
+       { FRAGMENT, "l_diffuse[i]", "float", "max(dot(n_zzz_normal, n_zzz_light_dir[i]), 0.0)", 0 },
+       { FRAGMENT, "l_specular[i]", "float", "pow(max(dot(n_zzz_half_vec[i], n_zzz_normal), 0.0), material.shininess)", 0 },
+       { FRAGMENT, "n_zzz_half_vec[i]", "vec3", "normalize(zzz_light_dir[i]-zzz_incident_dir)", 0 },
+       { FRAGMENT, "n_zzz_light_dir[i]", "vec3", "normalize(zzz_light_dir[i])", 0 },
        { FRAGMENT, "n_tbn_normal", "vec3", "normal_sample*2.0-1.0", "n" },
        { FRAGMENT, "n_eye_normal", "vec3", "normalize(eye_normal)", "!n" },
        { FRAGMENT, "normal_sample", "vec3", "texture2D(normalmap, texture_coord).xyz", 0 },
@@ -85,9 +86,9 @@ const ProgramBuilder::VariableDefinition ProgramBuilder::standard_variables[] =
        { VERTEX, "gl_Position", "vec4", "projection_matrix*eye_vertex", 0 },
        { VERTEX, "shd_vertex", "vec3", "vec3(dot(eye_vertex, gl_EyePlaneS[shadow_unit]), dot(eye_vertex, gl_EyePlaneT[shadow_unit]), dot(eye_vertex, gl_EyePlaneR[shadow_unit]))", "g" },
        { VERTEX, "shd_vertex", "vec3", "(shd_eye_matrix*eye_vertex).xyz", "!g" },
-       { VERTEX, "tbn_light_dir", "vec3", "eye_light_dir*eye_tbn_matrix", 0 },
-       { VERTEX, "eye_light_dir", "vec3", "normalize(eye_light_position.xyz-eye_vertex.xyz*eye_light_position.w)", 0 },
-       { VERTEX, "eye_light_position", "vec4", "light_sources[0].position", 0 },
+       { VERTEX, "tbn_light_dir[i]", "vec3", "eye_light_dir[i]*eye_tbn_matrix", 0 },
+       { VERTEX, "eye_light_dir[i]", "vec3", "normalize(eye_light_position[i].xyz-eye_vertex.xyz*eye_light_position[i].w)", 0 },
+       { VERTEX, "eye_light_position[i]", "vec4", "light_sources[i].position", 0 },
        { VERTEX, "tbn_incident_dir", "vec3", "eye_incident_dir*eye_tbn_matrix", 0 },
        { VERTEX, "eye_incident_dir", "vec3", "normalize(eye_vertex.xyz)", 0 },
        { VERTEX, "eye_tbn_matrix", "mat3", "mat3(eye_tangent, eye_binormal, eye_normal)", 0 },
@@ -116,7 +117,7 @@ const ProgramBuilder::VariableDefinition ProgramBuilder::standard_variables[] =
        { UNIFORM, "eye_obj_normal_matrix", "mat3", "gl_NormalMatrix", 0 },
        { UNIFORM, "projection_matrix", "mat4", "gl_ProjectionMatrix", 0 },
        { UNIFORM, "shd_eye_matrix", "mat4", 0, 0 },
-       { UNIFORM, "light_sources", "struct { vec4 position; vec4 diffuse; vec4 specular; }[2]", "gl_LightSource", 0 },
+       { UNIFORM, "light_sources", "struct { vec4 position; vec4 diffuse; vec4 specular; }[MAX_LIGHTS]", "gl_LightSource[i]", 0 },
        { UNIFORM, "ambient_color", "vec4", 0, 0 },
        { UNIFORM, "material", "struct { vec4 ambient; vec4 diffuse; vec4 specular; float shininess; }", "gl_FrontMaterial", 0 },
 
@@ -294,6 +295,10 @@ void ProgramBuilder::add_shaders(Program &prog) const
                }
        }
 
+       // Array sizes need to be resolved for inline processing
+       for(list<ShaderVariable>::iterator i=variables.end(); i!=variables.begin(); )
+               (--i)->resolve_array(features);
+
        for(list<ShaderVariable *>::const_iterator i=resolved_vars.begin(); i!=resolved_vars.end(); ++i)
                (*i)->check_inline(features.legacy, !optimize);
 
@@ -352,10 +357,63 @@ string ProgramBuilder::create_source(const list<ShaderVariable *> &variables, Va
 
        source += "void main()\n{\n";
 
+       list<ShaderVariable *> loop_vars;
+       unsigned loop_size = 0;
        for(list<ShaderVariable *>::const_iterator i=variables.begin(); i!=variables.end(); ++i)
        {
+               if(!loop_vars.empty() && !loop_vars.back()->in_loop && (*i)->array_size!=loop_size)
+               {
+                       /* Declare all variables that need to be visible outside the loop.
+                       Output variables are already declared. */
+                       for(list<ShaderVariable *>::const_iterator j=loop_vars.begin(); j!=loop_vars.end(); ++j)
+                       {
+                               InterfaceFlags interface = (*j)->get_interface_flags(scope);
+                               if(!(*j)->in_loop && !(interface&OUTPUT))
+                                       source += format("\t%s;\n", (*j)->create_declaration());
+                       }
+
+                       source += format("\tfor(int i=0; i<%d; ++i)\n\t{\n", loop_size);
+                       for(list<ShaderVariable *>::const_iterator j=loop_vars.begin(); j!=loop_vars.end(); ++j)
+                       {
+                               if((*j)->variable->scope==scope && !(*j)->inlined)
+                               {
+                                       string decl;
+                                       if((*j)->in_loop)
+                                               decl = (*j)->create_declaration(0, true);
+                                       else
+                                       {
+                                               decl = (*j)->resolved_name;
+                                               if(!(*j)->array_sum)
+                                                       decl += "[i]";
+                                       }
+                                       const char *oper = ((*j)->array_sum ? "+=" : "=");
+                                       source += format("\t\t%s %s %s;\n", decl, oper, (*j)->create_expression("i"));
+                               }
+
+                               InterfaceFlags interface = (*j)->get_interface_flags(scope);
+                               if(interface&OUTPUT)
+                               {
+                                       string expr = ((*j)->inlined ? (*j)->create_expression("i") : (*j)->resolved_name+"[i]");
+                                       source += format("\t\t%c_%s[i] = %s;\n", interfaces[scope], (*j)->resolved_name, expr);
+                               }
+                       }
+                       source += "\t}\n";
+
+                       loop_vars.clear();
+               }
+
                InterfaceFlags interface = (*i)->get_interface_flags(scope);
 
+               if((*i)->array_size>1)
+               {
+                       if((*i)->variable->scope==scope || (interface&OUTPUT))
+                       {
+                               loop_size = (*i)->array_size;
+                               loop_vars.push_back(*i);
+                       }
+                       continue;
+               }
+
                if((*i)->variable->scope==scope && !(*i)->inlined)
                {
                        string decl = ((interface&GOAL) ? (*i)->resolved_name : (*i)->create_declaration());
@@ -401,7 +459,7 @@ bool ProgramBuilder::evaluate_flags(const char *flags) const
        return cond;
 }
 
-ProgramBuilder::MatchLevel ProgramBuilder::name_match(const char *n1, const char *n2, const char **space)
+ProgramBuilder::MatchType ProgramBuilder::name_match(const char *n1, const char *n2, const char **space)
 {
        int i = 0;
        int zzz = -1;
@@ -435,13 +493,14 @@ ProgramBuilder::MatchLevel ProgramBuilder::name_match(const char *n1, const char
                ++n2;
                ++i;
        }
-       return (!*n1 && !*n2) ? zzz>=0 ? FUZZY : EXACT : NO_MATCH;
+       return (!*n1 && !*n2) ? (zzz>=0 ? FUZZY : EXACT) : ((*n1=='[' || *n2=='[') ? ARRAY : NO_MATCH);
 }
 
 bool ProgramBuilder::parse_identifier(const char *ptr, unsigned &start, unsigned &length)
 {
        bool found = false;
        bool member = false;
+       bool subscript = false;
        for(const char *i=ptr;; ++i)
        {
                if(!found)
@@ -463,10 +522,23 @@ bool ProgramBuilder::parse_identifier(const char *ptr, unsigned &start, unsigned
                }
                else
                {
-                       if(!isalnum(*i) && *i!='_')
+                       if(subscript)
+                       {
+                               if(*i==']')
+                               {
+                                       length = i+1-(ptr+start);
+                                       return true;
+                               }
+                               else if(!isalpha(*i) || i>ptr+start+length+1)
+                                       return true;
+                       }
+                       else if(!isalnum(*i) && *i!='_')
                        {
                                length = i-(ptr+start);
-                               return true;
+                               if(*i=='[')
+                                       subscript = true;
+                               else
+                                       return true;
                        }
                }
        }
@@ -512,6 +584,7 @@ ProgramBuilder::StandardFeatures::StandardFeatures():
        texture(false),
        material(false),
        lighting(false),
+       max_lights(1),
        specular(false),
        normalmap(false),
        shadow(false),
@@ -550,17 +623,36 @@ ProgramBuilder::ShaderVariable::ShaderVariable(const std::string &n):
        variable(0),
        resolved_name(n),
        fuzzy_space(name.find("zzz")!=string::npos),
+       array_sum(false),
+       array_size(0),
        inlined(false),
-       inline_parens(false)
-{ }
+       inline_parens(false),
+       in_loop(false)
+{
+       string::size_type bracket = name.find('[');
+       if(bracket!=string::npos)
+               array_subscript = name.substr(bracket+1, name.size()-bracket-2);
+}
 
 void ProgramBuilder::ShaderVariable::resolve(const VariableDefinition &var)
 {
        variable = &var;
        const char *space = 0;
-       if(name_match(var.name, resolved_name.c_str(), &space)==FUZZY)
-               resolve_space(string(space, 3));
+       MatchType match = name_match(var.name, resolved_name.c_str(), &space);
 
+       if(match==FUZZY)
+               resolve_space(string(space, 3));
+       else if(match==ARRAY)
+       {
+               if(array_subscript.empty())
+                       array_sum = true;
+               else if(var.scope==UNIFORM)
+               {
+                       const char *bracket = strrchr(variable->type, '[');
+                       if(bracket)
+                               array_subscript = string(bracket+1, strlen(bracket)-2);
+               }
+       }
 }
 
 void ProgramBuilder::ShaderVariable::resolve(ShaderVariable &var)
@@ -590,6 +682,51 @@ void ProgramBuilder::ShaderVariable::resolve_space(const string &space)
                        (*i)->resolve_space(space);
 }
 
+void ProgramBuilder::ShaderVariable::resolve_array(const StandardFeatures &features, unsigned size_hint)
+{
+       if(array_size)
+               return;
+       if(!array_sum && array_subscript.empty())
+               return;
+
+       if(!array_subscript.empty())
+       {
+               string::size_type bracket = resolved_name.find('[');
+               if(bracket!=string::npos)
+                       resolved_name = resolved_name.substr(0, bracket);
+       }
+
+       if(variable && variable->scope==UNIFORM)
+       {
+               if(array_subscript=="MAX_LIGHTS")
+                       array_size = features.max_lights;
+               else if(isnumrc(array_subscript))
+                       array_size = lexical_cast<unsigned>(array_subscript);
+               else
+                       throw invalid_variable_definition("invalid array size");
+       }
+
+       if(!array_size)
+       {
+               for(list<ShaderVariable *>::const_iterator i=referenced_vars.begin(); i!=referenced_vars.end(); ++i)
+                       if((*i)->array_size)
+                       {
+                               array_size = (*i)->array_size;
+                               break;
+                       }
+       }
+
+       if(!array_size && size_hint)
+               array_size = size_hint;
+
+       if(array_size)
+       {
+               for(list<ShaderVariable *>::const_iterator i=referenced_vars.begin(); i!=referenced_vars.end(); ++i)
+                       if(!(*i)->array_subscript.empty() && !(*i)->array_size)
+                               (*i)->resolve_array(features, array_size);
+       }
+}
+
 void ProgramBuilder::ShaderVariable::add_reference(ShaderVariable &var)
 {
        referenced_vars.push_back(&var);
@@ -610,12 +747,13 @@ void ProgramBuilder::ShaderVariable::check_inline(bool allow_legacy, bool trivia
 {
        if(variable->expression)
        {
+               if(array_sum && array_size>1)
+                       return;
                if(!allow_legacy && !strncmp(variable->expression, "gl_", 3))
                        return;
 
                // Never inline goal variables
-               unsigned total_refs = referenced_by.size();
-               if(!total_refs)
+               if(referenced_by.empty())
                        return;
 
                // Inline an expression consisting of a single identifier
@@ -630,10 +768,24 @@ void ProgramBuilder::ShaderVariable::check_inline(bool allow_legacy, bool trivia
                if(trivial_only)
                        return;
 
+               /* If all references to the variable come from arrays in the same scope
+               and of the same size, the variable can be embedded in the loop. */
+               in_loop = (array_size>1 && !array_sum);
+               for(list<ShaderVariable *>::const_iterator i=referenced_by.begin(); i!=referenced_by.end(); ++i)
+                       if((*i)->variable->scope!=variable->scope || (*i)->array_size!=array_size)
+                               in_loop = false;
+               
+               /* Count all refs to this variable.  Refs from array variables count once
+               per loop iteration. */
+               unsigned total_refs = 0;
                unsigned in_scope_refs = 0;
                for(list<ShaderVariable *>::const_iterator i=referenced_by.begin(); i!=referenced_by.end(); ++i)
+               {
+                       unsigned count = max((*i)->array_size*!in_loop, 1U);
+                       total_refs += count;
                        if((*i)->variable->scope==variable->scope)
-                               ++in_scope_refs;
+                               in_scope_refs += count;
+               }
 
                /* Inline if there's only one ref, or if all refs are in other scopes.
                In the latter case, the actual inlining will happen in the interface
@@ -690,46 +842,61 @@ ProgramBuilder::InterfaceFlags ProgramBuilder::ShaderVariable::get_interface_fla
        return static_cast<InterfaceFlags>(flags);
 }
 
-string ProgramBuilder::ShaderVariable::create_declaration(char interface) const
+string ProgramBuilder::ShaderVariable::create_declaration(char interface, bool loop) const
 {
-       if(variable->scope==UNIFORM)
+       if(variable->scope==UNIFORM && !array_subscript.empty())
        {
                const char *bracket = strrchr(variable->type, '[');
                if(bracket)
-                       return format("%s %s%s", string(variable->type, bracket), resolved_name, bracket);
+                       return format("%s %s[%d]", string(variable->type, bracket), resolved_name, array_size);
        }
 
+       string array;
+       if(!array_sum && array_size>1 && !loop)
+               array = format("[%d]", array_size);
+
        if(interface)
-               return format("%s %c_%s", variable->type, interface, resolved_name);
+               return format("%s %c_%s%s", variable->type, interface, resolved_name, array);
        else
-               return format("%s %s", variable->type, resolved_name);
+               return format("%s %s%s", variable->type, resolved_name, array);
 }
 
-string ProgramBuilder::ShaderVariable::create_replacement(VariableScope from_scope) const
+string ProgramBuilder::ShaderVariable::create_replacement(VariableScope from_scope, const char *loop) const
 {
        string replacement = resolved_name;
+       InterfaceFlags interface = NO_INTERFACE;
        if(variable)
        {
-               InterfaceFlags interface = get_interface_flags(from_scope);
+               interface = get_interface_flags(from_scope);
                if((interface&INPUT) && interfaces[from_scope-1])
                        replacement = format("%c_%s", interfaces[from_scope-1], replacement);
                else if(inlined)
                {
-                       replacement = create_expression();
+                       replacement = create_expression(loop);
                        if(inline_parens)
                                replacement = "("+replacement+")";
+                       return replacement;
                }
        }
 
+       // Add an array subscript, unless the variable is embedded in a loop
+       if(!array_subscript.empty() && !in_loop)
+       {
+               if(loop)
+                       return format("%s[%s]", replacement, loop);
+               else if(!variable || variable->scope==UNIFORM)
+                       return replacement+"[0]";
+       }
+
        return replacement;
 }
 
-string ProgramBuilder::ShaderVariable::create_expression() const
+string ProgramBuilder::ShaderVariable::create_expression(const char *loop) const
 {
        map<string, string> replace_map;
        for(list<ShaderVariable *>::const_iterator i=referenced_vars.begin(); i!=referenced_vars.end(); ++i)
        {
-               string replacement = (*i)->create_replacement(variable->scope);
+               string replacement = (*i)->create_replacement(variable->scope, loop);
                if(replacement!=(*i)->name)
                        replace_map[(*i)->name] = replacement;
        }
@@ -747,6 +914,7 @@ ProgramBuilder::StandardFeatures::Loader::Loader(StandardFeatures &f):
        add("custom",    &StandardFeatures::custom);
        add("lighting",  &StandardFeatures::lighting);
        add("material",  &StandardFeatures::material);
+       add("max_lights", &StandardFeatures::max_lights);
        add("normalmap", &StandardFeatures::normalmap);
        add("reflection", &StandardFeatures::reflection);
        add("shadow",    &StandardFeatures::shadow);
index 69170e02ef979f543b16f1fc53ef0c9adbc0c2d1..cca0c14912165616eecc7f9838e59671a9a2ebb1 100644 (file)
@@ -32,6 +32,7 @@ public:
                bool texture;
                bool material;
                bool lighting;
+               unsigned max_lights;
                bool specular;
                bool normalmap;
                bool shadow;
@@ -79,31 +80,37 @@ private:
                std::string resolved_name;
                bool fuzzy_space;
                std::string resolved_space;
+               bool array_sum;
+               std::string array_subscript;
+               unsigned array_size;
                std::list<ShaderVariable *> referenced_vars;
                std::list<ShaderVariable *> referenced_by;
                bool inlined;
                bool inline_parens;
+               bool in_loop;
 
                ShaderVariable(const std::string &);
 
                void resolve(const VariableDefinition &);
                void resolve(ShaderVariable &);
                void resolve_space(const std::string &);
+               void resolve_array(const StandardFeatures &, unsigned = 0);
                void add_reference(ShaderVariable &);
                void update_reference(ShaderVariable &, ShaderVariable &);
                void check_inline(bool, bool);
                bool is_referenced_from(VariableScope) const;
                InterfaceFlags get_interface_flags(VariableScope) const;
-               std::string create_declaration(char = 0) const;
-               std::string create_replacement(VariableScope) const;
-               std::string create_expression() const;
+               std::string create_declaration(char = 0, bool = false) const;
+               std::string create_replacement(VariableScope, const char * = 0) const;
+               std::string create_expression(const char * = 0) const;
        };
 
-       enum MatchLevel
+       enum MatchType
        {
                NO_MATCH,
                EXACT,
-               FUZZY
+               FUZZY,
+               ARRAY
        };
 
        StandardFeatures features;
@@ -123,7 +130,7 @@ public:
 private:
        std::string create_source(const std::list<ShaderVariable *> &, VariableScope) const;
        bool evaluate_flags(const char *) const;
-       static MatchLevel name_match(const char *, const char *, const char ** = 0);
+       static MatchType name_match(const char *, const char *, const char ** = 0);
        static bool parse_identifier(const char *, unsigned &, unsigned &);
        static std::vector<std::string> extract_identifiers(const char *);
        static std::string replace_identifiers(const char *, const std::map<std::string, std::string> &);